2020-10-10 18:42:56 -05:00
function love . load ( )
2019-05-22 22:57:34 -05:00
highscores = { }
2021-10-16 14:15:44 -05:00
love.graphics . setDefaultFilter ( " linear " , " nearest " )
2020-10-10 20:17:48 -05:00
require " load.rpc "
2019-05-22 22:57:34 -05:00
require " load.graphics "
require " load.fonts "
require " load.sounds "
require " load.bgm "
require " load.save "
2021-01-24 13:55:35 -06:00
require " load.bigint "
2021-03-03 09:33:10 -06:00
require " load.version "
2019-05-22 22:57:34 -05:00
loadSave ( )
2021-08-15 22:50:00 -05:00
require " funcs "
2019-05-22 22:57:34 -05:00
require " scene "
2021-02-22 20:43:01 -06:00
2020-12-20 08:45:49 -06:00
--config["side_next"] = false
--config["reverse_rotate"] = true
2021-02-22 20:43:01 -06:00
--config["das_last_key"] = false
2021-03-11 07:33:05 -06:00
--config["fullscreen"] = false
2019-05-29 20:55:01 -05:00
love.window . setMode ( love.graphics . getWidth ( ) , love.graphics . getHeight ( ) , { resizable = true } ) ;
2021-05-23 13:07:07 -05:00
2021-05-23 13:57:04 -05:00
-- used for screenshots
GLOBAL_CANVAS = love.graphics . newCanvas ( )
2019-05-29 20:55:01 -05:00
2023-07-01 22:15:14 -05:00
-- aliasing to prevent people using math.random by accident
math.random = love.math . random
math.randomseed = love.math . setRandomSeed
math.randomseed ( os.time ( ) )
2021-01-24 13:55:35 -06:00
-- init config
2021-03-11 08:24:19 -06:00
initConfig ( )
2021-03-11 07:33:05 -06:00
2021-03-11 08:24:19 -06:00
love.window . setFullscreen ( config [ " fullscreen " ] )
if config.secret then playSE ( " welcome " ) end
2020-12-18 20:25:09 -06:00
2021-03-11 08:24:19 -06:00
-- import custom modules
initModules ( )
end
2020-11-02 21:47:58 -06:00
2021-03-11 08:24:19 -06:00
function initModules ( )
2021-12-04 23:17:25 -06:00
-- replays are not loaded here, but they are cleared
replays = { }
2020-11-02 21:47:58 -06:00
game_modes = { }
mode_list = love.filesystem . getDirectoryItems ( " tetris/modes " )
for i = 1 , # mode_list do
2021-08-15 22:50:00 -05:00
if ( mode_list [ i ] ~= " gamemode.lua " and string.sub ( mode_list [ i ] , - 4 ) == " .lua " ) then
2020-11-02 21:47:58 -06:00
game_modes [ # game_modes + 1 ] = require ( " tetris.modes. " .. string.sub ( mode_list [ i ] , 1 , - 5 ) )
end
end
rulesets = { }
rule_list = love.filesystem . getDirectoryItems ( " tetris/rulesets " )
for i = 1 , # rule_list do
2021-08-15 22:50:00 -05:00
if ( rule_list [ i ] ~= " ruleset.lua " and string.sub ( rule_list [ i ] , - 4 ) == " .lua " ) then
2020-11-02 21:47:58 -06:00
rulesets [ # rulesets + 1 ] = require ( " tetris.rulesets. " .. string.sub ( rule_list [ i ] , 1 , - 5 ) )
end
end
--sort mode/rule lists
local function padnum ( d ) return ( " %03d%s " ) : format ( # d , d ) end
2020-11-08 15:19:01 -06:00
table.sort ( game_modes , function ( a , b )
return tostring ( a.name ) : gsub ( " %d+ " , padnum ) < tostring ( b.name ) : gsub ( " %d+ " , padnum ) end )
2020-11-02 21:47:58 -06:00
table.sort ( rulesets , function ( a , b )
2020-11-08 15:19:01 -06:00
return tostring ( a.name ) : gsub ( " %d+ " , padnum ) < tostring ( b.name ) : gsub ( " %d+ " , padnum ) end )
2019-05-22 22:57:34 -05:00
end
function love . draw ( )
2021-05-23 13:57:04 -05:00
love.graphics . setCanvas ( GLOBAL_CANVAS )
love.graphics . clear ( )
love.graphics . push ( )
2020-10-09 17:43:22 -05:00
2019-05-29 20:55:01 -05:00
-- get offset matrix
local width = love.graphics . getWidth ( )
local height = love.graphics . getHeight ( )
local scale_factor = math.min ( width / 640 , height / 480 )
love.graphics . translate (
( width - scale_factor * 640 ) / 2 ,
( height - scale_factor * 480 ) / 2
)
love.graphics . scale ( scale_factor )
2021-05-23 13:07:07 -05:00
2019-05-22 22:57:34 -05:00
scene : render ( )
2021-09-28 18:13:16 -05:00
2021-10-08 19:06:36 -05:00
if config.gamesettings . display_gamemode == 1 or scene.title == " Title " then
love.graphics . setFont ( font_3x5_2 )
love.graphics . setColor ( 1 , 1 , 1 , 1 )
2021-10-18 22:27:54 -05:00
love.graphics . printf (
2022-04-28 13:47:31 -05:00
string.format ( " %.2f " , 1.0 / love.timer . getAverageDelta ( ) ) ..
2021-10-18 22:27:54 -05:00
" fps - " .. version , 0 , 460 , 635 , " right "
)
2021-10-08 19:06:36 -05:00
end
2021-09-28 18:13:16 -05:00
2019-05-22 22:57:34 -05:00
love.graphics . pop ( )
2021-05-23 13:07:07 -05:00
2021-05-23 13:57:04 -05:00
love.graphics . setCanvas ( )
love.graphics . setColor ( 1 , 1 , 1 , 1 )
love.graphics . draw ( GLOBAL_CANVAS )
2019-05-22 22:57:34 -05:00
end
2020-11-08 14:55:06 -06:00
function love . keypressed ( key , scancode )
2019-05-22 22:57:34 -05:00
-- global hotkeys
2021-09-09 18:02:38 -05:00
if scancode == " f11 " then
2019-05-22 22:57:34 -05:00
config [ " fullscreen " ] = not config [ " fullscreen " ]
2021-03-11 07:33:05 -06:00
saveConfig ( )
2019-05-22 22:57:34 -05:00
love.window . setFullscreen ( config [ " fullscreen " ] )
2020-11-10 16:37:59 -06:00
elseif scancode == " f2 " and scene.title ~= " Input Config " and scene.title ~= " Game " then
scene = InputConfigScene ( )
2020-12-21 15:00:03 -06:00
switchBGM ( nil )
2021-10-16 18:08:01 -05:00
loadSave ( )
2020-12-19 19:31:14 -06:00
-- secret sound playing :eyes:
elseif scancode == " f8 " and scene.title == " Title " then
config.secret = not config.secret
saveConfig ( )
2020-12-19 19:43:57 -06:00
scene.restart_message = true
2020-12-19 19:31:14 -06:00
if config.secret then playSE ( " mode_decide " )
else playSE ( " erase " ) end
2021-05-23 13:07:07 -05:00
-- f12 is reserved for saving screenshots
2021-10-16 18:10:33 -05:00
elseif scancode == " f12 " then
local ss_name = os.date ( " ss/%Y-%m-%d_%H-%M-%S.png " )
2021-06-03 15:00:33 -05:00
local info = love.filesystem . getInfo ( " ss " , " directory " )
if not info then
2021-03-01 19:37:44 -06:00
love.filesystem . remove ( " ss " )
love.filesystem . createDirectory ( " ss " )
end
2022-03-03 13:19:56 -06:00
print ( " Saving screenshot as " .. love.filesystem . getSaveDirectory ( ) .. " / " .. ss_name )
2021-05-23 13:57:04 -05:00
GLOBAL_CANVAS : newImageData ( ) : encode ( " png " , ss_name )
2021-01-29 16:29:27 -06:00
-- function keys are reserved
elseif string.match ( scancode , " ^f[1-9]$ " ) or string.match ( scancode , " ^f[1-9][0-9]+$ " ) then
return
-- escape is reserved for menu_back
elseif scancode == " escape " then
scene : onInputPress ( { input = " menu_back " , type = " key " , key = key , scancode = scancode } )
2020-11-10 16:37:59 -06:00
-- pass any other key to the scene, with its configured mapping
2019-05-22 22:57:34 -05:00
else
2020-11-08 15:19:01 -06:00
local input_pressed = nil
if config.input and config.input . keys then
input_pressed = config.input . keys [ scancode ]
end
2020-11-08 14:55:06 -06:00
scene : onInputPress ( { input = input_pressed , type = " key " , key = key , scancode = scancode } )
2019-05-22 22:57:34 -05:00
end
end
2020-11-08 14:55:06 -06:00
function love . keyreleased ( key , scancode )
2020-11-10 16:37:59 -06:00
-- escape is reserved for menu_back
if scancode == " escape " then
2020-11-08 15:19:01 -06:00
scene : onInputRelease ( { input = " menu_back " , type = " key " , key = key , scancode = scancode } )
2020-11-10 16:37:59 -06:00
-- function keys are reserved
elseif string.match ( scancode , " ^f[1-9]$ " ) or string.match ( scancode , " ^f[1-9][0-9]+$ " ) then
return
-- handle all other keys; tab is reserved, but the input config scene keeps it from getting configured as a game input, so pass tab to the scene here
2020-11-08 14:55:06 -06:00
else
2020-11-08 15:19:01 -06:00
local input_released = nil
if config.input and config.input . keys then
input_released = config.input . keys [ scancode ]
end
2020-11-08 14:55:06 -06:00
scene : onInputRelease ( { input = input_released , type = " key " , key = key , scancode = scancode } )
end
end
function love . joystickpressed ( joystick , button )
2020-11-08 15:19:01 -06:00
local input_pressed = nil
if
config.input and
config.input . joysticks and
config.input . joysticks [ joystick : getName ( ) ] and
config.input . joysticks [ joystick : getName ( ) ] . buttons
then
input_pressed = config.input . joysticks [ joystick : getName ( ) ] . buttons [ button ]
end
scene : onInputPress ( { input = input_pressed , type = " joybutton " , name = joystick : getName ( ) , button = button } )
2020-11-08 14:55:06 -06:00
end
function love . joystickreleased ( joystick , button )
2020-11-08 15:19:01 -06:00
local input_released = nil
if
config.input and
config.input . joysticks and
config.input . joysticks [ joystick : getName ( ) ] and
config.input . joysticks [ joystick : getName ( ) ] . buttons
then
input_released = config.input . joysticks [ joystick : getName ( ) ] . buttons [ button ]
end
scene : onInputRelease ( { input = input_released , type = " joybutton " , name = joystick : getName ( ) , button = button } )
2020-11-08 14:55:06 -06:00
end
function love . joystickaxis ( joystick , axis , value )
2020-11-08 15:19:01 -06:00
local input_pressed = nil
local positive_released = nil
local negative_released = nil
if
config.input and
config.input . joysticks and
config.input . joysticks [ joystick : getName ( ) ] and
config.input . joysticks [ joystick : getName ( ) ] . axes and
config.input . joysticks [ joystick : getName ( ) ] . axes [ axis ]
then
2021-02-24 15:58:42 -06:00
if math.abs ( value ) >= 1 then
2021-03-07 15:03:02 -06:00
input_pressed = config.input . joysticks [ joystick : getName ( ) ] . axes [ axis ] [ value >= 1 and " positive " or " negative " ]
2020-11-08 15:19:01 -06:00
end
positive_released = config.input . joysticks [ joystick : getName ( ) ] . axes [ axis ] . positive
negative_released = config.input . joysticks [ joystick : getName ( ) ] . axes [ axis ] . negative
end
2021-02-24 15:58:42 -06:00
if math.abs ( value ) >= 1 then
2020-11-08 15:19:01 -06:00
scene : onInputPress ( { input = input_pressed , type = " joyaxis " , name = joystick : getName ( ) , axis = axis , value = value } )
else
scene : onInputRelease ( { input = positive_released , type = " joyaxis " , name = joystick : getName ( ) , axis = axis , value = value } )
scene : onInputRelease ( { input = negative_released , type = " joyaxis " , name = joystick : getName ( ) , axis = axis , value = value } )
end
2020-11-08 14:55:06 -06:00
end
2021-03-07 15:42:33 -06:00
local last_hat_direction = " "
2021-03-07 19:43:55 -06:00
local directions = {
[ " u " ] = " up " ,
[ " d " ] = " down " ,
[ " l " ] = " left " ,
[ " r " ] = " right " ,
}
2021-03-07 15:42:33 -06:00
2020-11-08 14:55:06 -06:00
function love . joystickhat ( joystick , hat , direction )
2020-11-08 15:19:01 -06:00
local input_pressed = nil
local has_hat = false
if
config.input and
config.input . joysticks and
config.input . joysticks [ joystick : getName ( ) ] and
config.input . joysticks [ joystick : getName ( ) ] . hats and
config.input . joysticks [ joystick : getName ( ) ] . hats [ hat ]
then
if direction ~= " c " then
input_pressed = config.input . joysticks [ joystick : getName ( ) ] . hats [ hat ] [ direction ]
end
has_hat = true
end
if input_pressed then
2021-03-07 15:42:33 -06:00
for i = 1 , # direction do
local char = direction : sub ( i , i )
local _ , count = last_hat_direction : gsub ( char , char )
if count == 0 then
2021-03-07 19:43:55 -06:00
scene : onInputPress ( { input = config.input . joysticks [ joystick : getName ( ) ] . hats [ hat ] [ char ] , type = " joyhat " , name = joystick : getName ( ) , hat = hat , direction = char } )
end
end
for i = 1 , # last_hat_direction do
local char = last_hat_direction : sub ( i , i )
local _ , count = direction : gsub ( char , char )
if count == 0 then
scene : onInputRelease ( { input = config.input . joysticks [ joystick : getName ( ) ] . hats [ hat ] [ char ] , type = " joyhat " , name = joystick : getName ( ) , hat = hat , direction = char } )
2021-03-07 15:42:33 -06:00
end
end
last_hat_direction = direction
2020-11-08 15:19:01 -06:00
elseif has_hat then
for i , direction in ipairs { " d " , " l " , " ld " , " lu " , " r " , " rd " , " ru " , " u " } do
scene : onInputRelease ( { input = config.input . joysticks [ joystick : getName ( ) ] . hats [ hat ] [ direction ] , type = " joyhat " , name = joystick : getName ( ) , hat = hat , direction = direction } )
end
2021-03-07 19:43:55 -06:00
last_hat_direction = " "
2020-11-08 15:19:01 -06:00
elseif direction ~= " c " then
2021-03-07 15:42:33 -06:00
for i = 1 , # direction do
local char = direction : sub ( i , i )
local _ , count = last_hat_direction : gsub ( char , char )
if count == 0 then
2021-03-07 19:43:55 -06:00
scene : onInputPress ( { input = directions [ char ] , type = " joyhat " , name = joystick : getName ( ) , hat = hat , direction = char } )
end
end
for i = 1 , # last_hat_direction do
local char = last_hat_direction : sub ( i , i )
local _ , count = direction : gsub ( char , char )
if count == 0 then
scene : onInputRelease ( { input = directions [ char ] , type = " joyhat " , name = joystick : getName ( ) , hat = hat , direction = char } )
2021-03-07 15:42:33 -06:00
end
end
last_hat_direction = direction
2020-11-08 15:19:01 -06:00
else
for i , direction in ipairs { " d " , " l " , " ld " , " lu " , " r " , " rd " , " ru " , " u " } do
scene : onInputRelease ( { input = nil , type = " joyhat " , name = joystick : getName ( ) , hat = hat , direction = direction } )
end
2021-03-07 19:43:55 -06:00
last_hat_direction = " "
2020-11-08 15:19:01 -06:00
end
2020-11-08 14:55:06 -06:00
end
2021-09-20 22:33:27 -05:00
function love . wheelmoved ( x , y )
scene : onInputPress ( { input = nil , type = " wheel " , x = x , y = y } )
end
2019-05-22 22:57:34 -05:00
function love . focus ( f )
2021-09-20 22:33:27 -05:00
if f then
resumeBGM ( true )
2019-05-22 22:57:34 -05:00
else
2021-09-20 22:33:27 -05:00
pauseBGM ( true )
2019-05-22 22:57:34 -05:00
end
end
2021-01-28 22:13:17 -06:00
function love . resize ( w , h )
2023-07-01 22:15:14 -05:00
GLOBAL_CANVAS : release ( )
GLOBAL_CANVAS = love.graphics . newCanvas ( w , h )
2021-05-23 13:07:07 -05:00
end
2023-07-01 22:27:10 -05:00
-- higher values of TARGET_FPS will make the game run "faster"
-- since the game is mostly designed for 60 FPS
2021-05-23 13:07:07 -05:00
local TARGET_FPS = 60
2022-04-28 13:47:31 -05:00
local FRAME_DURATION = 1.0 / TARGET_FPS
2021-05-23 13:07:07 -05:00
2023-07-01 22:30:36 -05:00
-- custom run function; optimizes game by syncing draw/update calls
2021-05-23 13:07:07 -05:00
function love . run ( )
if love.load then love.load ( love.arg . parseGameArguments ( arg ) , arg ) end
if love.timer then love.timer . step ( ) end
local dt = 0
local last_time = love.timer . getTime ( )
2022-04-28 13:47:31 -05:00
local time_accumulator = 0.0
2021-05-23 13:07:07 -05:00
return function ( )
if love.event then
love.event . pump ( )
for name , a , b , c , d , e , f in love.event . poll ( ) do
if name == " quit " then
if not love.quit or not love.quit ( ) then
return a or 0
end
end
love.handlers [ name ] ( a , b , c , d , e , f )
end
end
if love.timer then
2021-05-23 13:57:04 -05:00
processBGMFadeout ( love.timer . step ( ) )
end
2021-05-23 13:07:07 -05:00
if scene and scene.update and love.timer then
2021-05-23 13:57:04 -05:00
scene : update ( )
2022-04-28 13:47:31 -05:00
if time_accumulator < FRAME_DURATION then
2021-05-23 13:57:04 -05:00
if love.graphics and love.graphics . isActive ( ) and love.draw then
love.graphics . origin ( )
love.graphics . clear ( love.graphics . getBackgroundColor ( ) )
love.draw ( )
love.graphics . present ( )
end
2022-04-28 13:47:31 -05:00
-- request 1ms delays first but stop short of overshooting, then do "0ms" delays without overshooting (0ms requests generally do a delay of some nonzero amount of time, but maybe less than 1ms)
for milliseconds = 0.001 , 0.000 , - 0.001 do
local max_delay = 0.0
while max_delay < FRAME_DURATION do
local delay_start_time = love.timer . getTime ( )
if delay_start_time - last_time < FRAME_DURATION - max_delay then
love.timer . sleep ( milliseconds )
local last_delay = love.timer . getTime ( ) - delay_start_time
if last_delay > max_delay then
max_delay = last_delay
end
else
break
end
end
end
while love.timer . getTime ( ) - last_time < FRAME_DURATION do
-- busy loop, do nothing here until delay is finished; delays above stop short of finishing, so this part can finish it off precisely
2021-05-23 13:57:04 -05:00
end
end
2022-04-28 13:47:31 -05:00
local finish_delay_time = love.timer . getTime ( )
local real_frame_duration = finish_delay_time - last_time
time_accumulator = time_accumulator + real_frame_duration - FRAME_DURATION
last_time = finish_delay_time
2021-05-23 13:57:04 -05:00
end
2021-05-23 13:07:07 -05:00
end
2021-05-23 13:57:04 -05:00
end