Compare commits

...

35 Commits

Author SHA1 Message Date
Ishaan Bhardwaj
83e197b5d6 Slight RPC change to the selection menus 2021-12-09 21:51:41 -05:00
Ishaan Bhardwaj
1c0b73987d Rearrange sliders on the game settings menu 2021-12-09 21:48:39 -05:00
Ishaan Bhardwaj
afe6a43dab Rearrange game settings, add toggle for replay saving 2021-12-09 21:44:18 -05:00
Ishaan Bhardwaj
47a5a53e23 Fixed sorting replays in the replay select
In addition, minor change to the default text that shows up when the game is paused
2021-12-09 18:18:23 -05:00
Ishaan Bhardwaj
b9ae08051a Fixed BGM not resetting after a replay 2021-12-08 21:56:31 -05:00
Ishaan Bhardwaj
d7f4aa2007 Reverted a mode select change 2021-12-08 21:56:17 -05:00
Ishaan Bhardwaj
ca85107063 Replace replay select title graphic with text 2021-12-08 21:37:34 -05:00
Ishaan Bhardwaj
fdcec19d56 Bump version to v0.3.1 + re-add snow easter egg 2021-12-08 21:30:09 -05:00
Ishaan Bhardwaj
89c7205347 Replay system v3 + love.math.random migration 2021-12-08 21:23:00 -05:00
Ishaan Bhardwaj
9b41e56135 Replay system v2 2021-12-08 20:19:46 -05:00
710f658540 Merge pull request #48 from BoatsandJoes/replays
Added replays
2021-12-07 22:39:08 -05:00
BoatsandJoes
332e3869de Replay menu no longer crashes if level or timer is nil. 2021-12-06 22:38:07 -06:00
BoatsandJoes
febd1de0ef Replays are now fully functional. 2021-12-05 22:17:44 -06:00
BoatsandJoes
81ab7cd4de Replays now replay inputs properly, and replay list has fast scroll. 2021-12-05 21:16:13 -06:00
BoatsandJoes
a5750e4959 Replays list is now sorted, and replays are smaller. 2021-12-05 15:41:51 -06:00
BoatsandJoes
59c7834c9a Fixed replay deserialization. 2021-12-05 00:18:19 -06:00
BoatsandJoes
71ada76a00 Started work on replay select menu. 2021-12-04 23:37:51 -06:00
BoatsandJoes
6c4551ebef Added replay saving. 2021-12-04 20:35:15 -06:00
9fc7e4b1eb Merge pull request #47 from BoatsandJoes/game-over-animation
Game over fadeout easing function now accelerates
2021-12-02 19:27:32 -05:00
BoatsandJoes
9e59c158b2 Line clear easing is now quadratic for all line clear delays. 2021-12-02 16:22:29 -06:00
BoatsandJoes
e464307625 Game over animation is longer, and reveals stack once it's over. 2021-12-02 15:49:27 -06:00
BoatsandJoes
888312c578 Game over fadeout easing function now accelerates. 2021-12-02 13:42:51 -06:00
a838294435 Merge pull request #46 from BoatsandJoes/line-clear-animation
Added new default line clear animations using easing functions.
2021-12-02 13:57:19 -05:00
BoatsandJoes
049806d9e2 Added a new default line clear animation using easing functions. 2021-12-02 10:59:45 -06:00
Ishaan Bhardwaj
c1693524d7 Credits change 2021-11-30 20:08:52 -05:00
Ishaan Bhardwaj
a063f10d33 Merge branch 'master' of https://github.com/MillaBasset/cambridge 2021-11-23 22:56:19 -05:00
Ishaan Bhardwaj
6e0b5e27c1 Rearranged the spawn SE actions 2021-11-23 22:56:11 -05:00
Joe Zeng
18e0e02c76 Removed a stupid exception to the mixing-tabs-and-spaces rule.
I apologize that it took me 2 years to think of this workaround.
2021-11-09 14:48:05 -05:00
Ishaan Bhardwaj
9381091110 Updated 3694 -> 3701 in other places 2021-11-03 17:34:34 -04:00
deb69fe28d Merge pull request #43 from Kirby703/patch-5
tap roll 3694 -> 3701 frames
2021-11-03 17:32:43 -04:00
Kirby703
3085b765e5 fix roll 3694 -> 3701 frames 2021-11-03 17:31:04 -04:00
hailey
412405c1a1 celebrate!!! 2021-10-22 15:04:28 +10:00
Ishaan Bhardwaj
7495c4ad04 Revert "Separate in-game bindings from menu bindings"
This reverts commit 0fce4b632f.
This commit caused issue #41. Will resolve ASAP.
2021-10-21 20:52:42 -04:00
Ishaan Bhardwaj
0fce4b632f Separate in-game bindings from menu bindings
Also preemptive version bump
2021-10-19 18:35:32 -04:00
Ishaan Bhardwaj
aa56248e34 Add an FPS counter 2021-10-18 23:27:54 -04:00
25 changed files with 529 additions and 67 deletions

View File

@@ -57,13 +57,13 @@ Coding conventions
Use tabs to indent, spaces to align. Use tabs to indent, spaces to align.
* Specifically, spaces should not appear at the beginning of a line, and tabs should not appear _except_ at the beginning of a line. * Specifically, spaces should not appear at the beginning of a line, and tabs should not appear _except_ at the beginning of a line.
* The sole exception is in a multiline `if` statement; the initial `if` should have four spaces before it to align it with an `elseif` on the next line. For example: * If you're aligning multiline if-statements, the initial "if", "elseif" or "else" should be flush left with the indentation level, with spaces padding the gap to the next word as necessary. For example:
```lua ```lua
---- 4 spaces if self.level < 900 then return 12
if self.level < 900 then return 12 elseif self.level < 1200 then return 8
elseif self.level < 1200 then return 8 else return 6
else return 6 end end
``` ```
Comments at the end of lines of code must be one line long. Multi-line comments must appear in their own block. Comments at the end of lines of code must be one line long. Multi-line comments must appear in their own block.

