From 9b41e56135b9b590fda7ee6da91ae8270ffc4c1d Mon Sep 17 00:00:00 2001 From: Ishaan Bhardwaj Date: Wed, 8 Dec 2021 20:19:46 -0500 Subject: [PATCH] Replay system v2 --- scene/mode_select.lua | 34 ++++++- scene/replay.lua | 8 +- scene/replay_select.lua | 13 +-- tetris/components/grid.lua | 13 +++ tetris/modes/gamemode.lua | 137 +++++++++++++------------- tetris/randomizers/fixed_sequence.lua | 9 +- 6 files changed, 131 insertions(+), 83 deletions(-) diff --git a/scene/mode_select.lua b/scene/mode_select.lua index d35594c..d495958 100755 --- a/scene/mode_select.lua +++ b/scene/mode_select.lua @@ -39,14 +39,24 @@ end function ModeSelectScene: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 @@ -136,10 +146,26 @@ function ModeSelectScene:onInputPress(e) 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 == "left" or e.input == "right" or e.scancode == "left" or e.scancode == "right" then self:switchSelect() elseif e.input == "menu_back" or e.scancode == "delete" or e.scancode == "backspace" then @@ -154,6 +180,10 @@ function ModeSelectScene: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/scene/replay.lua b/scene/replay.lua index 7c875d3..2829d70 100644 --- a/scene/replay.lua +++ b/scene/replay.lua @@ -5,11 +5,12 @@ local ReplayScene = Scene:extend() ReplayScene.title = "Replay" function ReplayScene:new(replay, game_mode, ruleset, inputs) + config.gamesettings = replay["gamesettings"] self.secret_inputs = inputs self.game = game_mode(self.secret_inputs) self.ruleset = ruleset(self.game) -- Replace piece randomizer with replay piece sequence - local randomizer = Sequence(table.keys(ruleset.colourscheme)) + local randomizer = Sequence() randomizer.sequence = replay["pieces"] self.game:initializeReplay(self.ruleset, randomizer) self.inputs = { @@ -28,7 +29,7 @@ function ReplayScene:new(replay, game_mode, ruleset, inputs) self.replay = replay self.replay_index = 1 DiscordRPC:update({ - details = self.game.rpc_details, + details = "Viewing a replay", state = self.game.name, largeImageKey = "ingame-"..self.game:getBackground().."00" }) @@ -55,11 +56,14 @@ end function ReplayScene:render() self.game:draw(self.paused) + love.graphics.setFont(font_3x5_3) + love.graphics.printf("REPLAY", 0, 0, 635, "right") end function ReplayScene:onInputPress(e) if (e.input == "menu_back") then self.game:onExit() + loadSave() scene = ReplaySelectScene() elseif e.input == "pause" and not (self.game.game_over or self.game.completed) then self.paused = not self.paused diff --git a/scene/replay_select.lua b/scene/replay_select.lua index c52d96c..72f3e57 100644 --- a/scene/replay_select.lua +++ b/scene/replay_select.lua @@ -45,7 +45,6 @@ function ReplaySelectScene:new() self.menu_state = { replay = current_replay, } - self.secret_inputs = {} self.das = 0 DiscordRPC:update({ details = "In menus", @@ -55,8 +54,6 @@ function ReplaySelectScene:new() 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 @@ -126,6 +123,7 @@ function ReplaySelectScene:render() 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 @@ -145,9 +143,6 @@ 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.x % 2 == 1 then - self:switchSelect() - end if e.y ~= 0 then self:changeOption(-e.y) end @@ -179,7 +174,7 @@ function ReplaySelectScene:onInputPress(e) replays[self.menu_state.replay], mode, rules, - self.secret_inputs + replays[self.menu_state.replay]["secret_inputs"] ) elseif e.input == "up" or e.scancode == "up" then self:changeOption(-1) @@ -207,8 +202,6 @@ function ReplaySelectScene:onInputPress(e) self.das_down = 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 @@ -221,8 +214,6 @@ function ReplaySelectScene:onInputRelease(e) 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 end diff --git a/tetris/components/grid.lua b/tetris/components/grid.lua index d8c2bd1..2015e2b 100644 --- a/tetris/components/grid.lua +++ b/tetris/components/grid.lua @@ -182,6 +182,19 @@ function Grid:clearSpecificRow(row) 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) if piece.big then self:applyBigPiece(piece) diff --git a/tetris/modes/gamemode.lua b/tetris/modes/gamemode.lua index 15b1da5..538877d 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.secret_inputs = secret_inputs + self.replay_pieces = {} self.save_replay = true end @@ -90,7 +91,9 @@ function GameMode:getGravity() return 1/64 end function GameMode:getNextPiece(ruleset) local shape = self.used_randomizer:nextPiece() - self.replay_pieces = self.replay_pieces..shape + if self.save_replay then + self.replay_pieces[#self.replay_pieces + 1] = shape + end return { skin = self:getSkin(), shape = shape, @@ -102,6 +105,15 @@ function GameMode:getSkin() return "2tie" end +function GameMode:sharedInitialize(ruleset) + 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 + function GameMode:initialize(ruleset) -- generate next queue self.used_randomizer = ( @@ -111,27 +123,71 @@ function GameMode:initialize(ruleset) ) and self.randomizer or BagRandomizer(table.keys(ruleset.colourscheme)) ) - 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] + self:sharedInitialize(ruleset) 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)) + self:sharedInitialize(ruleset) +end + +function GameMode:saveReplay() + -- 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["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 - 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:update(inputs, ruleset) 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 return end @@ -145,24 +201,7 @@ function GameMode:update(inputs, ruleset) end end - -- check if inputs have changed since last frame - 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"] = {} - 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 + if self.save_replay then self:addReplayInput(inputs) end -- advance one frame if self:advanceOneFrame(inputs, ruleset) == false then return end @@ -371,41 +410,7 @@ function GameMode:onGameOver() switchBGM(nil) local alpha = 0 local animation_length = 120 - if self.game_over_frames == 1 then - alpha = 1 - 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 - elseif self.game_over_frames < animation_length then + 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 diff --git a/tetris/randomizers/fixed_sequence.lua b/tetris/randomizers/fixed_sequence.lua index cbd8606..2c78436 100644 --- a/tetris/randomizers/fixed_sequence.lua +++ b/tetris/randomizers/fixed_sequence.lua @@ -8,8 +8,13 @@ function Sequence:initialize() end function Sequence:generatePiece() - local piece = string.sub(self.sequence, self.counter + 1, self.counter + 1) - self.counter = (self.counter + 1) % string.len(self.sequence) + local piece + 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 end