From 6c4551ebefe90edaf9bc3a83890be4abe364f83d Mon Sep 17 00:00:00 2001 From: BoatsandJoes Date: Sat, 4 Dec 2021 20:35:15 -0600 Subject: [PATCH 1/7] Added replay saving. --- tetris/modes/gamemode.lua | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tetris/modes/gamemode.lua b/tetris/modes/gamemode.lua index abbee8e..f765be1 100644 --- a/tetris/modes/gamemode.lua +++ b/tetris/modes/gamemode.lua @@ -7,6 +7,7 @@ local playedGoSE = false local Grid = require 'tetris.components.grid' local Randomizer = require 'tetris.randomizers.randomizer' local BagRandomizer = require 'tetris.randomizers.bag' +local binser = require 'libs.binser' local GameMode = Object:extend() @@ -72,6 +73,8 @@ function GameMode:new(secret_inputs) self.section_start_time = 0 self.section_times = { [0] = 0 } self.secondary_section_times = { [0] = 0 } + self.replay_inputs = {} + self.replay_pieces = {} end function GameMode:getARR() return 1 end @@ -86,6 +89,7 @@ function GameMode:getGravity() return 1/64 end function GameMode:getNextPiece(ruleset) local shape = self.used_randomizer:nextPiece() + table.insert(self.replay_pieces,shape) return { skin = self:getSkin(), shape = shape, @@ -98,6 +102,10 @@ function GameMode:getSkin() end function GameMode:initialize(ruleset) + local dummy_entry = {} + dummy_entry["inputs"] = {} + dummy_entry["frames"] = 0 + table.insert(self.replay_inputs, dummy_entry) -- generate next queue self.used_randomizer = ( table.equalvalues( @@ -129,6 +137,19 @@ function GameMode:update(inputs, ruleset) end end + -- check if inputs have changed since last frame + local last_input_index = table.maxn(self.replay_inputs) + if self.replay_inputs[last_input_index]["inputs"] ~= inputs then + -- insert new inputs into replay inputs table + local new_inputs = {} + new_inputs["inputs"] = inputs + new_inputs["frames"] = 0 + table.insert(self.replay_inputs,new_inputs) + else + -- add 1 to input frame counter + self.replay_inputs[last_input_index]["frames"] = self.replay_inputs[last_input_index]["frames"] + 1 + end + -- advance one frame if self:advanceOneFrame(inputs, ruleset) == false then return end @@ -342,6 +363,18 @@ function GameMode:onGameOver() 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 + elseif self.game_over_frames == 2 * animation_length then + -- Save replay. + local replay = {} + replay["inputs"] = self.replay_inputs + replay["pieces"] = self.replay_pieces + replay["mode"] = self.name + replay["ruleset"] = self.ruleset.name + if love.filesystem.getInfo("replays") == nil then + love.filesystem.createDirectory("replays") + end + local replay_number = table.getn(love.filesystem.getDirectoryItems("replays")) + 1 + love.filesystem.write("replays/"..replay_number..".lua", binser.serialize(replay)) end love.graphics.setColor(0, 0, 0, alpha) love.graphics.rectangle( From 71ada76a004f81b6fe83c64ec532d447b5ed0fbd Mon Sep 17 00:00:00 2001 From: BoatsandJoes Date: Sat, 4 Dec 2021 23:17:25 -0600 Subject: [PATCH 2/7] Started work on replay select menu. --- main.lua | 2 + scene.lua | 2 + scene/replay.lua | 11 +++ scene/replay_select.lua | 148 ++++++++++++++++++++++++++++++++++++++ scene/title.lua | 1 + tetris/modes/gamemode.lua | 30 ++++++-- 6 files changed, 189 insertions(+), 5 deletions(-) create mode 100644 scene/replay.lua create mode 100644 scene/replay_select.lua diff --git a/main.lua b/main.lua index f54b3c1..c48c24f 100644 --- a/main.lua +++ b/main.lua @@ -35,6 +35,8 @@ function love.load() end function initModules() + -- replays are not loaded here, but they are cleared + replays = {} game_modes = {} mode_list = love.filesystem.getDirectoryItems("tetris/modes") for i=1,#mode_list do diff --git a/scene.lua b/scene.lua index aaa81b8..cc5a2f6 100644 --- a/scene.lua +++ b/scene.lua @@ -10,7 +10,9 @@ function Scene:onInputRelease() end ExitScene = require "scene.exit" GameScene = require "scene.game" +ReplayScene = require "scene.replay" ModeSelectScene = require "scene.mode_select" +ReplaySelectScene = require "scene.replay_select" KeyConfigScene = require "scene.key_config" StickConfigScene = require "scene.stick_config" InputConfigScene = require "scene.input_config" diff --git a/scene/replay.lua b/scene/replay.lua new file mode 100644 index 0000000..2be8812 --- /dev/null +++ b/scene/replay.lua @@ -0,0 +1,11 @@ +local ReplayScene = Scene:extend() + +ReplayScene.title = "Replay" + +require 'load.save' + +function ReplayScene:new(replay, inputs) + -- TODO +end + +return ReplayScene diff --git a/scene/replay_select.lua b/scene/replay_select.lua new file mode 100644 index 0000000..b359e8d --- /dev/null +++ b/scene/replay_select.lua @@ -0,0 +1,148 @@ +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 + replays[i] = binser.deserialize(love.filesystem.read("replays/"..replay_file_list[i])) + end + -- TODO sort replays list + 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.secret_inputs = {} + 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 then + self.das = self.das + 1 + else + self.das = 0 + end + + if self.das >= 15 then + self:changeOption(self.das_up and -1 or 1) + 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) + + 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 + end + + love.graphics.setColor(1, 1, 1, 0.5) + love.graphics.rectangle("fill", 20, 258, 240, 22) + + love.graphics.setFont(font_3x5_2) + for idx, replay in pairs(replays) do + if(idx >= self.menu_state.replay-9 and idx <= self.menu_state.replay+9) then + local display_string = replay["mode"].." "..replay["ruleset"].." "..replay["timer"].." "..replay["level"].." "..os.date("%c", replay["timestamp"]) + love.graphics.printf(display_string, 40, (260 - 20*(self.menu_state.replay)) + 20 * idx, 200, "left") + end + end +end + +function ReplaySelectScene:onInputPress(e) + if self.display_warning and e.input then + scene = TitleScene() + elseif e.type == "wheel" then + if e.x % 2 == 1 then + self:switchSelect() + end + 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") + scene = ReplayScene( + replays[self.menu_state.replay], + self.secret_inputs + ) + elseif e.input == "up" or e.scancode == "up" then + self:changeOption(-1) + self.das_up = true + self.das_down = nil + elseif e.input == "down" or e.scancode == "down" then + self:changeOption(1) + self.das_down = true + self.das_up = nil + elseif e.input == "menu_back" or e.scancode == "delete" or e.scancode == "backspace" then + scene = TitleScene() + elseif e.input then + self.secret_inputs[e.input] = true + 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 then + self.secret_inputs[e.input] = false + 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 diff --git a/scene/title.lua b/scene/title.lua index 05df3bb..6f111dc 100644 --- a/scene/title.lua +++ b/scene/title.lua @@ -5,6 +5,7 @@ TitleScene.restart_message = false local main_menu_screens = { ModeSelectScene, + ReplaySelectScene, SettingsScene, CreditsScene, ExitScene, diff --git a/tetris/modes/gamemode.lua b/tetris/modes/gamemode.lua index f765be1..1c4632a 100644 --- a/tetris/modes/gamemode.lua +++ b/tetris/modes/gamemode.lua @@ -360,21 +360,41 @@ function GameMode:onGameOver() 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). + elseif self.game_over_frames == animation_length then alpha = 1 - elseif self.game_over_frames == 2 * animation_length then -- Save replay. local replay = {} replay["inputs"] = self.replay_inputs replay["pieces"] = self.replay_pieces 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["timestamp"] = os.time() if love.filesystem.getInfo("replays") == nil then love.filesystem.createDirectory("replays") end - local replay_number = table.getn(love.filesystem.getDirectoryItems("replays")) + 1 - love.filesystem.write("replays/"..replay_number..".lua", binser.serialize(replay)) + 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..".rply" then + collision = true + break + end + end + end + love.filesystem.write("replays/"..replay_number..".rply", binser.serialize(replay)) + 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( From 59c7834c9afc316ff4c5690ab1dfea3a5cf3309a Mon Sep 17 00:00:00 2001 From: BoatsandJoes Date: Sun, 5 Dec 2021 00:18:19 -0600 Subject: [PATCH 3/7] Fixed replay deserialization. --- scene/replay_select.lua | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/scene/replay_select.lua b/scene/replay_select.lua index b359e8d..4d80f80 100644 --- a/scene/replay_select.lua +++ b/scene/replay_select.lua @@ -13,7 +13,9 @@ function ReplaySelectScene:new() replays = {} replay_file_list = love.filesystem.getDirectoryItems("replays") for i=1,#replay_file_list do - replays[i] = binser.deserialize(love.filesystem.read("replays/"..replay_file_list[i])) + local data = love.filesystem.read("replays/"..replay_file_list[i]) + local object = binser.deserialize(data) + replays[i] = object[1] end -- TODO sort replays list if table.getn(replays) == 0 then @@ -85,13 +87,14 @@ function ReplaySelectScene:render() end love.graphics.setColor(1, 1, 1, 0.5) - love.graphics.rectangle("fill", 20, 258, 240, 22) + love.graphics.rectangle("fill", 20, 258, 500, 22) love.graphics.setFont(font_3x5_2) for idx, replay in pairs(replays) do if(idx >= self.menu_state.replay-9 and idx <= self.menu_state.replay+9) then + -- TODO format timer into minutes:seconds:centiseconds local display_string = replay["mode"].." "..replay["ruleset"].." "..replay["timer"].." "..replay["level"].." "..os.date("%c", replay["timestamp"]) - love.graphics.printf(display_string, 40, (260 - 20*(self.menu_state.replay)) + 20 * idx, 200, "left") + love.graphics.printf(display_string, 40, (260 - 20*(self.menu_state.replay)) + 20 * idx, 500, "left") end end end From a5750e49592522df2a82eedc0695cc74b1d8fe68 Mon Sep 17 00:00:00 2001 From: BoatsandJoes Date: Sun, 5 Dec 2021 15:41:51 -0600 Subject: [PATCH 4/7] Replays list is now sorted, and replays are smaller. --- scene/replay_select.lua | 31 +++++++++++++++++++++++-------- tetris/modes/gamemode.lua | 23 +++++++++++++---------- 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/scene/replay_select.lua b/scene/replay_select.lua index 4d80f80..2e4591d 100644 --- a/scene/replay_select.lua +++ b/scene/replay_select.lua @@ -14,10 +14,26 @@ function ReplaySelectScene:new() 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 object = binser.deserialize(data) - replays[i] = object[1] + local new_replay = binser.deserialize(data)[1] + -- Insert, sorting by date played, newest first + local start_index, mid_index, end_index = 1, 1, i + if i ~= 1 then + while start_index <= end_index do + mid_index = math.floor((start_index + end_index) / 2) + print(start_index, mid_index, end_index) + print(replays[mid_index]) + print(new_replay) + if os.difftime(replays[mid_index]["timestamp"], new_replay["timestamp"]) <= 0 then + -- search first half + end_index = mid_index - 1 + else + -- search second half + start_index = mid_index + 1 + end + end + end + table.insert(replays, mid_index, new_replay) end - -- TODO sort replays list if table.getn(replays) == 0 then self.display_warning = true current_replay = 1 @@ -87,14 +103,13 @@ function ReplaySelectScene:render() end love.graphics.setColor(1, 1, 1, 0.5) - love.graphics.rectangle("fill", 20, 258, 500, 22) + love.graphics.rectangle("fill", 3, 258, 634, 22) love.graphics.setFont(font_3x5_2) - for idx, replay in pairs(replays) do + for idx, replay in ipairs(replays) do if(idx >= self.menu_state.replay-9 and idx <= self.menu_state.replay+9) then - -- TODO format timer into minutes:seconds:centiseconds - local display_string = replay["mode"].." "..replay["ruleset"].." "..replay["timer"].." "..replay["level"].." "..os.date("%c", replay["timestamp"]) - love.graphics.printf(display_string, 40, (260 - 20*(self.menu_state.replay)) + 20 * idx, 500, "left") + local display_string = os.date("%c", replay["timestamp"]).." "..replay["mode"].." "..replay["ruleset"].." Level: "..replay["level"].." Time: "..formatTime(replay["timer"]) + love.graphics.printf(display_string, 6, (260 - 20*(self.menu_state.replay)) + 20 * idx, 640, "left") end end end diff --git a/tetris/modes/gamemode.lua b/tetris/modes/gamemode.lua index 1c4632a..d357295 100644 --- a/tetris/modes/gamemode.lua +++ b/tetris/modes/gamemode.lua @@ -89,7 +89,7 @@ function GameMode:getGravity() return 1/64 end function GameMode:getNextPiece(ruleset) local shape = self.used_randomizer:nextPiece() - table.insert(self.replay_pieces,shape) + self.replay_pieces[#self.replay_pieces + 1] = shape return { skin = self:getSkin(), shape = shape, @@ -105,7 +105,7 @@ function GameMode:initialize(ruleset) local dummy_entry = {} dummy_entry["inputs"] = {} dummy_entry["frames"] = 0 - table.insert(self.replay_inputs, dummy_entry) + self.replay_inputs[#self.replay_inputs + 1] = dummy_entry -- generate next queue self.used_randomizer = ( table.equalvalues( @@ -138,16 +138,19 @@ function GameMode:update(inputs, ruleset) end -- check if inputs have changed since last frame - local last_input_index = table.maxn(self.replay_inputs) - if self.replay_inputs[last_input_index]["inputs"] ~= inputs then + if self.prev_inputs["left"] ~= inputs["left"] or self.prev_inputs["right"] ~= inputs["right"] + or self.prev_inputs["down"] ~= inputs["down"] or self.prev_inputs["up"] ~= inputs["up"] + or self.prev_inputs["rotate_left"] ~= inputs["rotate_left"] or self.prev_inputs["rotate_right"] ~= inputs["rotate_right"] + or self.prev_inputs["hold"] ~= inputs["hold"] or self.prev_inputs["rotate_180"] ~= inputs["rotate_180"] + or self.prev_inputs["rotate_left2"] ~= inputs["rotate_left2"] or self.prev_inputs["rotate_right2"] ~= inputs["rotate_right2"] then -- insert new inputs into replay inputs table local new_inputs = {} new_inputs["inputs"] = inputs new_inputs["frames"] = 0 - table.insert(self.replay_inputs,new_inputs) + self.replay_inputs[#self.replay_inputs + 1] = new_inputs else -- add 1 to input frame counter - self.replay_inputs[last_input_index]["frames"] = self.replay_inputs[last_input_index]["frames"] + 1 + self.replay_inputs[#self.replay_inputs]["frames"] = self.replay_inputs[#self.replay_inputs]["frames"] + 1 end -- advance one frame @@ -357,10 +360,7 @@ function GameMode:onGameOver() switchBGM(nil) 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 == animation_length then + if self.game_over_frames == 1 then alpha = 1 -- Save replay. local replay = {} @@ -392,6 +392,9 @@ function GameMode:onGameOver() end end love.filesystem.write("replays/"..replay_number..".rply", binser.serialize(replay)) + elseif 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 From 81ab7cd4def417c179971e1c95efdd2081491212 Mon Sep 17 00:00:00 2001 From: BoatsandJoes Date: Sun, 5 Dec 2021 21:16:13 -0600 Subject: [PATCH 5/7] Replays now replay inputs properly, and replay list has fast scroll. --- scene/replay.lua | 61 +++++++++++++++++++++++++++++-- scene/replay_select.lua | 75 +++++++++++++++++++++++++++++++++++---- tetris/modes/gamemode.lua | 11 +++--- 3 files changed, 132 insertions(+), 15 deletions(-) diff --git a/scene/replay.lua b/scene/replay.lua index 2be8812..c7814d9 100644 --- a/scene/replay.lua +++ b/scene/replay.lua @@ -2,10 +2,65 @@ local ReplayScene = Scene:extend() ReplayScene.title = "Replay" -require 'load.save' +function ReplayScene:new(replay, game_mode, ruleset, inputs) + self.secret_inputs = inputs + self.game = game_mode(self.secret_inputs) + self.ruleset = ruleset(self.game) + self.game:initialize(self.ruleset) + 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 = replay + self.replay_index = 1 + DiscordRPC:update({ + details = self.game.rpc_details, + state = self.game.name, + largeImageKey = "ingame-"..self.game:getBackground().."00" + }) +end -function ReplayScene:new(replay, inputs) - -- TODO +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({ + largeImageKey = "ingame-"..self.game:getBackground().."00" + }) + end +end + +function ReplayScene:render() + self.game:draw(self.paused) +end + +function ReplayScene:onInputPress(e) + if (e.input == "menu_back") then + self.game:onExit() + scene = 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 diff --git a/scene/replay_select.lua b/scene/replay_select.lua index 2e4591d..f1e43f6 100644 --- a/scene/replay_select.lua +++ b/scene/replay_select.lua @@ -20,9 +20,6 @@ function ReplaySelectScene:new() if i ~= 1 then while start_index <= end_index do mid_index = math.floor((start_index + end_index) / 2) - print(start_index, mid_index, end_index) - print(replays[mid_index]) - print(new_replay) if os.difftime(replays[mid_index]["timestamp"], new_replay["timestamp"]) <= 0 then -- search first half end_index = mid_index - 1 @@ -34,6 +31,7 @@ function ReplaySelectScene:new() end table.insert(replays, mid_index, new_replay) end + self.display_error = false if table.getn(replays) == 0 then self.display_warning = true current_replay = 1 @@ -59,14 +57,24 @@ end function ReplaySelectScene:update() switchBGM(nil) -- experimental - if self.das_up or self.das_down then + 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 - self:changeOption(self.das_up and -1 or 1) + 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 @@ -100,6 +108,19 @@ function ReplaySelectScene:render() 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) @@ -115,7 +136,7 @@ function ReplaySelectScene:render() end function ReplaySelectScene:onInputPress(e) - if self.display_warning and e.input then + if (self.display_warning or self.display_error) and e.input then scene = TitleScene() elseif e.type == "wheel" then if e.x % 2 == 1 then @@ -128,18 +149,56 @@ function ReplaySelectScene:onInputPress(e) 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, self.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() elseif e.input then @@ -152,6 +211,10 @@ function ReplaySelectScene:onInputRelease(e) 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 elseif e.input then self.secret_inputs[e.input] = false end diff --git a/tetris/modes/gamemode.lua b/tetris/modes/gamemode.lua index d357295..4fae4e6 100644 --- a/tetris/modes/gamemode.lua +++ b/tetris/modes/gamemode.lua @@ -102,10 +102,6 @@ function GameMode:getSkin() end function GameMode:initialize(ruleset) - local dummy_entry = {} - dummy_entry["inputs"] = {} - dummy_entry["frames"] = 0 - self.replay_inputs[#self.replay_inputs + 1] = dummy_entry -- generate next queue self.used_randomizer = ( table.equalvalues( @@ -145,8 +141,11 @@ function GameMode:update(inputs, ruleset) or self.prev_inputs["rotate_left2"] ~= inputs["rotate_left2"] or self.prev_inputs["rotate_right2"] ~= inputs["rotate_right2"] then -- insert new inputs into replay inputs table local new_inputs = {} - new_inputs["inputs"] = inputs - new_inputs["frames"] = 0 + 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 From febd1de0efd817d02df97a11d707b7210e2a5014 Mon Sep 17 00:00:00 2001 From: BoatsandJoes Date: Sun, 5 Dec 2021 22:17:44 -0600 Subject: [PATCH 6/7] Replays are now fully functional. --- scene/replay.lua | 7 +++- tetris/modes/gamemode.lua | 74 +++++++++++++++++++++++---------------- 2 files changed, 50 insertions(+), 31 deletions(-) diff --git a/scene/replay.lua b/scene/replay.lua index c7814d9..7c875d3 100644 --- a/scene/replay.lua +++ b/scene/replay.lua @@ -1,3 +1,5 @@ +local Sequence = require 'tetris.randomizers.fixed_sequence' + local ReplayScene = Scene:extend() ReplayScene.title = "Replay" @@ -6,7 +8,10 @@ function ReplayScene:new(replay, game_mode, ruleset, inputs) self.secret_inputs = inputs self.game = game_mode(self.secret_inputs) self.ruleset = ruleset(self.game) - self.game:initialize(self.ruleset) + -- Replace piece randomizer with replay piece sequence + local randomizer = Sequence(table.keys(ruleset.colourscheme)) + randomizer.sequence = replay["pieces"] + self.game:initializeReplay(self.ruleset, randomizer) self.inputs = { left=false, right=false, diff --git a/tetris/modes/gamemode.lua b/tetris/modes/gamemode.lua index 4fae4e6..15b1da5 100644 --- a/tetris/modes/gamemode.lua +++ b/tetris/modes/gamemode.lua @@ -74,7 +74,8 @@ function GameMode:new(secret_inputs) self.section_times = { [0] = 0 } self.secondary_section_times = { [0] = 0 } self.replay_inputs = {} - self.replay_pieces = {} + self.replay_pieces = "" + self.save_replay = true end function GameMode:getARR() return 1 end @@ -89,7 +90,7 @@ function GameMode:getGravity() return 1/64 end function GameMode:getNextPiece(ruleset) local shape = self.used_randomizer:nextPiece() - self.replay_pieces[#self.replay_pieces + 1] = shape + self.replay_pieces = self.replay_pieces..shape return { skin = self:getSkin(), shape = shape, @@ -114,7 +115,18 @@ function GameMode:initialize(ruleset) for i = 1, math.max(self.next_queue_length, 1) do table.insert(self.next_queue, self:getNextPiece(ruleset)) 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] +end + +function GameMode:initializeReplay(ruleset, randomizer) + self.used_randomizer = randomizer + self.save_replay = false + self.ruleset = ruleset + for i = 1, math.max(self.next_queue_length, 1) do + table.insert(self.next_queue, self:getNextPiece(ruleset)) + end + 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] end @@ -361,36 +373,38 @@ function GameMode:onGameOver() local animation_length = 120 if self.game_over_frames == 1 then alpha = 1 - -- Save replay. - local replay = {} - replay["inputs"] = self.replay_inputs - replay["pieces"] = self.replay_pieces - 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["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..".rply" then - collision = true - break + if self.save_replay then + -- Save replay. + local replay = {} + replay["inputs"] = self.replay_inputs + replay["pieces"] = self.replay_pieces + 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["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..".rply" then + collision = true + break + end end end + love.filesystem.write("replays/"..replay_number..".rply", binser.serialize(replay)) end - love.filesystem.write("replays/"..replay_number..".rply", binser.serialize(replay)) elseif 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) From 332e3869deade9fc9579d74983e069b20c7184b7 Mon Sep 17 00:00:00 2001 From: BoatsandJoes Date: Mon, 6 Dec 2021 22:38:07 -0600 Subject: [PATCH 7/7] Replay menu no longer crashes if level or timer is nil. --- scene/replay_select.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/scene/replay_select.lua b/scene/replay_select.lua index f1e43f6..c52d96c 100644 --- a/scene/replay_select.lua +++ b/scene/replay_select.lua @@ -129,7 +129,13 @@ function ReplaySelectScene:render() 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"].." Level: "..replay["level"].." Time: "..formatTime(replay["timer"]) + 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 love.graphics.printf(display_string, 6, (260 - 20*(self.menu_state.replay)) + 20 * idx, 640, "left") end end