diff --git a/docs/gamemodes.md b/docs/gamemodes.md index 537308f..19b755e 100644 --- a/docs/gamemodes.md +++ b/docs/gamemodes.md @@ -1,13 +1,26 @@ Game modes ========== -There are several classes of game modes. +There are several classes of game modes. The modes that originate from other games are organized by suffix: +* The "C" series stand for "Classic" games, games that were produced before around 1992-1993 and generally have no wallkicks or lock delay. + * C84 - The original version from the Electronika 60. + * C88 - Sega Tetris. + * C89 - Nintendo / NES Tetris. +* The "A" series stand for "Arika" games, or games in the Tetris the Grand Master series. + * A1 - Tetris The Grand Master (the original from 1998). + * A2 - Tetris The Absolute The Grand Master 2 PLUS. + * A3 - Tetris The Grand Master 3 Terror-Instinct. + * AX - Tetris The Grand Master ACE (X for Xbox). +* The "G" series stand for "Guideline" games, or games that follow the Tetris Guideline. + * GF - Tetris Friends (2007-2019) + * GJ - Tetris Online Japan (2005-2011) +* N stands for Nullpomino, only used for Phantom Mania N. MARATHON -------- -Modes in which the goal is to play as well as possible over a limited game interval, to ultimately achieve the title of Grand Master. +Modes in which the goal is to play as well as possible over a limited game interval. * **MARATHON 2020**: 2020 levels of pure pain. Can you make it all the way? @@ -15,6 +28,8 @@ From other games: * **MARATHON A1**: Tetris the Grand Master 1. * **MARATHON A2**: Tetris the Grand Master 2 (TAP Master). * **MARATHON A3**: Tetris the Grand Master 3 (no exams). +* **MARATHON AX4**: Another mode from TGM Ace. +* **MARATHON C89**: Nintendo NES Tetris. Can you transition and make it to the killscreen? SURVIVAL @@ -30,6 +45,14 @@ From other games: * **SURVIVAL A3**: Ti Shirase. +RACE +---- + +Modes with no levels, just a single timed goal. + +* **Race 40**: How fast can you clear 40 lines? No limits, no holds barred. + + PHANTOM MANIA ------------- @@ -40,6 +63,7 @@ Modes where pieces turn invisible as soon as you lock them. One of Cambridge's s * **Phantom Mania 2**: Phantom Mania but way faster! Can you face a mode where even the garbage and the next preview turn invisible? + OTHER MODES ----------- @@ -47,4 +71,6 @@ OTHER MODES * **TetrisGramâ„¢ Pacer Test**: is a multi-stage piece-placing ability test that progressively gets more difficult as it continues. -* **Interval Training**: 30 seconds per section. 20G. 15 frames of lock delay. How long can you last? \ No newline at end of file +* **Interval Training**: 30 seconds per section. 20G. 15 frames of lock delay. How long can you last? + +* **Demon Mode**: An original mode from Oshisaure! Can you push through the ever faster levels and not get denied? \ No newline at end of file diff --git a/scene/mode_select.lua b/scene/mode_select.lua index c54d8a7..a8b4573 100644 --- a/scene/mode_select.lua +++ b/scene/mode_select.lua @@ -15,14 +15,15 @@ game_modes = { require 'tetris.modes.phantom_mania', require 'tetris.modes.phantom_mania2', require 'tetris.modes.phantom_mania_n', - require 'tetris.modes.ligne', + require 'tetris.modes.race_40', require 'tetris.modes.marathon_a1', require 'tetris.modes.marathon_a2', require 'tetris.modes.marathon_a3', + require 'tetris.modes.marathon_ax4', + require 'tetris.modes.marathon_c89', require 'tetris.modes.survival_a1', require 'tetris.modes.survival_a2', require 'tetris.modes.survival_a3', - require 'tetris.modes.marathon_l1', } rulesets = { diff --git a/tetris/components/piece.lua b/tetris/components/piece.lua index 8de8e6c..56c1f7b 100644 --- a/tetris/components/piece.lua +++ b/tetris/components/piece.lua @@ -139,9 +139,9 @@ function Piece:draw(opacity, brightness, grid, partial_das) love.graphics.setColor(brightness, brightness, brightness, opacity) local offsets = self:getBlockOffsets() local gravity_offset = 0 - if grid ~= nil and not self:isDropBlocked(grid) then - gravity_offset = self.gravity * 16 - end + --if grid ~= nil and not self:isDropBlocked(grid) then + -- gravity_offset = self.gravity * 16 + --end if partial_das == nil then partial_das = 0 end for index, offset in pairs(offsets) do local x = self.position.x + offset.x diff --git a/tetris/modes/gamemode.lua b/tetris/modes/gamemode.lua index a468990..c5d5a15 100644 --- a/tetris/modes/gamemode.lua +++ b/tetris/modes/gamemode.lua @@ -36,6 +36,7 @@ function GameMode:new() self.enable_hold = false self.enable_hard_drop = true self.next_queue_length = 1 + self.additive_gravity = true self.draw_section_times = false self.draw_secondary_section_times = false self.big_mode = false @@ -120,7 +121,8 @@ function GameMode:update(inputs, ruleset) ruleset:processPiece( inputs, self.piece, self.grid, self:getGravity(), self.prev_inputs, self.move, self:getLockDelay(), self:getDropSpeed(), - self.drop_locked, self.hard_drop_locked, self.enable_hard_drop + self.drop_locked, self.hard_drop_locked, + self.enable_hard_drop, self.additive_gravity ) local piece_dy = self.piece.position.y - piece_y diff --git a/tetris/modes/marathon_l1.lua b/tetris/modes/marathon_ax4.lua similarity index 76% rename from tetris/modes/marathon_l1.lua rename to tetris/modes/marathon_ax4.lua index 7defb90..4ae54d3 100644 --- a/tetris/modes/marathon_l1.lua +++ b/tetris/modes/marathon_ax4.lua @@ -5,17 +5,15 @@ local Piece = require 'tetris.components.piece' local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls' -local MarathonL1Game = GameMode:extend() +local MarathonAX4Game = GameMode:extend() -MarathonL1Game.name = "Line Attack" -MarathonL1Game.hash = "MarathonL1" -MarathonL1Game.tagline = "Can you clear the time hurdles when the game goes this fast?" +MarathonAX4Game.name = "Marathon AX4" +MarathonAX4Game.hash = "MarathonAX4" +MarathonAX4Game.tagline = "Can you clear the time hurdles when the game goes this fast?" - - -function MarathonL1Game:new() - MarathonL1Game.super:new() +function MarathonAX4Game:new() + MarathonAX4Game.super:new() self.roll_frames = 0 self.randomizer = History6RollsRandomizer() @@ -30,7 +28,7 @@ function MarathonL1Game:new() self.next_queue_length = 3 end -function MarathonL1Game:getARE() +function MarathonAX4Game:getARE() if self.lines < 10 then return 18 elseif self.lines < 40 then return 14 elseif self.lines < 60 then return 12 @@ -40,24 +38,24 @@ function MarathonL1Game:getARE() else return 6 end end -function MarathonL1Game:getLineARE() +function MarathonAX4Game:getLineARE() return self:getARE() end -function MarathonL1Game:getDasLimit() +function MarathonAX4Game:getDasLimit() if self.lines < 20 then return 10 elseif self.lines < 50 then return 9 elseif self.lines < 70 then return 8 else return 7 end end -function MarathonL1Game:getLineClearDelay() +function MarathonAX4Game:getLineClearDelay() if self.lines < 10 then return 14 elseif self.lines < 30 then return 9 else return 5 end end -function MarathonL1Game:getLockDelay() +function MarathonAX4Game:getLockDelay() if self.lines < 10 then return 28 elseif self.lines < 20 then return 24 elseif self.lines < 30 then return 22 @@ -67,15 +65,15 @@ function MarathonL1Game:getLockDelay() else return 13 end end -function MarathonL1Game:getGravity() +function MarathonAX4Game:getGravity() return 20 end -function MarathonL1Game:getSection() +function MarathonAX4Game:getSection() return math.floor(level / 100) + 1 end -function MarathonL1Game:advanceOneFrame() +function MarathonAX4Game:advanceOneFrame() if self.clear then self.roll_frames = self.roll_frames + 1 if self.roll_frames < 0 then @@ -94,7 +92,7 @@ function MarathonL1Game:advanceOneFrame() return true end -function MarathonL1Game:onLineClear(cleared_row_count) +function MarathonAX4Game:onLineClear(cleared_row_count) if not self.clear then local new_lines = self.lines + cleared_row_count self:updateSectionTimes(self.lines, new_lines) @@ -106,11 +104,11 @@ function MarathonL1Game:onLineClear(cleared_row_count) end end -function MarathonL1Game:getSectionTime() +function MarathonAX4Game:getSectionTime() return self.frames - self.section_start_time end -function MarathonL1Game:updateSectionTimes(old_lines, new_lines) +function MarathonAX4Game:updateSectionTimes(old_lines, new_lines) if math.floor(old_lines / 10) < math.floor(new_lines / 10) then -- record new section table.insert(self.section_times, self:getSectionTime()) @@ -119,23 +117,23 @@ function MarathonL1Game:updateSectionTimes(old_lines, new_lines) end end -function MarathonL1Game:onPieceEnter() +function MarathonAX4Game:onPieceEnter() self.section_clear = false end -function MarathonL1Game:drawGrid(ruleset) +function MarathonAX4Game:drawGrid(ruleset) self.grid:draw() end -function MarathonL1Game:getHighscoreData() +function MarathonAX4Game:getHighscoreData() return { lines = self.lines, frames = self.frames, } end -function MarathonL1Game:drawScoringInfo() - MarathonL1Game.super.drawScoringInfo(self) +function MarathonAX4Game:drawScoringInfo() + MarathonAX4Game.super.drawScoringInfo(self) love.graphics.setColor(1, 1, 1, 1) @@ -165,12 +163,12 @@ function MarathonL1Game:drawScoringInfo() love.graphics.setColor(1, 1, 1, 1) end -function MarathonL1Game:getSectionEndLines() +function MarathonAX4Game:getSectionEndLines() return math.floor(self.lines / 10 + 1) * 10 end -function MarathonL1Game:getBackground() +function MarathonAX4Game:getBackground() return math.floor(self.lines / 10) end -return MarathonL1Game +return MarathonAX4Game diff --git a/tetris/modes/marathon_c89.lua b/tetris/modes/marathon_c89.lua new file mode 100644 index 0000000..7b451cc --- /dev/null +++ b/tetris/modes/marathon_c89.lua @@ -0,0 +1,185 @@ +require 'funcs' + +local GameMode = require 'tetris.modes.gamemode' +local Piece = require 'tetris.components.piece' + +local Randomizer = require 'tetris.randomizers.randomizer' + +local MarathonC89Game = GameMode:extend() + +MarathonC89Game.name = "Marathon C89" +MarathonC89Game.hash = "MarathonC89" +MarathonC89Game.tagline = "Can you play fast enough to reach the killscreen?" + + +function MarathonC89Game:new() + MarathonC89Game.super:new() + + self.randomizer = Randomizer() + + self.ready_frames = 1 + self.waiting_frames = 72 + + self.start_level = 12 + self.level = 12 + + self.lock_drop = true + self.enable_hard_drop = false + self.enable_hold = false + self.next_queue_length = 1 + self.additive_gravity = false +end + +function MarathonC89Game:getDropSpeed() return 1/2 end +function MarathonC89Game:getDasLimit() return 16 end +function MarathonC89Game:getARR() return 6 end + +function MarathonC89Game:getARE() return 6 end +function MarathonC89Game:getLineARE() return 6 end +function MarathonC89Game:getLineClearDelay() return 30 end +function MarathonC89Game:getLockDelay() return 0 end + +function MarathonC89Game:chargeDAS(inputs) + if inputs[self.das.direction] == true and + self.prev_inputs[self.das.direction] == true and + not inputs["down"] and + self.piece ~= nil + then + local das_frames = self.das.frames + 1 + if das_frames >= self:getDasLimit() then + if self.das.direction == "left" then + self.move = (self:getARR() == 0 and "speed" or "") .. "left" + self.das.frames = self:getDasLimit() - self:getARR() + elseif self.das.direction == "right" then + self.move = (self:getARR() == 0 and "speed" or "") .. "right" + self.das.frames = self:getDasLimit() - self:getARR() + end + else + self.move = "none" + self.das.frames = das_frames + end + elseif inputs["right"] == true then + self.das.direction = "right" + if not inputs["down"] and self.piece ~= nil then + self.move = "right" + self.das.frames = 0 + else + self.move = "none" + end + elseif inputs["left"] == true then + self.das.direction = "left" + if not inputs["down"] and self.piece ~= nil then + self.move = "left" + self.das.frames = 0 + else + self.move = "none" + end + else + self.move = "none" + end + + if self.das.direction == "left" and self.piece ~= nil and self.piece:isMoveBlocked(self.grid, {x=-1, y=0}) or + self.das.direction == "right" and self.piece ~= nil and self.piece:isMoveBlocked(self.grid, {x=1, y=0}) + then + self.das.frames = self:getDasLimit() + end + + if inputs["down"] == false and self.prev_inputs["down"] == true then + self.drop_bonus = 0 + end +end + +local gravity_table = { + [0] = + 1366/65536, 1525/65536, 1725/65536, 1986/65536, 2341/65536, + 2850/65536, 3641/65536, 5042/65536, 8192/65536, 10923/65536, + 13108/65536, 13108/65536, 13108/65536, 16384/65536, 16384/65536, + 16384/65536, 21846/65536, 21846/65536, 21846/65536 +} + +function MarathonC89Game:getGravity() + if self.waiting_frames > 0 then return 0 end + if self.level >= 29 then return 1 + elseif self.level >= 19 then return 1/2 + else return gravity_table[self.level] end +end + +function MarathonC89Game:advanceOneFrame() + if self.waiting_frames > 0 then + self.waiting_frames = self.waiting_frames - 1 + else + self.frames = self.frames + 1 + end + return true +end + +function MarathonC89Game:onPieceLock() + self.score = self.score + self.drop_bonus + self.drop_bonus = 0 +end + +local cleared_line_scores = { 40, 100, 300, 1200 } + +function MarathonC89Game:getLevelForLines() + if self.start_level < 10 then + return math.max(self.start_level, math.floor(self.lines / 10)) + elseif self.start_level < 16 then + return math.max(self.start_level, self.start_level + math.floor((self.lines - 100) / 10)) + else + return math.max(self.start_level, math.floor((self.lines - 60) / 10)) + end +end + +function MarathonC89Game:updateScore(level, drop_bonus, cleared_lines) + if cleared_lines > 0 then + self.score = self.score + cleared_line_scores[cleared_lines] * (self.level + 1) + self.lines = self.lines + cleared_lines + self.level = self:getLevelForLines() + else + self.drop_bonus = 0 + self.combo = 1 + end +end + +function MarathonC89Game:drawGrid() + self.grid:draw() + if self.piece ~= nil and self.level < 100 then + self:drawGhostPiece(ruleset) + end +end + +function MarathonC89Game:drawScoringInfo() + MarathonC89Game.super.drawScoringInfo(self) + love.graphics.setColor(1, 1, 1, 1) + + love.graphics.setFont(font_3x5_2) + love.graphics.print( + self.das.direction .. " " .. + self.das.frames .. " " .. + st(self.prev_inputs) + ) + love.graphics.printf("NEXT", 64, 40, 40, "left") + love.graphics.printf("LINES", 240, 120, 40, "left") + love.graphics.printf("SCORE", 240, 200, 40, "left") + + love.graphics.setFont(font_3x5_3) + love.graphics.printf(self.lines, 240, 140, 90, "left") + love.graphics.printf(self.score, 240, 220, 90, "left") + + love.graphics.setFont(font_8x11) + love.graphics.printf(formatTime(self.frames), 64, 420, 160, "center") +end + + +function MarathonC89Game:getBackground() + return math.min(self.level, 19) +end + +function MarathonC89Game:getHighscoreData() + return { + score = self.score, + level = self.level, + } +end + +return MarathonC89Game diff --git a/tetris/modes/ligne.lua b/tetris/modes/race_40.lua similarity index 71% rename from tetris/modes/ligne.lua rename to tetris/modes/race_40.lua index ab40da8..9f1a312 100644 --- a/tetris/modes/ligne.lua +++ b/tetris/modes/race_40.lua @@ -5,17 +5,15 @@ local Piece = require 'tetris.components.piece' local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls' -local LigneGame = GameMode:extend() +local Race40Game = GameMode:extend() -LigneGame.name = "Ligne" -LigneGame.hash = "Ligne" -LigneGame.tagline = "How fast can you clear 40 lines?" +Race40Game.name = "Race 40" +Race40Game.hash = "Race40" +Race40Game.tagline = "How fast can you clear 40 lines?" - - -function LigneGame:new() - LigneGame.super:new() +function Race40Game:new() + Race40Game.super:new() self.lines = 0 self.line_goal = 40 @@ -32,39 +30,39 @@ function LigneGame:new() self.next_queue_length = 3 end -function LigneGame:getDropSpeed() +function Race40Game:getDropSpeed() return 20 end -function LigneGame:getARR() +function Race40Game:getARR() return 0 end -function LigneGame:getARE() +function Race40Game:getARE() return 0 end -function LigneGame:getLineARE() +function Race40Game:getLineARE() return self:getARE() end -function LigneGame:getDasLimit() +function Race40Game:getDasLimit() return 6 end -function LigneGame:getLineClearDelay() +function Race40Game:getLineClearDelay() return 0 end -function LigneGame:getLockDelay() +function Race40Game:getLockDelay() return 15 end -function LigneGame:getGravity() +function Race40Game:getGravity() return 1/64 end -function LigneGame:advanceOneFrame() +function Race40Game:advanceOneFrame() if self.clear then self.roll_frames = self.roll_frames + 1 if self.roll_frames > 150 then @@ -77,11 +75,11 @@ function LigneGame:advanceOneFrame() return true end -function LigneGame:onPieceLock() +function Race40Game:onPieceLock() self.pieces = self.pieces + 1 end -function LigneGame:onLineClear(cleared_row_count) +function Race40Game:onLineClear(cleared_row_count) if not self.clear then self.lines = self.lines + cleared_row_count if self.lines >= self.line_goal then @@ -90,22 +88,22 @@ function LigneGame:onLineClear(cleared_row_count) end end -function LigneGame:drawGrid(ruleset) +function Race40Game:drawGrid(ruleset) self.grid:draw() if self.piece ~= nil then self:drawGhostPiece(ruleset) end end -function LigneGame:getHighscoreData() +function Race40Game:getHighscoreData() return { level = self.level, frames = self.frames, } end -function LigneGame:drawScoringInfo() - LigneGame.super.drawScoringInfo(self) +function Race40Game:drawScoringInfo() + Race40Game.super.drawScoringInfo(self) love.graphics.setColor(1, 1, 1, 1) local text_x = config["side_next"] and 320 or 240 @@ -124,8 +122,8 @@ function LigneGame:drawScoringInfo() love.graphics.printf(math.max(0, self.line_goal - self.lines), text_x, 340, 40, "left") end -function LigneGame:getBackground() +function Race40Game:getBackground() return 2 end -return LigneGame +return Race40Game diff --git a/tetris/rulesets/ruleset.lua b/tetris/rulesets/ruleset.lua index bc33106..f5f7c89 100644 --- a/tetris/rulesets/ruleset.lua +++ b/tetris/rulesets/ruleset.lua @@ -72,10 +72,17 @@ function Ruleset:movePiece(piece, grid, move) end end -function Ruleset:dropPiece(inputs, piece, grid, gravity, drop_speed, drop_locked, hard_drop_locked, hard_drop_enabled) +function Ruleset:dropPiece( + inputs, piece, grid, gravity, drop_speed, drop_locked, hard_drop_locked, + hard_drop_enabled, additive_gravity +) local y = piece.position.y if inputs["down"] == true and drop_locked == false then - piece:addGravity(gravity + drop_speed, grid) + if additive_gravity then + piece:addGravity(gravity + drop_speed, grid) + else + piece:addGravity(math.max(gravity, drop_speed), grid) + end elseif inputs["up"] == true and hard_drop_enabled == true then if hard_drop_locked == true or piece:isDropBlocked(grid) then piece:addGravity(gravity, grid) @@ -127,11 +134,15 @@ function Ruleset:onPieceCreate(piece) end function Ruleset:processPiece( inputs, piece, grid, gravity, prev_inputs, move, lock_delay, drop_speed, - drop_locked, hard_drop_locked, hard_drop_enabled + drop_locked, hard_drop_locked, + hard_drop_enabled, additive_gravity ) self:rotatePiece(inputs, piece, grid, prev_inputs, false) self:movePiece(piece, grid, move) - self:dropPiece(inputs, piece, grid, gravity, drop_speed, drop_locked, hard_drop_locked, hard_drop_enabled) + self:dropPiece( + inputs, piece, grid, gravity, drop_speed, drop_locked, hard_drop_locked, + hard_drop_enabled, additive_gravity + ) self:lockPiece(piece, grid, lock_delay) end