View File

@@ -102,7 +102,6 @@ Other Notable Games
- [Tetra Online](https://github.com/Juan-Cartes/Tetra-Offline) by Mine - [Tetra Online](https://github.com/Juan-Cartes/Tetra-Offline) by Mine
- [Techmino](https://discord.gg/6Yuww44tq8) by MrZ - [Techmino](https://discord.gg/6Yuww44tq8) by MrZ
- [Example Block Game](https://github.com/oshisaure/example-block-game) by Oshisaure - [Example Block Game](https://github.com/oshisaure/example-block-game) by Oshisaure
- [TETR.IO](https://tetr.io) by osk
- [Master of Blocks](https://discord.gg/72FZ49mjWh) by Phoenix Flare - [Master of Blocks](https://discord.gg/72FZ49mjWh) by Phoenix Flare
- [Spirit Drop](https://rayblastgames.com/spiritdrop.php) by RayRay26 - [Spirit Drop](https://rayblastgames.com/spiritdrop.php) by RayRay26
- [Puzzle Trial](https://kagamine-rin.itch.io/puzzle-trial) by Rin - [Puzzle Trial](https://kagamine-rin.itch.io/puzzle-trial) by Rin

View File

@@ -1 +1 @@
version = "v0.3" version = "v0.3.1"

View File

@@ -35,6 +35,8 @@ function love.load()
end end
function initModules() function initModules()
-- replays are not loaded here, but they are cleared
replays = {}
game_modes = {} game_modes = {}
mode_list = love.filesystem.getDirectoryItems("tetris/modes") mode_list = love.filesystem.getDirectoryItems("tetris/modes")
for i=1,#mode_list do for i=1,#mode_list do
@@ -78,7 +80,10 @@ function love.draw()
if config.gamesettings.display_gamemode == 1 or scene.title == "Title" then if config.gamesettings.display_gamemode == 1 or scene.title == "Title" then
love.graphics.setFont(font_3x5_2) love.graphics.setFont(font_3x5_2)
love.graphics.setColor(1, 1, 1, 1) love.graphics.setColor(1, 1, 1, 1)
love.graphics.printf(version, 0, 460, 635, "right") love.graphics.printf(
string.format("%.2f", 1 / love.timer.getAverageDelta()) ..
"fps - " .. version, 0, 460, 635, "right"
)
end end
love.graphics.pop() love.graphics.pop()

BIN
res/img/rpc/1year.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

View File

@@ -10,7 +10,9 @@ function Scene:onInputRelease() end
ExitScene = require "scene.exit" ExitScene = require "scene.exit"
GameScene = require "scene.game" GameScene = require "scene.game"
ReplayScene = require "scene.replay"
ModeSelectScene = require "scene.mode_select" ModeSelectScene = require "scene.mode_select"
ReplaySelectScene = require "scene.replay_select"
KeyConfigScene = require "scene.key_config" KeyConfigScene = require "scene.key_config"
StickConfigScene = require "scene.stick_config" StickConfigScene = require "scene.stick_config"
InputConfigScene = require "scene.input_config" InputConfigScene = require "scene.input_config"

View File

@@ -40,14 +40,14 @@ function CreditsScene:render()
love.graphics.setFont(font_3x5_4) love.graphics.setFont(font_3x5_4)
love.graphics.print("Cambridge Credits", 320, 500 - offset) love.graphics.print("Cambridge Credits", 320, 500 - offset)
love.graphics.print("THANK YOU\nFOR PLAYING!", 320, math.max(2050 - offset, 240)) love.graphics.print("THANK YOU\nFOR PLAYING!", 320, math.max(2030 - offset, 240))
love.graphics.setFont(font_3x5_3) love.graphics.setFont(font_3x5_3)
love.graphics.print("Game Developers", 320, 550 - offset) love.graphics.print("Game Developers", 320, 550 - offset)
love.graphics.print("Project Heads", 320, 640 - offset) love.graphics.print("Project Heads", 320, 640 - offset)
love.graphics.print("Notable Game Developers", 320, 750 - offset) love.graphics.print("Notable Game Developers", 320, 750 - offset)
love.graphics.print("Special Thanks", 320, 1020 - offset) love.graphics.print("Special Thanks", 320, 1000 - offset)
love.graphics.print("- Milla", 320, math.max(2130 - offset, 320)) love.graphics.print("- Milla", 320, math.max(2110 - offset, 320))
love.graphics.setFont(font_3x5_2) love.graphics.setFont(font_3x5_2)
love.graphics.print("Oshisaure\nJoe Zeng", 320, 590 - offset) love.graphics.print("Oshisaure\nJoe Zeng", 320, 590 - offset)
@@ -55,7 +55,7 @@ function CreditsScene:render()
love.graphics.print( love.graphics.print(
"2Tie - TGMsim\nAxel Fox - Multimino\nDr Ocelot - Tetra Legends\n" .. "2Tie - TGMsim\nAxel Fox - Multimino\nDr Ocelot - Tetra Legends\n" ..
"Electra - ZTrix\nFelicity/nightmareci/kdex - Shiromino\n" .. "Electra - ZTrix\nFelicity/nightmareci/kdex - Shiromino\n" ..
"Mine - Tetra Online\nMrZ - Techmino\nosk - TETR.IO\n" .. "Mine - Tetra Online\nMrZ - Techmino\n" ..
"Phoenix Flare - Master of Blocks\nRayRay26 - Spirit Drop\n" .. "Phoenix Flare - Master of Blocks\nRayRay26 - Spirit Drop\n" ..
"Rin - Puzzle Trial\nsinefuse - stackfuse", "Rin - Puzzle Trial\nsinefuse - stackfuse",
320, 790 - offset 320, 790 - offset
@@ -75,7 +75,7 @@ function CreditsScene:render()
"Tetra Legends Discord\nTetra Online Discord\nMultimino Discord\n" .. "Tetra Legends Discord\nTetra Online Discord\nMultimino Discord\n" ..
"Hard Drop Discord\nRusty's Systemspace\nCambridge Discord\n" .. "Hard Drop Discord\nRusty's Systemspace\nCambridge Discord\n" ..
"And to you, the player!", "And to you, the player!",
320, 1060 - offset 320, 1040 - offset
) )
end end

View File

@@ -40,6 +40,8 @@ function GameScene:update()
self.game:update(inputs, self.ruleset) self.game:update(inputs, self.ruleset)
self.game.grid:update() self.game.grid:update()
DiscordRPC:update({ DiscordRPC:update({
details = self.game.rpc_details,
state = self.game.name,
largeImageKey = "ingame-"..self.game:getBackground().."00" largeImageKey = "ingame-"..self.game:getBackground().."00"
}) })
end end

View File

@@ -13,11 +13,12 @@ ConfigScene.options = {
{"world_reverse", "A Button Rotation", false, {"Left", "Auto", "Right"}}, {"world_reverse", "A Button Rotation", false, {"Left", "Auto", "Right"}},
{"spawn_positions", "Spawn Positions", false, {"Per ruleset", "In field", "Out of field"}}, {"spawn_positions", "Spawn Positions", false, {"Per ruleset", "In field", "Out of field"}},
{"display_gamemode", "Debug Info", false, {"On", "Off"}}, {"display_gamemode", "Debug Info", false, {"On", "Off"}},
{"das_last_key", "DAS Last Key", false, {"Off", "On"}}, {"save_replay", "Save Replays", false, {"On", "Off"}},
{"smooth_movement", "Smooth Piece Drop", false, {"On", "Off"}}, {"smooth_movement", "Smooth Piece Drop", false, {"On", "Off"}},
{"synchroes_allowed", "Synchroes", false, {"Per ruleset", "On", "Off"}},
{"diagonal_input", "Diagonal Input", false, {"On", "Off"}}, {"diagonal_input", "Diagonal Input", false, {"On", "Off"}},
{"das_last_key", "DAS Last Key", false, {"Off", "On"}},
{"buffer_lock", "Buffer Drop Type", false, {"Off", "Hold", "Tap"}}, {"buffer_lock", "Buffer Drop Type", false, {"Off", "Hold", "Tap"}},
{"synchroes_allowed", "Synchroes", false, {"Per ruleset", "On", "Off"}},
{"sfx_volume", "SFX", true, "sfxSlider"}, {"sfx_volume", "SFX", true, "sfxSlider"},
{"bgm_volume", "BGM", true, "bgmSlider"}, {"bgm_volume", "BGM", true, "bgmSlider"},
} }
@@ -33,8 +34,8 @@ function ConfigScene:new()
state = "Changing game settings", state = "Changing game settings",
}) })
self.sfxSlider = newSlider(165, 400, 225, config.sfx_volume * 100, 0, 100, function(v) config.sfx_volume = v / 100 end, {width=20, knob="circle", track="roundrect"}) self.sfxSlider = newSlider(165, 420, 225, config.sfx_volume * 100, 0, 100, function(v) config.sfx_volume = v / 100 end, {width=20, knob="circle", track="roundrect"})
self.bgmSlider = newSlider(465, 400, 225, config.bgm_volume * 100, 0, 100, function(v) config.bgm_volume = v / 100 end, {width=20, knob="circle", track="roundrect"}) self.bgmSlider = newSlider(465, 420, 225, config.bgm_volume * 100, 0, 100, function(v) config.bgm_volume = v / 100 end, {width=20, knob="circle", track="roundrect"})
end end
function ConfigScene:update() function ConfigScene:update()
@@ -58,7 +59,7 @@ function ConfigScene:render()
if not ConfigScene.options[self.highlight][3] then if not ConfigScene.options[self.highlight][3] then
love.graphics.rectangle("fill", 25, 98 + self.highlight * 20, 170, 22) love.graphics.rectangle("fill", 25, 98 + self.highlight * 20, 170, 22)
else else
love.graphics.rectangle("fill", 65 + (1+self.highlight-#self.options) * 300, 342, 215, 33) love.graphics.rectangle("fill", 65 + (1+self.highlight-#self.options) * 300, 362, 215, 33)
end end
love.graphics.setFont(font_3x5_2) love.graphics.setFont(font_3x5_2)
@@ -75,8 +76,8 @@ function ConfigScene:render()
love.graphics.setColor(1, 1, 1, 1) love.graphics.setColor(1, 1, 1, 1)
love.graphics.setFont(font_3x5_3) love.graphics.setFont(font_3x5_3)
love.graphics.print("SFX Volume: " .. math.floor(self.sfxSlider:getValue()) .. "%", 75, 345) love.graphics.print("SFX Volume: " .. math.floor(self.sfxSlider:getValue()) .. "%", 75, 365)
love.graphics.print("BGM Volume: " .. math.floor(self.bgmSlider:getValue()) .. "%", 375, 345) love.graphics.print("BGM Volume: " .. math.floor(self.bgmSlider:getValue()) .. "%", 375, 365)
love.graphics.setColor(1, 1, 1, 0.75) love.graphics.setColor(1, 1, 1, 0.75)
self.sfxSlider:draw() self.sfxSlider:draw()

View File

@@ -187,4 +187,4 @@ function ModeSelectScene:changeRuleset(rel)
self.menu_state.ruleset = Mod1(self.menu_state.ruleset + rel, len) self.menu_state.ruleset = Mod1(self.menu_state.ruleset + rel, len)
end end
return ModeSelectScene return ModeSelectScene

91
scene/replay.lua Normal file
View File

@@ -0,0 +1,91 @@
local Sequence = require 'tetris.randomizers.fixed_sequence'
local ReplayScene = Scene:extend()
ReplayScene.title = "Replay"
function ReplayScene:new(replay, game_mode, ruleset, inputs)
config.gamesettings = replay["gamesettings"]
love.math.setRandomSeed(replay["random_low"], replay["random_high"])
love.math.setRandomState(replay["random_state"])
self.retry_replay = replay
self.retry_mode = game_mode
self.retry_ruleset = ruleset
self.secret_inputs = inputs
self.game = game_mode(self.secret_inputs)
self.ruleset = ruleset(self.game)
self.game:initialize(self.ruleset, true)
self.inputs = {
left=false,
right=false,
up=false,
down=false,
rotate_left=false,
rotate_left2=false,
rotate_right=false,
rotate_right2=false,
rotate_180=false,
hold=false,
}
self.paused = false
self.replay = deepcopy(replay)
self.replay_index = 1
DiscordRPC:update({
details = "Viewing a replay",
state = self.game.name,
largeImageKey = "ingame-"..self.game:getBackground().."00"
})
end
function ReplayScene:update()
if love.window.hasFocus() and not self.paused then
self.inputs = self.replay["inputs"][self.replay_index]["inputs"]
self.replay["inputs"][self.replay_index]["frames"] = self.replay["inputs"][self.replay_index]["frames"] - 1
if self.replay["inputs"][self.replay_index]["frames"] == 0 and self.replay_index < table.getn(self.replay["inputs"]) then
self.replay_index = self.replay_index + 1
end
local input_copy = {}
for input, value in pairs(self.inputs) do
input_copy[input] = value
end
self.game:update(input_copy, self.ruleset)
self.game.grid:update()
DiscordRPC:update({
details = "Viewing a replay",
state = self.game.name,
largeImageKey = "ingame-"..self.game:getBackground().."00"
})
end
end
function ReplayScene:render()
self.game:draw(self.paused)
love.graphics.setColor(1, 1, 1, 1)
love.graphics.setFont(font_3x5_3)
love.graphics.printf("REPLAY", 0, 0, 635, "right")
end
function ReplayScene:onInputPress(e)
if (
e.input == "menu_back" or
e.input == "menu_decide" or
e.input == "retry"
) then
self.game:onExit()
loadSave()
love.math.setRandomSeed(os.time())
scene = (
(e.input == "retry") and
ReplayScene(
self.retry_replay, self.retry_mode,
self.retry_ruleset, self.secret_inputs
) or ReplaySelectScene()
)
elseif e.input == "pause" and not (self.game.game_over or self.game.completed) then
self.paused = not self.paused
if self.paused then pauseBGM()
else resumeBGM() end
end
end
return ReplayScene

223
scene/replay_select.lua Normal file
View File

@@ -0,0 +1,223 @@
local ReplaySelectScene = Scene:extend()
ReplaySelectScene.title = "Replays"
local binser = require 'libs.binser'
current_replay = 1
function ReplaySelectScene:new()
-- reload custom modules
initModules()
-- load replays
replays = {}
replay_file_list = love.filesystem.getDirectoryItems("replays")
for i=1,#replay_file_list do
local data = love.filesystem.read("replays/"..replay_file_list[i])
local new_replay = binser.deserialize(data)[1]
replays[#replays + 1] = new_replay
end
table.sort(replays, function(a, b)
return a["timestamp"] > b["timestamp"]
end)
self.display_error = false
if table.getn(replays) == 0 then
self.display_warning = true
current_replay = 1
else
self.display_warning = false
if current_replay > table.getn(replays) then
current_replay = 1
end
end
self.menu_state = {
replay = current_replay,
}
self.das = 0
DiscordRPC:update({
details = "In menus",
state = "Choosing a replay",
largeImageKey = "ingame-000"
})
end
function ReplaySelectScene:update()
switchBGM(nil) -- experimental
if self.das_up or self.das_down or self.das_left or self.das_right then
self.das = self.das + 1
else
self.das = 0
end
if self.das >= 15 then
local change = 0
if self.das_up then
change = -1
elseif self.das_down then
change = 1
elseif self.das_left then
change = -9
elseif self.das_right then
change = 9
end
self:changeOption(change)
self.das = self.das - 4
end
DiscordRPC:update({
details = "In menus",
state = "Choosing a replay",
largeImageKey = "ingame-000"
})
end
function ReplaySelectScene:render()
love.graphics.draw(
backgrounds[0],
0, 0, 0,
0.5, 0.5
)
-- Same graphic as mode select
--love.graphics.draw(misc_graphics["select_mode"], 20, 40)
love.graphics.setFont(font_3x5_4)
love.graphics.print("SELECT REPLAY", 20, 35)
if self.display_warning then
love.graphics.setFont(font_3x5_3)
love.graphics.printf(
"You have no replays.",
80, 200, 480, "center"
)
love.graphics.setFont(font_3x5_2)
love.graphics.printf(
"Come back to this menu after playing some games. " ..
"Press any button to return to the main menu.",
80, 250, 480, "center"
)
return
elseif self.display_error then
love.graphics.setFont(font_3x5_3)
love.graphics.printf(
"You are missing this mode or ruleset.",
80, 200, 480, "center"
)
love.graphics.setFont(font_3x5_2)
love.graphics.printf(
"Come back after getting the proper mode or ruleset. " ..
"Press any button to return to the main menu.",
80, 250, 480, "center"
)
return
end
love.graphics.setColor(1, 1, 1, 0.5)
love.graphics.rectangle("fill", 3, 258, 634, 22)
love.graphics.setColor(1, 1, 1, 1)
love.graphics.setFont(font_3x5_2)
for idx, replay in ipairs(replays) do
if(idx >= self.menu_state.replay-9 and idx <= self.menu_state.replay+9) then
local display_string = os.date("%c", replay["timestamp"]).." - "..replay["mode"].." - "..replay["ruleset"]
if replay["level"] ~= nil then
display_string = display_string.." - Level: "..replay["level"]
end
if replay["timer"] ~= nil then
display_string = display_string.." - Time: "..formatTime(replay["timer"])
end
if #display_string > 75 then
display_string = display_string:sub(1, 75) .. "..."
end
love.graphics.printf(display_string, 6, (260 - 20*(self.menu_state.replay)) + 20 * idx, 640, "left")
end
end
end
function ReplaySelectScene:onInputPress(e)
if (self.display_warning or self.display_error) and e.input then
scene = TitleScene()
elseif e.type == "wheel" then
if e.y ~= 0 then
self:changeOption(-e.y)
end
elseif e.input == "menu_decide" or e.scancode == "return" then
current_replay = self.menu_state.replay
-- Same as mode decide
playSE("mode_decide")
-- Get game mode and ruleset
local mode
local rules
for key, value in pairs(game_modes) do
if value.name == replays[self.menu_state.replay]["mode"] then
mode = value
break
end
end
for key, value in pairs(rulesets) do
if value.name == replays[self.menu_state.replay]["ruleset"] then
rules = value
break
end
end
if mode == nil or rules == nil then
self.display_error = true
return
end
-- TODO compare replay versions to current versions for Cambridge, ruleset, and mode
scene = ReplayScene(
replays[self.menu_state.replay],
mode,
rules,
replays[self.menu_state.replay]["secret_inputs"]
)
elseif e.input == "up" or e.scancode == "up" then
self:changeOption(-1)
self.das_up = true
self.das_down = nil
self.das_left = nil
self.das_right = nil
elseif e.input == "down" or e.scancode == "down" then
self:changeOption(1)
self.das_down = true
self.das_up = nil
self.das_left = nil
self.das_right = nil
elseif e.input == "left" or e.scancode == "left" then
self:changeOption(-9)
self.das_left = true
self.das_right = nil
self.das_up = nil
self.das_down = nil
elseif e.input == "right" or e.scancode == "right" then
self:changeOption(9)
self.das_right = true
self.das_left = nil
self.das_up = nil
self.das_down = nil
elseif e.input == "menu_back" or e.scancode == "delete" or e.scancode == "backspace" then
scene = TitleScene()
end
end
function ReplaySelectScene:onInputRelease(e)
if e.input == "up" or e.scancode == "up" then
self.das_up = nil
elseif e.input == "down" or e.scancode == "down" then
self.das_down = nil
elseif e.input == "right" or e.scancode == "right" then
self.das_right = nil
elseif e.input == "left" or e.scancode == "left" then
self.das_left = nil
end
end
function ReplaySelectScene:changeOption(rel)
local len = table.getn(replays)
self.menu_state.replay = Mod1(self.menu_state.replay + rel, len)
playSE("cursor")
end
return ReplaySelectScene

View File

@@ -20,7 +20,7 @@ function SettingsScene:new()
self.menu_state = 1 self.menu_state = 1
DiscordRPC:update({ DiscordRPC:update({
details = "In settings", details = "In settings",
state = settingsidle[math.random(#settingsidle)], state = settingsidle[love.math.random(#settingsidle)],
largeImageKey = "settings", largeImageKey = "settings",
}) })
end end

View File

@@ -5,6 +5,7 @@ TitleScene.restart_message = false
local main_menu_screens = { local main_menu_screens = {
ModeSelectScene, ModeSelectScene,
ReplaySelectScene,
SettingsScene, SettingsScene,
CreditsScene, CreditsScene,
ExitScene, ExitScene,
@@ -43,7 +44,7 @@ function TitleScene:new()
self.text_flag = false self.text_flag = false
DiscordRPC:update({ DiscordRPC:update({
details = "In menus", details = "In menus",
state = mainmenuidle[math.random(#mainmenuidle)], state = mainmenuidle[love.math.random(#mainmenuidle)],
largeImageKey = "icon2", largeImageKey = "icon2",
largeImageText = version largeImageText = version
}) })
@@ -62,24 +63,19 @@ end
function TitleScene:render() function TitleScene:render()
love.graphics.setFont(font_3x5_4) love.graphics.setFont(font_3x5_4)
love.graphics.setColor(1, 1, 1, 1 - self.snow_bg_opacity) love.graphics.setColor(1, 1, 1, 1 - self.snow_bg_opacity)
love.graphics.draw(
backgrounds["title"], -- title_night
0, 0, 0,
0.5, 0.5
)
--[[ --[[
love.graphics.draw(
backgrounds["title"],
0, 0, 0,
0.5, 0.5
)
]]
love.graphics.draw(
backgrounds["title_night"],
0, 0, 0,
0.5, 0.5
)
love.graphics.draw( love.graphics.draw(
misc_graphics["icon"], misc_graphics["icon"],
460, 170, 0, 460, 170, 0,
2, 2 2, 2
) )
love.graphics.printf("Thanks for 1 year!", 430, 280, 160, "center") ]]
--love.graphics.printf("Thanks for 1 year!", 430, 280, 160, "center")
love.graphics.setFont(font_3x5_2) love.graphics.setFont(font_3x5_2)
love.graphics.setColor(1, 1, 1, self.snow_bg_opacity) love.graphics.setColor(1, 1, 1, self.snow_bg_opacity)
@@ -125,8 +121,6 @@ function TitleScene:onInputPress(e)
playSE("cursor") playSE("cursor")
elseif e.input == "menu_back" or e.scancode == "backspace" or e.scancode == "delete" then elseif e.input == "menu_back" or e.scancode == "backspace" or e.scancode == "delete" then
love.event.quit() love.event.quit()
-- no winter easter egg for now
--[[
else else
self.text = self.text .. (e.scancode or "") self.text = self.text .. (e.scancode or "")
if self.text == "ffffff" then if self.text == "ffffff" then
@@ -135,7 +129,6 @@ function TitleScene:onInputPress(e)
largeImageKey = "snow" largeImageKey = "snow"
}) })
end end
]]
end end
end end

View File

@@ -182,6 +182,19 @@ function Grid:clearSpecificRow(row)
end end
end end
function Grid:clearBlock(x, y)
self.grid[x+1][y+1] = empty
end
function Grid:clearBottomRows(num)
local old_isRowFull = self.isRowFull
self.isRowFull = function(self, row)
return row >= self.height + 1 - num
end
self:clearClearedRows()
self.isRowFull = old_isRowFull
end
function Grid:applyPiece(piece) function Grid:applyPiece(piece)
if piece.big then if piece.big then
self:applyBigPiece(piece) self:applyBigPiece(piece)

View File

@@ -7,6 +7,7 @@ local playedGoSE = false
local Grid = require 'tetris.components.grid' local Grid = require 'tetris.components.grid'
local Randomizer = require 'tetris.randomizers.randomizer' local Randomizer = require 'tetris.randomizers.randomizer'
local BagRandomizer = require 'tetris.randomizers.bag' local BagRandomizer = require 'tetris.randomizers.bag'
local binser = require 'libs.binser'
local GameMode = Object:extend() local GameMode = Object:extend()
@@ -16,6 +17,12 @@ GameMode.tagline = ""
GameMode.rollOpacityFunction = function(age) return 0 end GameMode.rollOpacityFunction = function(age) return 0 end
function GameMode:new(secret_inputs) function GameMode:new(secret_inputs)
self.replay_inputs = {}
self.random_low, self.random_high = love.math.getRandomSeed()
self.random_state = love.math.getRandomState()
self.secret_inputs = secret_inputs
self.save_replay = config.gamesettings.save_replay == 1
self.grid = Grid(10, 24) self.grid = Grid(10, 24)
self.randomizer = Randomizer() self.randomizer = Randomizer()
self.piece = nil self.piece = nil
@@ -97,7 +104,7 @@ function GameMode:getSkin()
return "2tie" return "2tie"
end end
function GameMode:initialize(ruleset) function GameMode:initialize(ruleset, replay)
-- generate next queue -- generate next queue
self.used_randomizer = ( self.used_randomizer = (
table.equalvalues( table.equalvalues(
@@ -107,15 +114,72 @@ function GameMode:initialize(ruleset)
self.randomizer or BagRandomizer(table.keys(ruleset.colourscheme)) self.randomizer or BagRandomizer(table.keys(ruleset.colourscheme))
) )
self.ruleset = ruleset self.ruleset = ruleset
self.save_replay = not replay
for i = 1, math.max(self.next_queue_length, 1) do for i = 1, math.max(self.next_queue_length, 1) do
table.insert(self.next_queue, self:getNextPiece(ruleset)) table.insert(self.next_queue, self:getNextPiece(ruleset))
end end
self.lock_on_soft_drop = ({ruleset.softdrop_lock, self.instant_soft_drop, false, true })[config.gamesettings.manlock] self.lock_on_soft_drop = ({ruleset.softdrop_lock, self.instant_soft_drop, false, true})[config.gamesettings.manlock]
self.lock_on_hard_drop = ({ruleset.harddrop_lock, self.instant_hard_drop, true, false})[config.gamesettings.manlock] self.lock_on_hard_drop = ({ruleset.harddrop_lock, self.instant_hard_drop, true, false})[config.gamesettings.manlock]
end end
function GameMode:saveReplay()
-- Save replay.
local replay = {}
replay["inputs"] = self.replay_inputs
replay["random_low"] = self.random_low
replay["random_high"] = self.random_high
replay["random_state"] = self.random_state
replay["mode"] = self.name
replay["ruleset"] = self.ruleset.name
replay["timer"] = self.frames
replay["score"] = self.score
replay["level"] = self.level
replay["lines"] = self.lines
replay["gamesettings"] = config.gamesettings
replay["secret_inputs"] = self.secret_inputs
replay["timestamp"] = os.time()
if love.filesystem.getInfo("replays") == nil then
love.filesystem.createDirectory("replays")
end
local replay_files = love.filesystem.getDirectoryItems("replays")
-- Select replay filename that doesn't collide with an existing one
local replay_number = 0
local collision = true
while collision do
collision = false
replay_number = replay_number + 1
for key, file in pairs(replay_files) do
if file == replay_number..".crp" then
collision = true
break
end
end
end
love.filesystem.write("replays/"..replay_number..".crp", binser.serialize(replay))
end
function GameMode:addReplayInput(inputs)
-- check if inputs have changed since last frame
if not equals(self.prev_inputs, inputs) then
-- insert new inputs into replay inputs table
local new_inputs = {}
new_inputs["inputs"] = {}
new_inputs["frames"] = 1
for key, value in pairs(inputs) do
new_inputs["inputs"][key] = value
end
self.replay_inputs[#self.replay_inputs + 1] = new_inputs
else
-- add 1 to input frame counter
self.replay_inputs[#self.replay_inputs]["frames"] = self.replay_inputs[#self.replay_inputs]["frames"] + 1
end
end
function GameMode:update(inputs, ruleset) function GameMode:update(inputs, ruleset)
if self.game_over or self.completed then if self.game_over or self.completed then
if self.save_replay and self.game_over_frames == 0 then
self:saveReplay()
end
self.game_over_frames = self.game_over_frames + 1 self.game_over_frames = self.game_over_frames + 1
return return
end end
@@ -129,6 +193,8 @@ function GameMode:update(inputs, ruleset)
end end
end end
if self.save_replay then self:addReplayInput(inputs) end
-- advance one frame -- advance one frame
if self:advanceOneFrame(inputs, ruleset) == false then return end if self:advanceOneFrame(inputs, ruleset) == false then return end
@@ -334,7 +400,16 @@ end
function GameMode:onGameOver() function GameMode:onGameOver()
switchBGM(nil) switchBGM(nil)
love.graphics.setColor(0, 0, 0, 1 - 2 ^ (-self.game_over_frames / 30)) local alpha = 0
local animation_length = 120
if self.game_over_frames < animation_length then
-- Show field for a bit, then fade out.
alpha = math.pow(2048, self.game_over_frames/animation_length - 1)
elseif self.game_over_frames < 2 * animation_length then
-- Keep field hidden for a short time, then pop it back in (for screenshots).
alpha = 1
end
love.graphics.setColor(0, 0, 0, alpha)
love.graphics.rectangle( love.graphics.rectangle(
"fill", 64, 80, "fill", 64, 80,
16 * self.grid.width, 16 * (self.grid.height - 4) 16 * self.grid.width, 16 * (self.grid.height - 4)
@@ -530,6 +605,8 @@ function GameMode:onEnterOrHold(inputs, ruleset)
if not self.grid:canPlacePiece(self.piece) then if not self.grid:canPlacePiece(self.piece) then
self.game_over = true self.game_over = true
return return
elseif self.piece:isDropBlocked(self.grid) then
playSE("bottom")
end end
ruleset:dropPiece( ruleset:dropPiece(
inputs, self.piece, self.grid, self:getGravity(), inputs, self.piece, self.grid, self:getGravity(),
@@ -578,10 +655,6 @@ function GameMode:initializeNextPiece(
self.piece_soft_locked = false self.piece_soft_locked = false
self.buffer_hard_drop = false self.buffer_hard_drop = false
self.buffer_soft_drop = false self.buffer_soft_drop = false
if self.piece:isDropBlocked(self.grid) and
self.grid:canPlacePiece(self.piece) then
playSE("bottom")
end
if generate_next_piece == nil then if generate_next_piece == nil then
table.remove(self.next_queue, 1) table.remove(self.next_queue, 1)
table.insert(self.next_queue, self:getNextPiece(ruleset)) table.insert(self.next_queue, self:getNextPiece(ruleset))
@@ -600,11 +673,18 @@ function GameMode:getHighScoreData()
end end
function GameMode:animation(x, y, skin, colour) function GameMode:animation(x, y, skin, colour)
-- Animation progress where 0 = start and 1 = end
local progress = 1
if self.last_lcd ~= 0 then
progress = (self.last_lcd - self.lcd) / self.last_lcd
end
-- Convert progress through the animation into an alpha value, with easing
local alpha = 1 - progress ^ 2
return { return {
1, 1, 1, 1, 1, 1,
-0.25 + 1.25 * (self.lcd / self.last_lcd), alpha,
skin, colour, skin, colour,
48 + x * 16, y * 16 48 + x * 16, y * 16
} }
end end
@@ -616,8 +696,56 @@ function GameMode:drawLineClearAnimation()
-- animation function -- animation function
-- params: block x, y, skin, colour -- params: block x, y, skin, colour
-- returns: table with RGBA, skin, colour, x, y -- returns: table with RGBA, skin, colour, x, y
-- Fadeout (default) -- Quadratic Fadeout (default)
--[[
function animation(x, y, skin, colour)
-- Animation progress where 0 = start and 1 = end
local progress = 1
if self.last_lcd ~= 0 then
progress = (self.last_lcd - self.lcd) / self.last_lcd
end
-- Convert progress through the animation into an alpha value, with easing
local alpha = 1 - progress ^ 2
return {
1, 1, 1,
alpha,
skin, colour,
48 + x * 16, y * 16
}
end
--]]
-- Flashy Fadeout
--[[
function animation(x, y, skin, colour)
-- Animation progress where 0 = start and 1 = end
local progress = 1
if self.last_lcd ~= 0 then
progress = (self.last_lcd - self.lcd) / self.last_lcd
end
-- Change this number to change "bounciness"
local bounce = 13
-- Convert progress through the animation into an alpha value
local alpha = 0
-- Cutoff is arbitrary: corresponds to level 500 in Marathon A2
if self.last_lcd > 25 then
-- Goes up and down: looks better when animation is long
alpha = 1 - (bounce * progress^3 - 1.5 * bounce * progress^2 + (0.5 * bounce + 1) * progress)
else
-- Always decreasing: looks better when animation is short
alpha = 1 - progress * progress
end
return {
1, 1, 1,
alpha,
skin, colour,
48 + x * 16, y * 16
}
end
--]]
-- Fadeout
--[[ --[[
function animation(x, y, skin, colour) function animation(x, y, skin, colour)
return { return {
@@ -906,7 +1034,7 @@ function GameMode:drawCustom() end
function GameMode:drawIfPaused() function GameMode:drawIfPaused()
love.graphics.setFont(font_3x5_3) love.graphics.setFont(font_3x5_3)
love.graphics.printf("GAME PAUSED!", 64, 160, 160, "center") love.graphics.printf("PAUSED!", 64, 160, 160, "center")
end end
-- transforms specified in here will transform the whole screen -- transforms specified in here will transform the whole screen

View File

@@ -112,7 +112,7 @@ function MarathonA2Game:advanceOneFrame()
if self.clear then if self.clear then
self.roll_frames = self.roll_frames + 1 self.roll_frames = self.roll_frames + 1
if self.roll_frames < 0 then return false end if self.roll_frames < 0 then return false end
if self.roll_frames > 3694 then if self.roll_frames > 3701 then
self.completed = true self.completed = true
if self.grade == 32 then if self.grade == 32 then
self.grade = 33 self.grade = 33
@@ -365,10 +365,10 @@ function MarathonA2Game:drawScoringInfo()
love.graphics.setFont(font_3x5_3) love.graphics.setFont(font_3x5_3)
if self.clear then if self.clear then
if self:qualifiesForMRoll() then if self:qualifiesForMRoll() then
if self.lines >= 32 and self.roll_frames > 3694 then love.graphics.setColor(1, 0.5, 0, 1) if self.lines >= 32 and self.roll_frames > 3701 then love.graphics.setColor(1, 0.5, 0, 1)
else love.graphics.setColor(0, 1, 0, 1) end else love.graphics.setColor(0, 1, 0, 1) end
else else
if self.roll_frames > 3694 then love.graphics.setColor(1, 0.5, 0, 1) if self.roll_frames > 3701 then love.graphics.setColor(1, 0.5, 0, 1)
else love.graphics.setColor(0, 1, 0, 1) end else love.graphics.setColor(0, 1, 0, 1) end
end end
end end

View File

@@ -13,7 +13,7 @@ function BagRandomizer:generatePiece()
table.insert(self.bag, v) table.insert(self.bag, v)
end end
end end
local x = math.random(table.getn(self.bag)) local x = love.math.random(table.getn(self.bag))
return table.remove(self.bag, x) return table.remove(self.bag, x)
end end

View File

@@ -10,7 +10,7 @@ function Bag7Randomizer:generatePiece()
if next(self.bag) == nil then if next(self.bag) == nil then
self.bag = {"I", "J", "L", "O", "S", "T", "Z"} self.bag = {"I", "J", "L", "O", "S", "T", "Z"}
end end
local x = math.random(table.getn(self.bag)) local x = love.math.random(table.getn(self.bag))
return table.remove(self.bag, x) return table.remove(self.bag, x)
end end

View File

@@ -6,7 +6,7 @@ function Bag7NoSZOStartRandomizer:shuffleBag()
local b = self.bag local b = self.bag
local ln = #b local ln = #b
for i = 1, ln do for i = 1, ln do
local j = math.random(i, ln) local j = love.math.random(i, ln)
b[i], b[j] = b[j], b[i] b[i], b[j] = b[j], b[i]
end end
end end

View File

@@ -8,8 +8,13 @@ function Sequence:initialize()
end end
function Sequence:generatePiece() function Sequence:generatePiece()
local piece = string.sub(self.sequence, self.counter + 1, self.counter + 1) local piece
self.counter = (self.counter + 1) % string.len(self.sequence) if type(self.sequence) == "string" then
piece = string.sub(self.sequence, self.counter + 1, self.counter + 1)
else
piece = self.sequence[self.counter + 1]
end
self.counter = (self.counter + 1) % (#self.sequence)
return piece return piece
end end

View File

@@ -10,11 +10,11 @@ end
function History4RollsRandomizer:generatePiece() function History4RollsRandomizer:generatePiece()
if self.first then if self.first then
self.first = false self.first = false
return self:updateHistory(({"L", "J", "I", "T"})[math.random(4)]) return self:updateHistory(({"L", "J", "I", "T"})[love.math.random(4)])
else else
local shapes = {"I", "J", "L", "O", "S", "T", "Z"} local shapes = {"I", "J", "L", "O", "S", "T", "Z"}
for i = 1, 4 do for i = 1, 4 do
local x = math.random(7) local x = love.math.random(7)
if not inHistory(shapes[x], self.history) or i == 4 then if not inHistory(shapes[x], self.history) or i == 4 then
return self:updateHistory(shapes[x]) return self:updateHistory(shapes[x])
end end

View File

@@ -10,11 +10,11 @@ end
function History6RollsRandomizer:generatePiece() function History6RollsRandomizer:generatePiece()
if self.first then if self.first then
self.first = false self.first = false
return self:updateHistory(({"L", "J", "I", "T"})[math.random(4)]) return self:updateHistory(({"L", "J", "I", "T"})[love.math.random(4)])
else else
local shapes = {"I", "J", "L", "O", "S", "T", "Z"} local shapes = {"I", "J", "L", "O", "S", "T", "Z"}
for i = 1, 6 do for i = 1, 6 do
local x = math.random(7) local x = love.math.random(7)
if not inHistory(shapes[x], self.history) or i == 6 then if not inHistory(shapes[x], self.history) or i == 6 then
return self:updateHistory(shapes[x]) return self:updateHistory(shapes[x])
end end

View File

@@ -28,12 +28,12 @@ end
function History6Rolls35PoolRandomizer:generatePiece() function History6Rolls35PoolRandomizer:generatePiece()
local index, x local index, x
if self.first then if self.first then
index = math.random(20) index = love.math.random(20)
x = self.pool[index] x = self.pool[index]
self.first = false self.first = false
else else
for i = 1, 6 do for i = 1, 6 do
index = math.random(#self.pool) index = love.math.random(#self.pool)
x = self.pool[index] x = self.pool[index]
if not inHistory(x, self.history) or i == 6 then if not inHistory(x, self.history) or i == 6 then
break break

View File

@@ -16,7 +16,7 @@ function Randomizer:initialize()
end end
function Randomizer:generatePiece() function Randomizer:generatePiece()
return self.possible_pieces[math.random(7)] return self.possible_pieces[love.math.random(7)]
end end
return Randomizer return Randomizer