diff --git a/tetris/modes/beginner_a2.lua b/tetris/modes/beginner_a2.lua new file mode 100644 index 0000000..0f872d7 --- /dev/null +++ b/tetris/modes/beginner_a2.lua @@ -0,0 +1,175 @@ +require 'funcs' + +local GameMode = require 'tetris.modes.gamemode' + +local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls' + +local BeginnerA2Game = GameMode:extend() + +BeginnerA2Game.name = "Beginner A2" +BeginnerA2Game.hash = "BeginnerA2" +BeginnerA2Game.tagline = "How many points can you score in 300 levels?" + +function BeginnerA2Game:new() + self.super:new() + + self.roll_frames = 0 + self.combo = 1 + self.randomizer = History6RollsRandomizer() + self.piece_time = 0 + + self.SGnames = { + "9", "8", "7", "6", "5", "4", "3", "2", "1", + "S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9", + "GM" + } + + self.lock_drop = false + self.lock_hard_drop = false + self.enable_hold = false + self.next_queue_length = 1 +end + +function BeginnerA2Game:getARE() return 25 end +function BeginnerA2Game:getLineARE() return self:getARE() end +function BeginnerA2Game:getLineClearDelay() return 40 end +function BeginnerA2Game:getDasLimit() return 14 end +function BeginnerA2Game:getLockDelay() return 30 end + +function BeginnerA2Game:getGravity() + if self.level < 8 then return 4/256 + elseif self.level < 19 then return 5/256 + elseif self.level < 35 then return 6/256 + elseif self.level < 40 then return 8/256 + elseif self.level < 50 then return 10/256 + elseif self.level < 60 then return 12/256 + elseif self.level < 70 then return 16/256 + elseif self.level < 80 then return 32/256 + elseif self.level < 90 then return 48/256 + elseif self.level < 100 then return 64/256 + elseif self.level < 108 then return 4/256 + elseif self.level < 119 then return 5/256 + elseif self.level < 125 then return 6/256 + elseif self.level < 131 then return 8/256 + elseif self.level < 139 then return 12/256 + elseif self.level < 149 then return 32/256 + elseif self.level < 156 then return 48/256 + elseif self.level < 164 then return 80/256 + elseif self.level < 174 then return 112/256 + elseif self.level < 180 then return 128/256 + elseif self.level < 200 then return 144/256 + elseif self.level < 212 then return 16/256 + elseif self.level < 221 then return 48/256 + elseif self.level < 232 then return 80/256 + elseif self.level < 244 then return 112/256 + elseif self.level < 256 then return 144/256 + elseif self.level < 267 then return 176/256 + elseif self.level < 277 then return 192/256 + elseif self.level < 287 then return 208/256 + elseif self.level < 295 then return 224/256 + elseif self.level < 300 then return 240/256 + else return 20 end +end + +function BeginnerA2Game:advanceOneFrame() + if self.clear then + self.roll_frames = self.roll_frames + 1 + if self.roll_frames > 1800 then + self.completed = true + end + elseif self.ready_frames == 0 then + self.frames = self.frames + 1 + end + return true +end + +function BeginnerA2Game:onPieceEnter() + if self.level ~= 299 and not self.clear and self.frames ~= 0 then + self.level = self.level + 1 + end +end + +function BeginnerA2Game:updateScore(level, drop_bonus, cleared_lines) + if not self.clear then + if self.grid:checkForBravo(cleared_lines) then self.bravo = 4 else self.bravo = 1 end + if cleared_lines > 0 then + self.combo = self.combo + (cleared_lines - 1) * 2 + self.score = self.score + ( + ((math.ceil((level + cleared_lines) / 4) + drop_bonus) * + cleared_lines * self.combo * self.bravo + + math.ceil(math.min(300, level + cleared_lines) / 2) + + math.max(self:getLockDelay() - self.piece_time, 0) * 7) + * 6 + ) + else + self.combo = 1 + end + self.drop_bonus = 0 + self.piece_time = 0 + end +end + +function BeginnerA2Game:whilePieceActive() + self.piece_time = self.piece_time + 1 +end + +function BeginnerA2Game:onLineClear(cleared_row_count) + self.level = math.min(self.level + cleared_row_count, 300) + if self.level == 300 and not self.clear then + self.clear = true + self.score = self.score + ( + 1253 * math.ceil(math.max(0, 18000 - self.frames) / 60) + ) + end +end + +function BeginnerA2Game:drawGrid() + self.grid:draw() + if self.piece ~= nil and self.level < 100 then + self:drawGhostPiece(ruleset) + end +end + +function BeginnerA2Game:getHighscoreData() + return { + score = self.score, + level = self.level, + frames = self.frames, + } +end + +function BeginnerA2Game:getBackground() + return math.floor(self.level / 100) +end + +function BeginnerA2Game:drawScoringInfo() + love.graphics.setColor(1, 1, 1, 1) + + love.graphics.setFont(font_3x5_2) + love.graphics.print( + self.das.direction .. " " .. + self.das.frames .. " " .. + self.piece_time .. " " .. + strTrueValues(self.prev_inputs) + ) + love.graphics.printf("NEXT", 64, 40, 40, "left") + love.graphics.printf("SCORE", 240, 200, 40, "left") + love.graphics.printf("LEVEL", 240, 320, 40, "left") + local sg = self.grid:checkSecretGrade() + if sg >= 5 then + love.graphics.printf("SECRET GRADE", 240, 430, 180, "left") + end + + love.graphics.setFont(font_3x5_3) + love.graphics.printf(self.score, 240, 220, 90, "left") + love.graphics.printf(self.level, 240, 340, 40, "right") + love.graphics.printf(300, 240, 370, 40, "right") + if sg >= 5 then + love.graphics.printf(self.SGnames[sg], 240, 450, 180, "left") + end + + love.graphics.setFont(font_8x11) + love.graphics.printf(formatTime(self.frames), 64, 420, 160, "center") +end + +return BeginnerA2Game \ No newline at end of file diff --git a/tetris/modes/big_meme.lua b/tetris/modes/big_meme.lua new file mode 100644 index 0000000..bc6bdf4 --- /dev/null +++ b/tetris/modes/big_meme.lua @@ -0,0 +1,40 @@ +require 'funcs' + +local SurvivalA3Game = require 'tetris.modes.survival_a3' + +local BigMemeGame = SurvivalA3Game:extend() + +BigMemeGame.name = "Big Survival A3" +BigMemeGame.hash = "BigA3" +BigMemeGame.tagline = "The blocks are bigger and the speeds are faster!" + +function BigMemeGame:initialize(ruleset) + self.super:initialize(ruleset) + self.big_mode = true +end + +local cleared_row_levels = {1, 2, 4, 6} + +function BigMemeGame:onLineClear(cleared_row_count) + cleared_row_count = cleared_row_count / 2 + if not self.clear then + local new_level = self.level + cleared_row_levels[cleared_row_count] + self:updateSectionTimes(self.level, new_level) + if new_level >= 1300 or self:hitTorikan(self.level, new_level) then + self.clear = true + if new_level >= 1300 then + self.level = 1300 + self.grid:clear() + self.big_mode = true + self.roll_frames = -150 + else + self.game_over = true + end + else + self.level = math.min(new_level, 1300) + end + self:advanceBottomRow(-cleared_row_count) + end +end + +return BigMemeGame \ No newline at end of file diff --git a/tetris/modes/credits_a3.lua b/tetris/modes/credits_a3.lua new file mode 100644 index 0000000..a7b601d --- /dev/null +++ b/tetris/modes/credits_a3.lua @@ -0,0 +1,119 @@ +require 'funcs' + +local IntervalTrainingMode = require 'tetris.modes.interval_training' +local Piece = require 'tetris.components.piece' + +local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls' + +local CreditsA3Game = IntervalTrainingMode:extend() + +CreditsA3Game.name = "Credits A3" +CreditsA3Game.hash = "CreditsA3" +CreditsA3Game.tagline = "How consistently can you clear the Ti M-roll?" + +function CreditsA3Game:new() + CreditsA3Game.super:new() + self.section_time_limit = 3238 + self.norm = 0 + self.section = 0 +end + +function CreditsA3Game:advanceOneFrame(inputs, ruleset) + if self.frames == 0 then + switchBGM("credit_roll", "gm3") + end + if self.roll_frames > 0 then + self.roll_frames = self.roll_frames - 1 + if self.roll_frames == 0 then + -- reset + self.norm = 0 + self.frames = 0 + self.grid:clear() + switchBGM("credit_roll", "gm3") + self:initializeOrHold(inputs, ruleset) + else + return false + end + elseif self.ready_frames == 0 then + self.frames = self.frames + 1 + if self:getSectionTime() >= self.section_time_limit then + self.norm = self.norm + 16 + self.piece = nil + if self.norm >= 60 then + self.section = self.section + 1 + self.roll_frames = 150 + else + self.game_over = true + switchBGM(nil) + end + end + end + return true +end + +function CreditsA3Game:onPieceEnter() + -- do nothing +end + +function CreditsA3Game:onLineClear(cleared_row_count) + if not self.clear then + self.norm = self.norm + (cleared_row_count == 4 and 10 or cleared_row_count) + end +end + +CreditsA3Game.rollOpacityFunction = function(age) + if age > 4 then return 0 + else return 1 - age / 4 end +end + +function CreditsA3Game:drawGrid(ruleset) + if not self.game_over and self.roll_frames < 30 then + self.grid:drawInvisible(self.rollOpacityFunction) + else + self.grid:draw() + end +end + +function CreditsA3Game:drawScoringInfo() + love.graphics.setColor(1, 1, 1, 1) + + love.graphics.setFont(font_3x5_2) + love.graphics.print( + self.das.direction .. " " .. + self.das.frames .. " " .. + strTrueValues(self.prev_inputs) + ) + love.graphics.printf("NEXT", 64, 40, 40, "left") + love.graphics.printf("TIME LEFT", 240, 250, 80, "left") + love.graphics.printf("NORM", 240, 320, 40, "left") + + self:drawSectionTimesWithSplits(self.section) + + love.graphics.setFont(font_3x5_3) + -- draw time left, flash red if necessary + local time_left = self.section_time_limit - math.max(self:getSectionTime(), 0) + + if not self.game_over and not self.clear and time_left < frameTime(0,10) and time_left % 4 < 2 then + if self.norm >= 44 then + love.graphics.setColor(0.3, 1, 0.3, 1) -- flash green if goal has been cleared + else + love.graphics.setColor(1, 0.3, 0.3, 1) -- otherwise flash red + end + end + + love.graphics.printf(formatTime(time_left), 240, 270, 160, "left") + + love.graphics.setColor(1, 1, 1, 1) + love.graphics.printf(self.norm, 240, 340, 40, "right") + if self.game_over or self.roll_frames > 0 then + love.graphics.printf("60", 240, 370, 40, "right") + else + love.graphics.printf("44", 240, 370, 40, "right") + end +end + +function CreditsA3Game:getBackground() + return self.section +end + +return CreditsA3Game \ No newline at end of file diff --git a/tetris/modes/g_lock.lua b/tetris/modes/g_lock.lua index 6c6eda9..b67de99 100644 --- a/tetris/modes/g_lock.lua +++ b/tetris/modes/g_lock.lua @@ -206,7 +206,9 @@ function GLock:drawScoringInfo() love.graphics.setFont(font_3x5_3) if self:getGSection() == 11 then - if self.frames <= 18000 then love.graphics.printf("GM", 240, 140, 90, "left") + -- blame gizmo4487 for this time + -- he's a legend + if self.frames <= frameTime(5,19) then love.graphics.printf("GM", 240, 140, 90, "left") else love.graphics.printf("M", 240, 140, 90, "left") end else love.graphics.printf("G"..1+self:getGSection(), 240, 140, 90, "left") diff --git a/tetris/modes/kamui.lua b/tetris/modes/kamui.lua new file mode 100644 index 0000000..7605afc --- /dev/null +++ b/tetris/modes/kamui.lua @@ -0,0 +1,131 @@ +require 'funcs' + +local GameMode = require 'tetris.modes.gamemode' + +local Randomizer = require 'tetris.randomizers.kamui_sequence' + +local KamuiGame = GameMode:extend() + +KamuiGame.name = "Race A3" +KamuiGame.hash = "RaceA3" +KamuiGame.tagline = "How fast can you reach level 500?" + +function KamuiGame:new() + self.super:new() + + self.randomizer = Randomizer() + self.time_limit = 18000 + + self.section_start_time = 0 + self.section_times = { [0] = 0 } + + self.lock_drop = true + self.lock_hard_drop = true + self.enable_hold = true + self.next_queue_length = 3 +end + +function KamuiGame:getARE() + if self.level < 100 then return 16 + elseif self.level < 300 then return 12 + elseif self.level < 400 then return 6 + else return 5 end +end + +function KamuiGame:getLineARE() + if self.level < 100 then return 12 + elseif self.level < 400 then return 6 + else return 5 end +end + +function KamuiGame:getLineClearDelay() return self:getLineARE() end + +function KamuiGame:getDasLimit() + if self.level < 200 then return 10 + elseif self.level < 300 then return 9 + elseif self.level < 400 then return 8 + else return 6 end +end + +function KamuiGame:getLockDelay() + if self.level < 100 then return 30 + elseif self.level < 200 then return 26 + elseif self.level < 300 then return 22 + elseif self.level < 400 then return 18 + else return 15 end +end + +function KamuiGame:getGravity() return 20 end + +function KamuiGame:onPieceEnter() + if self.level % 100 ~= 99 and self.frames ~= 0 then + self.level = self.level + 1 + end +end + +function KamuiGame:onLineClear(cleared_row_count) + local cleared_row_levels = {1, 2, 4, 6} + local new_level = self.level + cleared_row_levels[cleared_row_count] + self:updateSectionTimes(self.level, new_level) + self.level = math.min(500, new_level) + self.game_over = self.level == 500 +end + +function KamuiGame:updateSectionTimes(old_level, new_level) + if math.floor(old_level / 100) < math.floor(new_level / 100) then + -- record new section + table.insert(self.section_times, self.frames - self.section_start_time) + self.section_start_time = self.frames + end +end + +function KamuiGame:advanceOneFrame() + if self.ready_frames == 0 then + self.frames = self.frames + 1 + self.time_limit = self.time_limit - 1 + end + return true +end + +function KamuiGame:drawGrid() self.grid:draw() end + +function KamuiGame:drawScoringInfo() + love.graphics.setColor(1, 1, 1, 1) + + love.graphics.setFont(font_3x5_2) + love.graphics.print( + self.das.direction .. " " .. + self.das.frames .. " " .. + strTrueValues(self.prev_inputs) + ) + love.graphics.printf("NEXT", 64, 40, 40, "left") + love.graphics.printf("TIME LEFT", 240, 250, 80, "left") + love.graphics.printf("LEVEL", 240, 320, 40, "left") + + local current_section = math.floor(self.level / 100) + 1 + self:drawSectionTimesWithSplits(current_section) + + love.graphics.setFont(font_3x5_3) + love.graphics.printf(self.level, 240, 340, 40, "right") + love.graphics.printf(self.level == 500 and self.level or (math.floor(self.level / 100) + 1) * 100, 240, 370, 40, "right") + + -- draw time left, flash red if necessary + if not self.game_over and self.time_limit < frameTime(0,10) and time_left % 4 < 2 then + love.graphics.setColor(1, 0.3, 0.3, 1) + end + love.graphics.printf(formatTime(self.time_limit), 240, 270, 160, "left") + love.graphics.setColor(1, 1, 1, 1) +end + +function KamuiGame:getBackground() + return math.floor(self.level / 100) +end + +function KamuiGame:getHighscoreData() + return { + level = self.level, + frames = self.frames, + } +end + +return KamuiGame \ No newline at end of file diff --git a/tetris/modes/marathon_c88.lua b/tetris/modes/marathon_c88.lua index f02ae85..c063b19 100644 --- a/tetris/modes/marathon_c88.lua +++ b/tetris/modes/marathon_c88.lua @@ -27,6 +27,7 @@ function MarathonC88Game:new() self.enable_hold = false self.next_queue_length = 1 + self.irs = false self.grid:applyCeiling(4) end diff --git a/tetris/modes/marathon_wcb.lua b/tetris/modes/marathon_wcb.lua new file mode 100644 index 0000000..58b578e --- /dev/null +++ b/tetris/modes/marathon_wcb.lua @@ -0,0 +1,135 @@ +require 'funcs' + +local GameMode = require 'tetris.modes.gamemode' +local Piece = require 'tetris.components.piece' + +local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls' + +local MarathonWCBGame = GameMode:extend() + +MarathonWCBGame.name = "Marathon WCB" +MarathonWCBGame.hash = "MarathonWCB" +MarathonWCBGame.tagline = "When all the pieces slip right to their destinations... can you keep up?" + + +function MarathonWCBGame:new() + MarathonWCBGame.super:new() + + self.pieces = 0 + self.randomizer = History6RollsRandomizer() + + self.lock_drop = true + self.lock_hard_drop = true + self.instant_hard_drop = true + self.instant_soft_drop = true + self.enable_hold = false + self.next_queue_length = 3 + + self.piece_is_active = false +end + +function MarathonWCBGame:getDropSpeed() + return 20 +end + +function MarathonWCBGame:getARR() + return 0 +end + +function MarathonWCBGame:getARE() + return 0 +end + +function MarathonWCBGame:getLineARE() + return 0 +end + +function MarathonWCBGame:getDasLimit() + return 0 +end + +function MarathonWCBGame:getLineClearDelay() + return 0 +end + +function MarathonWCBGame:getLockDelay() + return math.huge +end + +function MarathonWCBGame:getGravity() + return self.piece_is_active and 20 or 0 +end + +function MarathonWCBGame:advanceOneFrame() + if self.ready_frames == 0 then + self.frames = self.frames + 1 + end + return true +end + +function MarathonWCBGame:onAttemptPieceMove() + if self.piece ~= nil then + -- don't let the piece move before it's finished dropping + self.piece:dropToBottom(self.grid) + end + self.piece_is_active = true +end + +function MarathonWCBGame:onAttemptPieceRotate() + self.piece_is_active = true +end + +function MarathonWCBGame:onPieceLock() + self.super:onPieceLock() + self.piece_is_active = false + self.pieces = self.pieces + 1 +end + +function MarathonWCBGame:onLineClear(cleared_row_count) + self.lines = self.lines + cleared_row_count +end + +function MarathonWCBGame:drawGrid(ruleset) + self.grid:draw() + if self.piece ~= nil then + self:drawGhostPiece(ruleset) + end +end + +function MarathonWCBGame:getHighscoreData() + return { + pieces = self.pieces, + frames = self.frames, + } +end + +function MarathonWCBGame:drawScoringInfo() + MarathonWCBGame.super.drawScoringInfo(self) + love.graphics.setColor(1, 1, 1, 1) + + local text_x = config["side_next"] and 320 or 240 + + love.graphics.setFont(font_3x5_2) + love.graphics.printf("NEXT", 64, 40, 40, "left") + love.graphics.printf("lines", text_x, 160, 80, "left") + love.graphics.printf("pieces", text_x, 220, 80, "left") + love.graphics.printf("piece/sec", text_x, 280, 80, "left") + local sg = self.grid:checkSecretGrade() + if sg >= 5 then + love.graphics.printf("SECRET GRADE", 240, 430, 180, "left") + end + + love.graphics.setFont(font_3x5_3) + love.graphics.printf(self.lines, text_x, 180, 80, "left") + love.graphics.printf(self.pieces, text_x, 240, 80, "left") + love.graphics.printf(string.format("%.04f", self.pieces / math.max(1, self.frames) * 60), text_x, 300, 80, "left") + if sg >= 5 then + love.graphics.printf(self.SGnames[sg], 240, 450, 180, "left") + end +end + +function MarathonWCBGame:getBackground() + return (math.floor(self.pieces / 50) % 20) +end + +return MarathonWCBGame \ No newline at end of file diff --git a/tetris/modes/non.lua b/tetris/modes/non.lua new file mode 100644 index 0000000..ce1873a --- /dev/null +++ b/tetris/modes/non.lua @@ -0,0 +1,77 @@ +require 'funcs' + +local GameMode = require 'tetris.modes.gamemode' + +local Bag7NoSZOStartRandomizer = require 'tetris.randomizers.bag7noSZOstart' + +local NightOfNights = GameMode:extend() + +NightOfNights.name = "Night of Nights" +NightOfNights.hash = "NightOfNights" +NightOfNights.tagline = "The pieces lock down super fast! How long can you survive?" + +function NightOfNights:new() + self.super:new() + + self.active_time = 0 + + self.randomizer = Bag7NoSZOStartRandomizer() + + self.lock_drop = true + self.lock_hard_drop = true + self.enable_hold = true + self.next_queue_length = 3 +end + +function NightOfNights:getARE() return 0 end +function NightOfNights:getLineARE() return 0 end +function NightOfNights:getLineClearDelay() return 0 end +function NightOfNights:getDasLimit() return 6 end +function NightOfNights:getGravity() return 20 end + +function NightOfNights:whilePieceActive() + self.active_time = self.active_time + 1 + if self.active_time >= 20 then self.piece.locked = true end +end + +function NightOfNights:onPieceLock(piece, cleared_row_count) + self.super:onPieceLock() + self.active_time = 0 + self.lines = self.lines + cleared_row_count +end + +function NightOfNights:drawGrid() + self.grid:draw() +end + +function NightOfNights:drawScoringInfo() + love.graphics.setColor(1, 1, 1, 1) + + love.graphics.setFont(font_3x5_2) + love.graphics.print( + self.das.direction .. " " .. + self.das.frames .. " " .. + strTrueValues(self.prev_inputs) + ) + love.graphics.printf("NEXT", 64, 40, 40, "left") + love.graphics.printf("LINES", 240, 200, 40, "left") + + love.graphics.setFont(font_3x5_4) + love.graphics.printf(self.lines, 240, 220, 90, "left") + + love.graphics.setFont(font_8x11) + love.graphics.printf(formatTime(self.frames), 64, 420, 160, "center") +end + +function NightOfNights:getBackground() + return 19 +end + +function NightOfNights:getHighscoreData() + return { + level = self.level, + frames = self.frames, + } +end + +return NightOfNights \ No newline at end of file diff --git a/tetris/modes/pro_tl.lua b/tetris/modes/pro_tl.lua new file mode 100644 index 0000000..48002c9 --- /dev/null +++ b/tetris/modes/pro_tl.lua @@ -0,0 +1,136 @@ +require 'funcs' + +local MarathonAX4Game = require 'tetris.modes.marathon_ax4' +local TetraRandomizer = require 'tetris.randomizers.tetra' + +local ProGame = MarathonAX4Game:extend() + +ProGame.name = "Final TL" +ProGame.hash = "ProTL" +ProGame.tagline = "Your next pieces start disappearing! What lies past Mach 9?" + +function ProGame:new() + self.super:new() + self.next_queue_length = 6 + self.randomizer = TetraRandomizer() +end + +function ProGame:getARE() return 6 end +function ProGame:getLineARE() return 6 end +function ProGame:getLineClearDelay() return 6 end +function ProGame:getDasLimit() return 6 end +function ProGame:getDropSpeed() return 20 end + +function ProGame:getGravity() + if self.lines < 20 then return 1 + elseif self.lines < 40 then return 3 + elseif self.lines < 60 then return 5 + else return 20 end +end + +function ProGame:getLockDelay() + if self.lines < 20 then return 30 + elseif self.lines < 40 then return 29 + elseif self.lines < 60 then return 27 + elseif self.lines < 80 then return 23 + elseif self.lines < 100 then return 21 + elseif self.lines < 120 then return 20 + elseif self.lines < 140 then return 18 + elseif self.lines < 160 then return 17 + elseif self.lines < 180 then return 15 + else return 14 end +end + +function ProGame:advanceOneFrame(inputs, ruleset) + if self.clear then + self.roll_frames = self.roll_frames + 1 + if self.roll_frames < 0 then + return false + elseif self.roll_frames > 2968 then + self.completed = true + end + elseif self.ready_frames == 0 then + if not self.section_clear then + self.frames = self.frames + 1 + end + if self:getSectionTime() >= self.section_time_limit then + self.game_over = true + end + end + + ruleset.onPieceMove = function() end + ruleset.onPieceRotate = function() end + + return true +end + +function ProGame:onLineClear(cleared_row_count) + if not self.clear then + local new_lines = self.lines + cleared_row_count + self:updateSectionTimes(self.lines, new_lines) + self.lines = math.min(new_lines, 200) + if self.lines == 200 then + self.grid:clear() + self.clear = true + self.roll_frames = -150 + else + self.enable_hold = self.lines < 160 + self.next_queue_length = math.max(1, 6 - math.floor(self.lines / 20)) + end + end +end + +function ProGame:updateSectionTimes(old_lines, new_lines) + if math.floor(old_lines / 20) < math.floor(new_lines / 20) then + -- record new section + table.insert(self.section_times, self:getSectionTime()) + self.section_start_time = self.frames + self.section_clear = true + end +end + +function ProGame:drawGrid() + self.grid:draw() + self:drawGhostPiece() +end + +function ProGame:drawScoringInfo() + MarathonAX4Game.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 .. " " .. + strTrueValues(self.prev_inputs) + ) + love.graphics.printf("NEXT", 64, 40, 40, "left") + if self.lines < 200 then love.graphics.printf("TIME LEFT", 240, 250, 80, "left") end + love.graphics.printf("LINES", 240, 320, 40, "left") + + local current_section = math.floor(self.lines / 20) + 1 + self:drawSectionTimesWithSplits(current_section) + + love.graphics.setFont(font_3x5_3) + love.graphics.printf(self.lines, 240, 340, 40, "right") + love.graphics.printf(self.clear and self.lines or self:getSectionEndLines(), 240, 370, 40, "right") + + -- draw time left, flash red if necessary + local time_left = self.section_time_limit - math.max(self:getSectionTime(), 0) + if not self.game_over and not self.clear and time_left < frameTime(0,10) and time_left % 4 < 2 then + love.graphics.setColor(1, 0.3, 0.3, 1) + end + if self.lines < 200 then love.graphics.printf(formatTime(time_left), 240, 270, 160, "left") end + love.graphics.setColor(1, 1, 1, 1) +end + +function ProGame:getSectionEndLines() + return math.floor(self.lines / 20 + 1) * 20 +end + +function ProGame:getBackground() + return math.floor(self.lines / 20) +end + +return ProGame \ No newline at end of file diff --git a/tetris/modes/race_40.lua b/tetris/modes/race_40.lua index c794a21..62039f8 100644 --- a/tetris/modes/race_40.lua +++ b/tetris/modes/race_40.lua @@ -58,7 +58,7 @@ function Race40Game:getLineARE() end function Race40Game:getDasLimit() - return 10 + return 8 end function Race40Game:getLineClearDelay() diff --git a/tetris/modes/speed.lua b/tetris/modes/speed.lua new file mode 100644 index 0000000..73e1416 --- /dev/null +++ b/tetris/modes/speed.lua @@ -0,0 +1,135 @@ +require 'funcs' + +local GameMode = require 'tetris.modes.gamemode' + +local Bag7Randomizer = require 'tetris.randomizers.bag7' + +local LudicrousSpeed = GameMode:extend() + +LudicrousSpeed.name = "Ludicrous Speed" +LudicrousSpeed.hash = "LSpeed" +LudicrousSpeed.tagline = "Don't make Keanu sad. Always stay above the speed limit." + +function LudicrousSpeed:new() + self.super:new() + + self.time_limit = 301 + self.pps = {} + self.pps_limit = 1 + self.pieces = 0 + + self.randomizer = Bag7Randomizer() + + self.lock_drop = true + self.lock_hard_drop = true + self.instant_soft_drop = false + self.instant_hard_drop = true + self.enable_hold = true + self.next_queue_length = 3 + + self.irs = false + self.ihs = false +end + +function LudicrousSpeed:getGravity() + if self.lines < 180 then + return (0.8 - (math.floor(self.lines / 10) * 0.007)) ^ -math.floor(self.lines / 10) / 60 + else return 20 end +end + +function LudicrousSpeed:getARE() return 0 end +function LudicrousSpeed:getLineARE() return 0 end +function LudicrousSpeed:getLineClearDelay() return 0 end +function LudicrousSpeed:getDasLimit() return 8 end +function LudicrousSpeed:getDropSpeed() return 20 end + +local function mean(t) + local sum = 0 + local count = 0 + + for k, v in pairs(t) do + if type(v) == 'number' then + sum = sum + v + count = count + 1 + end + end + + return (sum / count) +end + +function LudicrousSpeed:advanceOneFrame() + if self.ready_frames == 0 then + self.frames = self.frames + 1 + if mean(self.pps) * 2 < self.pps_limit then + self.time_limit = self.time_limit - 1 + self.game_over = self.time_limit <= 0 + else self.time_limit = 301 end + if self.frames % 30 == 0 and self.frames ~= 0 then + if table.getn(self.pps) == 30 then + table.remove(self.pps, 1) + table.insert(self.pps, self.pieces) + else table.insert(self.pps, self.pieces) end + self.pieces = 0 + --print(table.concat(self.pps, " ")) + end + end + return true +end + +function LudicrousSpeed:onPieceLock() + self.super:onPieceLock() + self.pieces = self.pieces + 1 +end + +function LudicrousSpeed:onLineClear(cleared_row_count) + self.lines = self.lines + cleared_row_count + self.pps_limit = 1 + math.floor(self.lines / 10) / 10 +end + +function LudicrousSpeed:drawGrid() + self.grid:draw() + self:drawGhostPiece() +end + +function LudicrousSpeed:drawScoringInfo() + love.graphics.setColor(1, 1, 1, 1) + + local text_x = config["side_next"] and 320 or 240 + + love.graphics.setFont(font_3x5_2) + love.graphics.printf("NEXT", 64, 40, 40, "left") + love.graphics.printf("LINES", text_x, 120, 80, "left") + love.graphics.printf("piece/sec", text_x, 180, 80, "left") + love.graphics.printf("REQUIRED PPS", text_x, 240, 120, "left") + + love.graphics.print( + self.das.direction .. " " .. + self.das.frames .. " " .. + strTrueValues(self.prev_inputs) .. + self.drop_bonus + ) + + love.graphics.setFont(font_3x5_4) + if self.time_limit <= 300 then love.graphics.printf(formatTime(self.time_limit), text_x, 320, 160, "left") end + + love.graphics.setFont(font_3x5_3) + love.graphics.printf(self.lines, text_x, 140, 80, "left") + love.graphics.printf(string.format("%.02f", self.frames > 30 and mean(self.pps) * 2 or 0), text_x, 200, 80, "left") + love.graphics.printf(string.format("%.02f", self.pps_limit), text_x, 260, 80, "left") + + love.graphics.setFont(font_8x11) + love.graphics.printf(formatTime(self.frames), 64, 420, 160, "center") +end + +function LudicrousSpeed:getBackground() + return math.floor(self.lines / 10) +end + +function LudicrousSpeed:getHighscoreData() + return { + lines = self.lines, + frames = self.frames, + } +end + +return LudicrousSpeed \ No newline at end of file diff --git a/tetris/modes/survival_c88.lua b/tetris/modes/survival_c88.lua new file mode 100644 index 0000000..b67a0d2 --- /dev/null +++ b/tetris/modes/survival_c88.lua @@ -0,0 +1,15 @@ +require 'funcs' + +local MarathonC88Game = require 'tetris.modes.marathon_c88' + +local SurvivalC88Game = MarathonC88Game:extend() + +SurvivalC88Game.name = "Survival C88" +SurvivalC88Game.hash = "Shimizu" +SurvivalC88Game.tagline = "You can't rotate the pieces initially! What will you do?" + +function SurvivalC88Game:getGravity() + return 20 +end + +return SurvivalC88Game \ No newline at end of file diff --git a/tetris/randomizers/fixed_sequence.lua b/tetris/randomizers/fixed_sequence.lua new file mode 100644 index 0000000..cbd8606 --- /dev/null +++ b/tetris/randomizers/fixed_sequence.lua @@ -0,0 +1,16 @@ +local Randomizer = require 'tetris.randomizers.randomizer' + +local Sequence = Randomizer:extend() + +function Sequence:initialize() + self.sequence = "IJLOT" + self.counter = 0 +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) + return piece +end + +return Sequence \ No newline at end of file diff --git a/tetris/randomizers/kamui_sequence.lua b/tetris/randomizers/kamui_sequence.lua new file mode 100644 index 0000000..4791447 --- /dev/null +++ b/tetris/randomizers/kamui_sequence.lua @@ -0,0 +1,10 @@ +local Sequence = require 'tetris.randomizers.fixed_sequence' + +local KamuiSequence = Sequence:extend() + +function KamuiSequence:initialize() + self.super:initialize() + self.sequence = "JISTZOSJITOZLISTJLOSZTJOILZJOITZJLISZTLJIOZLSTJZLSOTJLIZSTJOLISTZJOLTZSOJLZIOJSLZTJSIZOJTLSIJTLZSOJTZSLIOZSJLTZOISTZLOJTZILOSTIJOLSTIZLJOTZILSJZTOSIZJOLTISJZOLITSJZLTSOJLTZOJISTZLOSIZJTSIOZLSJIZTLJIZTLSJZILOTJZLOISZJLOTSJIZTOLSZJITSOZITSLJOTSLJOZITJOZLSTIOJSTLOIJTSOZILTSOJZILSOTZLSJTIZSJOTILZJOISLJZITLOZJTILSJTIZOJTIZOLJTSOZJTSILJZTSOIZJSTOIJLZTIJOLSISOJTLZOIJLTZOILSSJTOZILSOTZLSJIOLZJTSOIJZTSOJLISTZLIOJTZSSOLIZSTJOSLTIZSJLOTZISLJOTZLJOSTZJIOLSTIZOSLJZISOJLIZTOJILZOJILTOJISZOTJTIOSZTLJSZOITLZOJSILZJOILJZOISJTLOZSJIOLJSZTLISJOZLISOJZILOSTILOTJSLZITSOJITLZSJTIZOLJISTOJILZSTOILZSJOTISLOZJITOLZJSILOZSIJLZTSOJZLTIJSLTIZJLOSTJZLSIOTJZLOSOTSLIOZSTJOILSZTJLOZSTILZSOJITLZSIJTOLZSTJOZSLJIZSOJLZITJSLZOJTLSOIZJSOSLTZOSSTLZOIJSTOZILJSOSTIJLSOTJLSOTIJSZOLTSJZLOSJTZILOTSZLOITZSJITLZJIOSZTLIJOSLIZTOLIZTJLSZOTJLSOTZJIOSZTJTOSZIJTSOIZJLSOSILJTZOIJSTIJLTOSZJLOTSIZOLSTJIOJZTILSOJZTILJZSOSITLZSOTJIZOSLJTOSZLITSJLIZTOLSIZOJTSZOJTLZSJTOILZJTSLZJTISLZTJISOLZTSIOLJTZIOJSLTIJZSTLJIOTSLZJITLOJIZLZSOIJLZOTSSJZTIO" +end + +return KamuiSequence \ No newline at end of file diff --git a/tetris/randomizers/tetra.lua b/tetris/randomizers/tetra.lua new file mode 100644 index 0000000..7dcea7e --- /dev/null +++ b/tetris/randomizers/tetra.lua @@ -0,0 +1,72 @@ +-- A randomizer based on Tetra Legends' "Tetra-X" game mode +-- There are two main rules: +-- * if piece hasn't been generated for 13+ pieces, force it +-- * if piece has been generated in the last two pieces, don't give it out +-- +-- The official implementation also has self-balancing functionality (bias +-- towards pieces that have appeared less often). + +local Randomizer = require 'tetris.randomizers.randomizer' + +local TetraXRandomizer = Randomizer:extend() + +function TetraXRandomizer:initialize() + local pieces = {"I", "J", "L", "O", "S", "T", "Z"} + + self.count = {} + self.lastseen = {} + self.totalcount = 0 + self.pieceselection = pieces + + for _, piece in ipairs(pieces) do + self.count[piece] = 0 + self.lastseen[piece] = -1 -- use -1 as magic value for "not seen" + end + +end + +function TetraXRandomizer:generatePiece() + local generated = nil + while true do + generated = self.pieceselection[math.random(#self.pieceselection)] + if not (self.lastseen[generated] == 0 or self.lastseen[generated] == 1) then + break + end + end + + -- piece has not been generated in the last 13+ + for piece, val in pairs(self.lastseen) do + if val >= 13 then + generated = piece + break + end + end + + for piece, val in pairs(self.lastseen) do + if piece == generated then + self.lastseen[piece] = 0 + else + if val ~= -1 then + self.lastseen[piece] = val + 1 + end + end + end + + self.count[generated] = self.count[generated] + 1 + self.totalcount = self.totalcount + 1 + + -- shuffle the piece selection for the next time + self.pieceselection = {} + for piece, count in pairs(self.count) do + -- in this case 6 = #piece types - 1, so all probabilities sum to 1 + local probability = (self.totalcount - count) / (self.totalcount * 6) + local chances = math.floor(probability * 1000 + 0.5) -- simulated "round" + for _ = 1, chances do + table.insert(self.pieceselection, piece) + end + end + + return generated +end + +return TetraXRandomizer diff --git a/tetris/rulesets/bonkers.lua b/tetris/rulesets/bonkers.lua new file mode 100644 index 0000000..8b728b6 --- /dev/null +++ b/tetris/rulesets/bonkers.lua @@ -0,0 +1,147 @@ +local Ruleset = require "tetris.rulesets.ruleset" + +local BONKERS = Ruleset:extend() + +BONKERS.name = "B.O.N.K.E.R.S." +BONKERS.hash = "Bonkers" +BONKERS.world = true + +BONKERS.spawn_positions = { + I = { x=5, y=4 }, + J = { x=4, y=5 }, + L = { x=4, y=5 }, + O = { x=5, y=5 }, + S = { x=4, y=5 }, + T = { x=4, y=5 }, + Z = { x=4, y=5 }, +} + +BONKERS.colourscheme = { + I = "G", + J = "G", + L = "G", + O = "G", + S = "G", + T = "G", + Z = "G", +} + +BONKERS.softdrop_lock = false +BONKERS.harddrop_lock = true +BONKERS.enable_IRS_wallkicks = true + +BONKERS.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} }, + } +} + +-- Component functions. + +function BONKERS:attemptRotate(new_inputs, piece, grid, initial) + local rot_dir = 0 + + if (new_inputs["rotate_left"] or new_inputs["rotate_left2"]) then + rot_dir = 3 + elseif (new_inputs["rotate_right"] or new_inputs["rotate_right2"]) then + rot_dir = 1 + elseif (new_inputs["rotate_180"]) then + rot_dir = self:get180RotationValue() + end + + if rot_dir == 0 then return 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) + + if (grid:canPlacePiece(new_piece)) then + if piece:isDropBlocked(grid) then + self:attemptWallkicks(piece, new_piece, rot_dir, grid) + else + piece:setRelativeRotation(rot_dir) + self:onPieceRotate(piece, grid) + end + else + if not(initial and self.enable_IRS_wallkicks == false) then + self:attemptWallkicks(piece, new_piece, rot_dir, grid) + end + end +end + +function BONKERS:attemptWallkicks(piece, new_piece, rot_dir, grid) + + if piece.shape == "I" then + horizontal_kicks = {0, 1, -1, 2, -2} + else + horizontal_kicks = {0, 1, -1} + end + + for y_offset = 24, -24, -1 do + for idx, x_offset in pairs(horizontal_kicks) do + local offset = {x=x_offset, y=y_offset} + kicked_piece = new_piece:withOffset(offset) + if grid:canPlacePiece(kicked_piece) then + piece:setRelativeRotation(rot_dir) + piece:setOffset(offset) + piece.lock_delay = 0 -- rotate reset + return + end + end + end + +end + +function BONKERS:onPieceDrop(piece, grid) + piece.lock_delay = 0 -- step reset +end + +function BONKERS:onPieceMove(piece, grid) + piece.lock_delay = 0 -- move reset +end + +function BONKERS:onPieceRotate(piece, grid) + piece.lock_delay = 0 -- rotate reset +end + +return BONKERS diff --git a/tetris/rulesets/crap.lua b/tetris/rulesets/crap.lua index 1a3c331..e10f25d 100644 --- a/tetris/rulesets/crap.lua +++ b/tetris/rulesets/crap.lua @@ -115,8 +115,8 @@ function CRAP:attemptWallkicks(piece, new_piece, rot_dir, grid) dx=math.floor(math.random(11))-5 dy=math.floor(math.random(11))-5 if grid:canPlacePiece(new_piece:withOffset({x=dx, y=dy})) then - piece:setRelativeRotation(rot_dir):setOffset({x=dx, y=dy}) self:onPieceRotate(piece, grid) + piece:setRelativeRotation(rot_dir):setOffset({x=dx, y=dy}) return end end diff --git a/tetris/rulesets/cultris.lua b/tetris/rulesets/cultris.lua index 01a62a3..0330c6d 100644 --- a/tetris/rulesets/cultris.lua +++ b/tetris/rulesets/cultris.lua @@ -27,9 +27,9 @@ function Cultris:attemptWallkicks(piece, new_piece, rot_dir, grid) for idx, offset in pairs(kicks) do kicked_piece = new_piece:withOffset(offset) if grid:canPlacePiece(kicked_piece) then + self:onPieceRotate(piece, grid) piece:setRelativeRotation(rot_dir) piece:setOffset(offset) - self:onPieceRotate(piece, grid) return end end diff --git a/tetris/rulesets/dtet.lua b/tetris/rulesets/dtet.lua index 60549ce..b522100 100644 --- a/tetris/rulesets/dtet.lua +++ b/tetris/rulesets/dtet.lua @@ -32,22 +32,22 @@ DTET.big_spawn_positions = { DTET.block_offsets = { I={ - { {x=0, y=0}, {x=-1, y=0}, {x=-2, y=0}, {x=1, y=0} }, - { {x=0, y=-1}, {x=0, y=-2}, {x=0, y=0}, {x=0, y=1} }, { {x=0, y=0}, {x=-1, y=0}, {x=-2, y=0}, {x=1, y=0} }, { {x=-1, y=-1}, {x=-1, y=-2}, {x=-1, y=0}, {x=-1, y=1} }, + { {x=0, y=0}, {x=-1, y=0}, {x=-2, y=0}, {x=1, y=0} }, + { {x=0, y=-1}, {x=0, y=-2}, {x=0, y=0}, {x=0, y=1} }, }, J={ - { {x=0, y=-1}, {x=-1, y=-1}, {x=1, y=-1}, {x=1, y=0} }, - { {x=0, y=-1}, {x=0, y=-2}, {x=0, y=0}, {x=-1, y=0} }, { {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=-1, y=-1} }, { {x=0, y=-1}, {x=1, y=-2}, {x=0, y=-2}, {x=0, y=0} }, + { {x=0, y=-1}, {x=-1, y=-1}, {x=1, y=-1}, {x=1, y=0} }, + { {x=0, y=-1}, {x=0, y=-2}, {x=0, y=0}, {x=-1, y=0} }, }, L={ - { {x=0, y=-1}, {x=-1, y=-1}, {x=1, y=-1}, {x=-1, y=0} }, - { {x=0, y=-1}, {x=-1, y=-2}, {x=0, y=-2}, {x=0, y=0} }, { {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=1, y=-1} }, { {x=0, y=-2}, {x=0, y=-1}, {x=1, y=0}, {x=0, y=0} }, + { {x=0, y=-1}, {x=-1, y=-1}, {x=1, y=-1}, {x=-1, y=0} }, + { {x=0, y=-1}, {x=-1, y=-2}, {x=0, y=-2}, {x=0, y=0} }, }, O={ { {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} }, @@ -62,10 +62,10 @@ DTET.block_offsets = { { {x=1, y=0}, {x=1, y=-1}, {x=0, y=-1}, {x=0, y=-2} }, }, T={ - { {x=0, y=-1}, {x=-1, y=-1}, {x=1, y=-1}, {x=0, y=0} }, - { {x=0, y=-1}, {x=0, y=0}, {x=-1, y=-1}, {x=0, y=-2} }, { {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=0, y=-1} }, { {x=0, y=-1}, {x=0, y=0}, {x=1, y=-1}, {x=0, y=-2} }, + { {x=0, y=-1}, {x=-1, y=-1}, {x=1, y=-1}, {x=0, y=0} }, + { {x=0, y=-1}, {x=0, y=0}, {x=-1, y=-1}, {x=0, y=-2} }, }, Z={ { {x=1, y=0}, {x=0, y=0}, {x=0, y=-1}, {x=-1, y=-1} }, @@ -75,64 +75,18 @@ DTET.block_offsets = { } } --- clockwise kicks: {{x=1, y=0}, {x=-1, y=0}, {x=0, y=1}, {x=1, y=1}, {x=-1, y=1}}, --- counterclockwise kicks: {{x=-1, y=0}, {x=1, y=0}, {x=0, y=1}, {x=-1, y=1}, {x=1, y=1}}, - -DTET.wallkicks_3x3 = { - [0]={ - [1]={{x=1, y=0}, {x=-1, y=0}, {x=0, y=1}, {x=1, y=1}, {x=-1, y=1}}, - [2]={{x=-1, y=0}, {x=1, y=0}, {x=0, y=1}, {x=-1, y=1}, {x=1, y=1}}, - [3]={{x=-1, y=0}, {x=1, y=0}, {x=0, y=1}, {x=-1, y=1}, {x=1, y=1}}, - }, - [1]={ - [0]={{x=-1, y=0}, {x=1, y=0}, {x=0, y=1}, {x=-1, y=1}, {x=1, y=1}}, - [2]={{x=1, y=0}, {x=-1, y=0}, {x=0, y=1}, {x=1, y=1}, {x=-1, y=1}}, - [3]={{x=-1, y=0}, {x=1, y=0}, {x=0, y=1}, {x=-1, y=1}, {x=1, y=1}}, - }, - [2]={ - [0]={{x=-1, y=0}, {x=1, y=0}, {x=0, y=1}, {x=-1, y=1}, {x=1, y=1}}, - [1]={{x=-1, y=0}, {x=1, y=0}, {x=0, y=1}, {x=-1, y=1}, {x=1, y=1}}, - [3]={{x=1, y=0}, {x=-1, y=0}, {x=0, y=1}, {x=1, y=1}, {x=-1, y=1}}, - }, - [3]={ - [0]={{x=1, y=0}, {x=-1, y=0}, {x=0, y=1}, {x=1, y=1}, {x=-1, y=1}}, - [1]={{x=-1, y=0}, {x=1, y=0}, {x=0, y=1}, {x=-1, y=1}, {x=1, y=1}}, - [2]={{x=-1, y=0}, {x=1, y=0}, {x=0, y=1}, {x=-1, y=1}, {x=1, y=1}}, - }, -}; - -DTET.wallkicks_line = { - [0]={ - [1]={{x=1, y=0}, {x=-1, y=0}, {x=0, y=1}, {x=1, y=1}, {x=-1, y=1}}, - [2]={{x=-1, y=0}, {x=1, y=0}, {x=0, y=1}, {x=-1, y=1}, {x=1, y=1}}, - [3]={{x=-1, y=0}, {x=1, y=0}, {x=0, y=1}, {x=-1, y=1}, {x=1, y=1}}, - }, - [1]={ - [0]={{x=-1, y=0}, {x=1, y=0}, {x=0, y=1}, {x=-1, y=1}, {x=1, y=1}}, - [2]={{x=1, y=0}, {x=-1, y=0}, {x=0, y=1}, {x=1, y=1}, {x=-1, y=1}}, - [3]={{x=-1, y=0}, {x=1, y=0}, {x=0, y=1}, {x=-1, y=1}, {x=1, y=1}}, - }, - [2]={ - [0]={{x=-1, y=0}, {x=1, y=0}, {x=0, y=1}, {x=-1, y=1}, {x=1, y=1}}, - [1]={{x=1, y=0}, {x=-1, y=0}, {x=0, y=1}, {x=1, y=1}, {x=-1, y=1}}, - [3]={{x=-1, y=0}, {x=1, y=0}, {x=0, y=1}, {x=-1, y=1}, {x=1, y=1}}, - }, - [3]={ - [0]={{x=1, y=0}, {x=-1, y=0}, {x=0, y=1}, {x=1, y=1}, {x=-1, y=1}}, - [1]={{x=-1, y=0}, {x=1, y=0}, {x=0, y=1}, {x=-1, y=1}, {x=1, y=1}}, - [2]={{x=-1, y=0}, {x=1, y=0}, {x=0, y=1}, {x=-1, y=1}, {x=1, y=1}}, - }, -}; +DTET.wallkicks_cw = {{x=1, y=0}, {x=-1, y=0}, {x=0, y=1}, {x=1, y=1}, {x=-1, y=1}} +DTET.wallkicks_ccw = {{x=-1, y=0}, {x=1, y=0}, {x=0, y=1}, {x=-1, y=1}, {x=1, y=1}} function DTET:attemptWallkicks(piece, new_piece, rot_dir, grid) - + local kicks - if piece.shape == "O" then + if piece.shape == "O" then return - elseif piece.shape == "I" then - kicks = DTET.wallkicks_line[piece.rotation][new_piece.rotation] + elseif rot_dir == 1 then + kicks = DTET.wallkicks_cw else - kicks = DTET.wallkicks_3x3[piece.rotation][new_piece.rotation] + kicks = DTET.wallkicks_ccw end assert(piece.rotation ~= new_piece.rotation) @@ -140,18 +94,19 @@ function DTET:attemptWallkicks(piece, new_piece, rot_dir, grid) for idx, offset in pairs(kicks) do kicked_piece = new_piece:withOffset(offset) if grid:canPlacePiece(kicked_piece) then + self:onPieceRotate(piece, grid) piece:setRelativeRotation(rot_dir) piece:setOffset(offset) - self:onPieceRotate(piece, grid) return end end + end function DTET:onPieceDrop(piece, grid) piece.lock_delay = 0 -- step reset end -function DTET:getDefaultOrientation() return 1 end +function DTET:getDefaultOrientation() return 3 end return DTET \ No newline at end of file diff --git a/tetris/rulesets/eheart.lua b/tetris/rulesets/eheart.lua index 18397d4..f938d12 100644 --- a/tetris/rulesets/eheart.lua +++ b/tetris/rulesets/eheart.lua @@ -37,8 +37,8 @@ end function EHeart:lateralKick(dx, piece, new_piece, rot_dir, grid) if (grid:canPlacePiece(new_piece:withOffset({x=dx, y=0}))) then - piece:setRelativeRotation(rot_dir):setOffset({x=dx, y=0}) self:onPieceRotate(piece, grid) + piece:setRelativeRotation(rot_dir):setOffset({x=dx, y=0}) end end diff --git a/tetris/rulesets/pptprs.lua b/tetris/rulesets/pptprs.lua index f2e138c..8b00c85 100644 --- a/tetris/rulesets/pptprs.lua +++ b/tetris/rulesets/pptprs.lua @@ -90,9 +90,9 @@ function PPTPRS:attemptWallkicks(piece, new_piece, rot_dir, grid) for idx, offset in pairs(kicks) do kicked_piece = new_piece:withOffset(offset) if grid:canPlacePiece(kicked_piece) then + self:onPieceRotate(piece, grid) piece:setRelativeRotation(rot_dir) piece:setOffset(offset) - self:onPieceRotate(piece, grid) return end end diff --git a/tetris/rulesets/segaccw.lua b/tetris/rulesets/segaccw.lua new file mode 100644 index 0000000..2281598 --- /dev/null +++ b/tetris/rulesets/segaccw.lua @@ -0,0 +1,23 @@ +local ARS = require 'tetris.rulesets.arika' + +local Sega = ARS:extend() + +Sega.name = "Sega CCW" +Sega.hash = "SegaCCW" + +function Sega:attemptRotate(new_inputs, piece, grid) + local rot_dir = 0 + + if new_inputs["rotate_left"] or new_inputs["rotate_left2"] or + new_inputs["rotate_right"] or new_inputs["rotate_right2"] or + new_inputs["rotate_180"] then rot_dir = 3 end + + local new_piece = piece:withRelativeRotation(rot_dir) + + if (grid:canPlacePiece(new_piece)) then + self:onPieceRotate(piece, grid) + piece:setRelativeRotation(rot_dir) + end +end + +return Sega diff --git a/tetris/rulesets/srs_o_spin.lua b/tetris/rulesets/srs_o_spin.lua new file mode 100644 index 0000000..27d6d40 --- /dev/null +++ b/tetris/rulesets/srs_o_spin.lua @@ -0,0 +1,76 @@ +local SRS = require 'tetris.rulesets.standard_exp' + +local OSpin = SRS:extend() + +OSpin.name = "SRS O-Spin" +OSpin.hash = "OSpin" + +OSpin.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=-1, y=0}, {x=-2, y=0}, {x=-2, y=-1}, {x=-1, y=-1} }, + { {x=-1, y=-1}, {x=-2, y=-1}, {x=-2, y=-2}, {x=-1, y=-2} }, + { {x=0, y=-1}, {x=-1, y=-1}, {x=-1, y=-2}, {x=0, y=-2} }, + }, + 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} }, + } +} + +function OSpin:attemptWallkicks(piece, new_piece, rot_dir, grid) + + local kicks + if 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 + self:onPieceRotate(piece, grid) + piece:setRelativeRotation(rot_dir) + piece:setOffset(offset) + return + end + end + +end + +return OSpin \ No newline at end of file diff --git a/tetris/rulesets/srs_x.lua b/tetris/rulesets/srs_x.lua index d2f0c37..bdaa4a5 100644 --- a/tetris/rulesets/srs_x.lua +++ b/tetris/rulesets/srs_x.lua @@ -1,159 +1,23 @@ local Piece = require 'tetris.components.piece' -local Ruleset = require 'tetris.rulesets.ruleset' +local Ruleset = require 'tetris.rulesets.ti_srs' local SRS = Ruleset:extend() SRS.name = "SRS-X" SRS.hash = "Reversed SRS drop functions" -SRS.world = true +SRS.softdrop_lock = true +SRS.harddrop_lock = false -SRS.spawn_positions = { - I = { x=5, y=4 }, - J = { x=4, y=5 }, - L = { x=4, y=5 }, - O = { x=5, y=5 }, - S = { x=4, y=5 }, - T = { x=4, y=5 }, - Z = { x=4, y=5 }, +SRS.colourscheme = { + I = "R", + L = "O", + J = "B", + S = "M", + Z = "G", + O = "Y", + T = "C", } -SRS.big_spawn_positions = { - I = { x=3, y=2 }, - J = { x=2, y=3 }, - L = { x=2, y=3 }, - O = { x=3, y=3 }, - S = { x=2, y=3 }, - T = { x=2, y=3 }, - Z = { x=2, y=3 }, -} - -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= 1, y=-2}, {x=-2, y= 1}}, - [2]={}, - [3]={{x= 2, y= 0}, {x=-1, 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]={}, - }, - [2]={ - [0]={}, - [1]={{x=-2, y= 0}, {x= 1, y= 0}, {x=-2, y=-1}, {x= 1, y= 1}}, - [3]={{x= 2, y= 0}, {x=-1, y= 0}, {x= 2, y=-1}, {x=-1, y= 1}}, - }, - [3]={ - [0]={{x=-2, y= 0}, {x= 1, y= 0}, {x=-2, y=-1}, {x= 1, y= 2}}, - [1]={}, - [2]={{x= 1, y= 0}, {x=-2, y= 0}, {x= 1, y=-2}, {x=-2, y= 1}}, - }, -}; - --- 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.rotations = 0 -end - -function SRS:onPieceDrop(piece, grid) - piece.lock_delay = 0 -- step reset -end - function SRS:onPieceMove(piece, grid) piece.lock_delay = 0 -- move reset if piece:isDropBlocked(grid) then diff --git a/tetris/rulesets/tetra.lua b/tetris/rulesets/tetra.lua index c72ace4..eeaef5c 100644 --- a/tetris/rulesets/tetra.lua +++ b/tetris/rulesets/tetra.lua @@ -77,9 +77,9 @@ function Tetra:attemptWallkicks(piece, new_piece, rot_dir, grid) for idx, offset in pairs(kicks) do kicked_piece = new_piece:withOffset(offset) if grid:canPlacePiece(kicked_piece) then + self:onPieceRotate(piece, grid) piece:setRelativeRotation(rot_dir) piece:setOffset(offset) - self:onPieceRotate(piece, grid) return end end diff --git a/tetris/rulesets/thenext.lua b/tetris/rulesets/thenext.lua index e636bca..4d462f3 100644 --- a/tetris/rulesets/thenext.lua +++ b/tetris/rulesets/thenext.lua @@ -103,9 +103,9 @@ function TheNext:attemptWallkicks(piece, new_piece, rot_dir, grid) for idx, offset in pairs(kicks) do kicked_piece = new_piece:withOffset(offset) if grid:canPlacePiece(kicked_piece) then + self:onPieceRotate(piece, grid) piece:setRelativeRotation(rot_dir) piece:setOffset(offset) - self:onPieceRotate(piece, grid) return end end