diff --git a/docs/gamemodes.md b/docs/gamemodes.md index d3fd10e..70fbbf1 100644 --- a/docs/gamemodes.md +++ b/docs/gamemodes.md @@ -3,19 +3,11 @@ 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 -------- @@ -28,8 +20,6 @@ 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 @@ -43,14 +33,7 @@ From other games: * **SURVIVAL A1**: 20G mode from Tetris the Grand Master. * **SURVIVAL A2**: T.A. Death. * **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. +* **SURVIVAL AX**: Another mode from TGM Ace. PHANTOM MANIA @@ -69,8 +52,4 @@ OTHER MODES * **Strategy**: How well can you plan ahead your movements? Can you handle only having a short time to place each piece? -* **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? - -* **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 +* **Big A2**: Marathon A2 but all the pieces are BIG! \ No newline at end of file diff --git a/tetris/modes/marathon_ax4.lua b/tetris/modes/survival_ax.lua similarity index 76% rename from tetris/modes/marathon_ax4.lua rename to tetris/modes/survival_ax.lua index f4b628c..c8c3adc 100644 --- a/tetris/modes/marathon_ax4.lua +++ b/tetris/modes/survival_ax.lua @@ -5,15 +5,15 @@ local Piece = require 'tetris.components.piece' local Bag7NoSZOStartRandomizer = require 'tetris.randomizers.bag7noSZOstart' -local MarathonAX4Game = GameMode:extend() +local SurvivalAXGame = GameMode:extend() -MarathonAX4Game.name = "Marathon AX4" -MarathonAX4Game.hash = "MarathonAX4" -MarathonAX4Game.tagline = "Can you clear the time hurdles when the game goes this fast?" +SurvivalAXGame.name = "Survival AX" +SurvivalAXGame.hash = "SurvivalAX" +SurvivalAXGame.tagline = "Can you clear the time hurdles when the game goes this fast?" -function MarathonAX4Game:new() - MarathonAX4Game.super:new() +function SurvivalAXGame:new() + SurvivalAXGame.super:new() self.roll_frames = 0 self.randomizer = Bag7NoSZOStartRandomizer() @@ -29,7 +29,7 @@ function MarathonAX4Game:new() self.next_queue_length = 3 end -function MarathonAX4Game:getARE() +function SurvivalAXGame:getARE() if self.lines < 10 then return 18 elseif self.lines < 40 then return 14 elseif self.lines < 60 then return 12 @@ -39,24 +39,24 @@ function MarathonAX4Game:getARE() else return 6 end end -function MarathonAX4Game:getLineARE() +function SurvivalAXGame:getLineARE() return self:getARE() end -function MarathonAX4Game:getDasLimit() +function SurvivalAXGame: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 MarathonAX4Game:getLineClearDelay() +function SurvivalAXGame:getLineClearDelay() if self.lines < 10 then return 14 elseif self.lines < 30 then return 9 else return 5 end end -function MarathonAX4Game:getLockDelay() +function SurvivalAXGame:getLockDelay() if self.lines < 10 then return 28 elseif self.lines < 20 then return 24 elseif self.lines < 30 then return 22 @@ -66,15 +66,15 @@ function MarathonAX4Game:getLockDelay() else return 13 end end -function MarathonAX4Game:getGravity() +function SurvivalAXGame:getGravity() return 20 end -function MarathonAX4Game:getSection() +function SurvivalAXGame:getSection() return math.floor(level / 100) + 1 end -function MarathonAX4Game:advanceOneFrame() +function SurvivalAXGame:advanceOneFrame() if self.clear then self.roll_frames = self.roll_frames + 1 if self.roll_frames < 0 then @@ -93,7 +93,7 @@ function MarathonAX4Game:advanceOneFrame() return true end -function MarathonAX4Game:onLineClear(cleared_row_count) +function SurvivalAXGame: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 +106,11 @@ function MarathonAX4Game:onLineClear(cleared_row_count) end end -function MarathonAX4Game:getSectionTime() +function SurvivalAXGame:getSectionTime() return self.frames - self.section_start_time end -function MarathonAX4Game:updateSectionTimes(old_lines, new_lines) +function SurvivalAXGame: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 +119,23 @@ function MarathonAX4Game:updateSectionTimes(old_lines, new_lines) end end -function MarathonAX4Game:onPieceEnter() +function SurvivalAXGame:onPieceEnter() self.section_clear = false end -function MarathonAX4Game:drawGrid(ruleset) +function SurvivalAXGame:drawGrid(ruleset) self.grid:draw() end -function MarathonAX4Game:getHighscoreData() +function SurvivalAXGame:getHighscoreData() return { lines = self.lines, frames = self.frames, } end -function MarathonAX4Game:drawScoringInfo() - MarathonAX4Game.super.drawScoringInfo(self) +function SurvivalAXGame:drawScoringInfo() + SurvivalAXGame.super.drawScoringInfo(self) love.graphics.setColor(1, 1, 1, 1) @@ -165,12 +165,12 @@ function MarathonAX4Game:drawScoringInfo() love.graphics.setColor(1, 1, 1, 1) end -function MarathonAX4Game:getSectionEndLines() +function SurvivalAXGame:getSectionEndLines() return math.floor(self.lines / 10 + 1) * 10 end -function MarathonAX4Game:getBackground() +function SurvivalAXGame:getBackground() return math.floor(self.lines / 10) end -return MarathonAX4Game +return SurvivalAXGame diff --git a/tetris/rulesets/arika.lua b/tetris/rulesets/arika.lua index 5441eca..f5a5759 100644 --- a/tetris/rulesets/arika.lua +++ b/tetris/rulesets/arika.lua @@ -110,13 +110,7 @@ function ARS:onPieceDrop(piece, grid) piece.lock_delay = 0 -- step reset end -function ARS:get180RotationValue() - if config.gamesettings.world_reverse == 3 then - return 1 - else - return 3 - end -end +function ARS:get180RotationValue() return 3 end function ARS:getDefaultOrientation() return 3 end -- downward facing pieces by default diff --git a/tetris/rulesets/arika_ace.lua b/tetris/rulesets/arika_ace.lua index eb8b5d3..6b201be 100755 --- a/tetris/rulesets/arika_ace.lua +++ b/tetris/rulesets/arika_ace.lua @@ -49,4 +49,8 @@ function ARS:onPieceRotate(piece, grid) end end +function ARS:get180RotationValue() return 3 end + +function ARS:getDefaultOrientation() return 3 end -- downward facing pieces by default + return ARS diff --git a/tetris/rulesets/arika_ace2.lua b/tetris/rulesets/arika_ace2.lua index 6316237..525ee1b 100644 --- a/tetris/rulesets/arika_ace2.lua +++ b/tetris/rulesets/arika_ace2.lua @@ -36,4 +36,8 @@ function ARS:onPieceRotate(piece, grid) end end +function ARS:get180RotationValue() return 3 end + +function ARS:getDefaultOrientation() return 3 end -- downward facing pieces by default + return ARS diff --git a/tetris/rulesets/arika_srs.lua b/tetris/rulesets/arika_srs.lua index 82c89d6..88b9b6e 100755 --- a/tetris/rulesets/arika_srs.lua +++ b/tetris/rulesets/arika_srs.lua @@ -4,7 +4,19 @@ local Ruleset = require 'tetris.rulesets.ti_srs' local SRS = Ruleset:extend() SRS.name = "ACE-SRS" -SRS.hash = "ACE Standard" +SRS.hash = "StandardACE" +SRS.world = true +SRS.colourscheme = { + I = "C", + L = "O", + J = "B", + S = "G", + Z = "R", + O = "Y", + T = "M", +} +SRS.softdrop_lock = false +SRS.harddrop_lock = true SRS.MANIPULATIONS_MAX = 128 diff --git a/tetris/rulesets/arika_ti.lua b/tetris/rulesets/arika_ti.lua index 9121266..9210a55 100644 --- a/tetris/rulesets/arika_ti.lua +++ b/tetris/rulesets/arika_ti.lua @@ -99,4 +99,8 @@ function ARS:onPieceRotate(piece, grid) end end +function ARS:get180RotationValue() return 3 end + +function ARS:getDefaultOrientation() return 3 end -- downward facing pieces by default + return ARS diff --git a/tetris/rulesets/cambridge.lua b/tetris/rulesets/cambridge.lua index 54983eb..27e3739 100644 --- a/tetris/rulesets/cambridge.lua +++ b/tetris/rulesets/cambridge.lua @@ -364,9 +364,9 @@ function CRS:attemptRotate(new_inputs, piece, grid, initial) if rot_dir == 0 then return end - if self.world and config.gamesettings.world_reverse == 2 then - rot_dir = 4 - rot_dir - end + if config.gamesettings.world_reverse == 3 or (self.world and config.gamesettings.world_reverse == 2) then + rot_dir = 4 - rot_dir + end local new_piece = piece:withRelativeRotation(rot_dir) self:attemptWallkicks(piece, new_piece, rot_dir, grid) diff --git a/tetris/rulesets/standard.lua b/tetris/rulesets/standard.lua new file mode 100644 index 0000000..601f614 --- /dev/null +++ b/tetris/rulesets/standard.lua @@ -0,0 +1,206 @@ +local Piece = require 'tetris.components.piece' +local Ruleset = require 'tetris.rulesets.ruleset' + +local SRS = Ruleset:extend() + +SRS.name = "Guideline SRS" +SRS.hash = "Standard" +SRS.world = true +SRS.colourscheme = { + I = "C", + L = "O", + J = "B", + S = "G", + Z = "R", + O = "Y", + T = "M", +} +SRS.softdrop_lock = false +SRS.harddrop_lock = true + +SRS.enable_IRS_wallkicks = true + +SRS.spawn_positions = { + I = { x=5, y=2 }, + J = { x=4, y=3 }, + L = { x=4, y=3 }, + O = { x=5, y=3 }, + S = { x=4, y=3 }, + T = { x=4, y=3 }, + Z = { x=4, y=3 }, +} + +SRS.big_spawn_positions = { + I = { x=3, y=0 }, + J = { x=2, y=1 }, + L = { x=2, y=1 }, + O = { x=3, y=1 }, + S = { x=2, y=1 }, + T = { x=2, y=1 }, + Z = { x=2, y=1 }, +} + +SRS.block_offsets = { + I={ + { {x=0, y=0}, {x=-1, y=0}, {x=-2, y=0}, {x=1, y=0} }, + { {x=0, y=0}, {x=0, y=-1}, {x=0, y=1}, {x=0, y=2} }, + { {x=0, y=1}, {x=-1, y=1}, {x=-2, y=1}, {x=1, y=1} }, + { {x=-1, y=0}, {x=-1, y=-1}, {x=-1, y=1}, {x=-1, y=2} }, + }, + J={ + { {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=-1, y=-1} }, + { {x=0, y=0}, {x=0, y=-1}, {x=0, y=1} , {x=1, y=-1} }, + { {x=0, y=0}, {x=1, y=0}, {x=-1, y=0}, {x=1, y=1} }, + { {x=0, y=0}, {x=0, y=1}, {x=0, y=-1}, {x=-1, y=1} }, + }, + L={ + { {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=1, y=-1} }, + { {x=0, y=0}, {x=0, y=-1}, {x=0, y=1}, {x=1, y=1} }, + { {x=0, y=0}, {x=1, y=0}, {x=-1, y=0}, {x=-1, y=1} }, + { {x=0, y=0}, {x=0, y=1}, {x=0, y=-1}, {x=-1, y=-1} }, + }, + O={ + { {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} }, + { {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} }, + { {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} }, + { {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} }, + }, + S={ + { {x=1, y=-1}, {x=0, y=-1}, {x=0, y=0}, {x=-1, y=0} }, + { {x=1, y=1}, {x=1, y=0}, {x=0, y=0}, {x=0, y=-1} }, + { {x=-1, y=1}, {x=0, y=1}, {x=0, y=0}, {x=1, y=0} }, + { {x=-1, y=-1}, {x=-1, y=0}, {x=0, y=0}, {x=0, y=1} }, + }, + T={ + { {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=0, y=-1} }, + { {x=0, y=0}, {x=0, y=-1}, {x=0, y=1}, {x=1, y=0} }, + { {x=0, y=0}, {x=1, y=0}, {x=-1, y=0}, {x=0, y=1} }, + { {x=0, y=0}, {x=0, y=1}, {x=0, y=-1}, {x=-1, y=0} }, + }, + Z={ + { {x=-1, y=-1}, {x=0, y=-1}, {x=0, y=0}, {x=1, y=0} }, + { {x=1, y=-1}, {x=1, y=0}, {x=0, y=0}, {x=0, y=1} }, + { {x=1, y=1}, {x=0, y=1}, {x=0, y=0}, {x=-1, y=0} }, + { {x=-1, y=1}, {x=-1, y=0}, {x=0, y=0}, {x=0, y=-1} }, + } +} + +SRS.wallkicks_3x3 = { + [0]={ + [1]={{x=-1, y=0}, {x=-1, y=-1}, {x=0, y=2}, {x=-1, y=2}}, + [2]={{x=0, y=1}, {x=0, y=-1}}, + [3]={{x=1, y=0}, {x=1, y=-1}, {x=0, y=2}, {x=1, y=2}}, + }, + [1]={ + [0]={{x=1, y=0}, {x=1, y=1}, {x=0, y=-2}, {x=1, y=-2}}, + [2]={{x=1, y=0}, {x=1, y=1}, {x=0, y=-2}, {x=1, y=-2}}, + [3]={{x=0, y=1}, {x=0, y=-1}}, + }, + [2]={ + [0]={{x=0, y=1}, {x=0, y=-1}}, + [1]={{x=-1, y=0}, {x=-1, y=-1}, {x=0, y=2}, {x=-1, y=2}}, + [3]={{x=1, y=0}, {x=1, y=-1}, {x=0, y=2}, {x=1, y=2}}, + }, + [3]={ + [0]={{x=-1, y=0}, {x=-1, y=1}, {x=0, y=-2}, {x=-1, y=-2}}, + [1]={{x=0, y=1}, {x=0, y=-1}}, + [2]={{x=-1, y=0}, {x=-1, y=1}, {x=0, y=-2}, {x=-1, y=-2}}, + }, +}; + +SRS.wallkicks_line = { + [0]={ + [1]={{x=-2, y=0}, {x=1, y=0}, {x=-2, y=1}, {x=1, y=-2}}, + [2]={}, + [3]={{x=-1, y=0}, {x=2, y=0}, {x=-1, y=-2}, {x=2, y=1}}, + }, + [1]={ + [0]={{x=2, y=0}, {x=-1, y=0}, {x=2, y=-1}, {x=-1, y=2}}, + [2]={{x=-1, y=0}, {x=2, y=0}, {x=-1, y=-2}, {x=2, y=1}}, + [3]={{x=0, y=1}, {x=0, y=-1}, {x=0, y=2}, {x=0, y=-2}}, + }, + [2]={ + [0]={}, + [1]={{x=1, y=0}, {x=-2, y=0}, {x=1, y=2}, {x=-2, y=-1}}, + [3]={{x=2, y=0}, {x=-1, y=0}, {x=2, y=-1}, {x=-1, y=2}}, + }, + [3]={ + [0]={{x=1, y=0}, {x=-2, y=0}, {x=1, y=2}, {x=-2, y=-1}}, + [1]={{x=0, y=1}, {x=0, y=-1}, {x=0, y=2}, {x=0, y=-2}}, + [2]={{x=-2, y=0}, {x=1, y=0}, {x=-2, y=1}, {x=1, y=-2}}, + }, +}; + +function SRS:check_new_low(piece) + for _, block in pairs(piece:getBlockOffsets()) do + local y = piece.position.y + block.y + if y > piece.lowest_y then + piece.manipulations = 0 + piece.lowest_y = y + end + end +end + +-- Component functions. + +function SRS:attemptWallkicks(piece, new_piece, rot_dir, grid) + + local kicks + if piece.shape == "O" then + return + elseif piece.shape == "I" then + kicks = SRS.wallkicks_line[piece.rotation][new_piece.rotation] + else + kicks = SRS.wallkicks_3x3[piece.rotation][new_piece.rotation] + end + + assert(piece.rotation ~= new_piece.rotation) + + for idx, offset in pairs(kicks) do + kicked_piece = new_piece:withOffset(offset) + if grid:canPlacePiece(kicked_piece) then + piece:setRelativeRotation(rot_dir) + piece:setOffset(offset) + self:onPieceRotate(piece, grid) + return + end + end + +end + +function SRS:onPieceCreate(piece, grid) + piece.manipulations = 0 + piece.lowest_y = -math.huge +end + +function SRS:onPieceDrop(piece, grid) + self:check_new_low(piece) + if piece.manipulations >= 15 and piece:isDropBlocked(grid) then + piece.locked = true + else + piece.lock_delay = 0 -- step reset + end +end + +function SRS:onPieceMove(piece, grid) + piece.lock_delay = 0 -- move reset + if piece:isDropBlocked(grid) then + piece.manipulations = piece.manipulations + 1 + if piece.manipulations >= 15 then + piece.locked = true + end + end +end + +function SRS:onPieceRotate(piece, grid) + piece.lock_delay = 0 -- rotate reset + self:check_new_low(piece) + piece.manipulations = piece.manipulations + 1 + if piece:isDropBlocked(grid) then + if piece.manipulations >= 15 then + piece.locked = true + end + end +end + +return SRS diff --git a/tetris/rulesets/standard_exp.lua b/tetris/rulesets/standard_exp.lua index d285662..b73d56a 100755 --- a/tetris/rulesets/standard_exp.lua +++ b/tetris/rulesets/standard_exp.lua @@ -3,18 +3,32 @@ local Ruleset = require 'tetris.rulesets.arika_srs' local SRS = Ruleset:extend() -SRS.name = "Guideline SRS" -SRS.hash = "Standard" +SRS.name = "SRS-X" +SRS.hash = "StandardEXP" +SRS.world = true +SRS.colourscheme = { + I = "C", + L = "O", + J = "B", + S = "G", + Z = "R", + O = "Y", + T = "M", +} +SRS.softdrop_lock = false +SRS.harddrop_lock = true SRS.enable_IRS_wallkicks = true -SRS.MANIPULATIONS_MAX = 15 +SRS.MANIPULATIONS_MAX = 24 +SRS.ROTATIONS_MAX = 12 function SRS:checkNewLow(piece) for _, block in pairs(piece:getBlockOffsets()) do local y = piece.position.y + block.y if y > piece.lowest_y then piece.manipulations = 0 + piece.rotations = 0 piece.lowest_y = y end end @@ -72,6 +86,7 @@ end function SRS:onPieceCreate(piece, grid) piece.manipulations = 0 + piece.rotations = 0 piece.lowest_y = -math.huge end @@ -88,7 +103,7 @@ function SRS:onPieceMove(piece, grid) piece.lock_delay = 0 -- move reset if piece:isDropBlocked(grid) then piece.manipulations = piece.manipulations + 1 - if piece.manipulations >= self.MANIPULATIONS_MAX then + if piece.manipulations >= 24 then piece.locked = true end end @@ -97,8 +112,8 @@ end function SRS:onPieceRotate(piece, grid) piece.lock_delay = 0 -- rotate reset self:checkNewLow(piece) - piece.manipulations = piece.manipulations + 1 - if piece.manipulations >= self.MANIPULATIONS_MAX then + piece.rotations = piece.rotations + 1 + if piece.rotations >= self.ROTATIONS_MAX then piece:moveInGrid({ x = 0, y = 1 }, 1, grid) if piece:isDropBlocked(grid) then piece.locked = true diff --git a/tetris/rulesets/ti_srs.lua b/tetris/rulesets/ti_srs.lua index 8e38244..046d2bd 100644 --- a/tetris/rulesets/ti_srs.lua +++ b/tetris/rulesets/ti_srs.lua @@ -4,7 +4,7 @@ local Ruleset = require 'tetris.rulesets.ruleset' local SRS = Ruleset:extend() SRS.name = "Ti-World" -SRS.hash = "Bad I-kicks" +SRS.hash = "StandardTI" SRS.world = true SRS.colourscheme = { I = "C", @@ -192,12 +192,6 @@ function SRS:onPieceRotate(piece, grid) end end -function SRS:get180RotationValue() - if config.gamesettings.world_reverse == 1 then - return 1 - else - return 3 - end -end +function SRS:get180RotationValue() return 3 end return SRS