From 514170e39c9c23cc4bb553de71b93bd51c20bb73 Mon Sep 17 00:00:00 2001 From: Ishaan Bhardwaj <59454579+SashLilac@users.noreply.github.com> Date: Wed, 11 Nov 2020 22:24:56 -0500 Subject: [PATCH] Add files via upload --- readme.txt | 3 + tetris/modes/4wide.lua | 39 ++++ tetris/modes/ck.lua | 329 +++++++++++++++++++++++++++ tetris/modes/demon_mode.lua | 269 ++++++++++++++++++++++ tetris/modes/interval_training.lua | 161 +++++++++++++ tetris/modes/konoha.lua | 192 ++++++++++++++++ tetris/modes/marathon_c88.lua | 150 ++++++++++++ tetris/modes/marathon_c89.lua | 186 +++++++++++++++ tetris/modes/phantom_mania_n.lua | 22 ++ tetris/modes/race_40.lua | 151 ++++++++++++ tetris/modes/scoredrain.lua | 189 +++++++++++++++ tetris/modes/tgmplus.lua | 214 +++++++++++++++++ tetris/randomizers/bag5.lua | 17 ++ tetris/randomizers/bag5alt.lua | 24 ++ tetris/randomizers/bag8.lua | 24 ++ tetris/randomizers/bag_konoha.lua | 28 +++ tetris/randomizers/recursive_bag.lua | 30 +++ tetris/randomizers/sega.lua | 19 ++ tetris/rulesets/crap.lua | 173 ++++++++++++++ tetris/rulesets/dtet.lua | 153 +++++++++++++ tetris/rulesets/eheart.lua | 45 ++++ tetris/rulesets/pptprs.lua | 102 +++++++++ tetris/rulesets/sega.lua | 10 + 23 files changed, 2530 insertions(+) create mode 100644 readme.txt create mode 100644 tetris/modes/4wide.lua create mode 100644 tetris/modes/ck.lua create mode 100644 tetris/modes/demon_mode.lua create mode 100644 tetris/modes/interval_training.lua create mode 100644 tetris/modes/konoha.lua create mode 100644 tetris/modes/marathon_c88.lua create mode 100644 tetris/modes/marathon_c89.lua create mode 100644 tetris/modes/phantom_mania_n.lua create mode 100644 tetris/modes/race_40.lua create mode 100644 tetris/modes/scoredrain.lua create mode 100644 tetris/modes/tgmplus.lua create mode 100644 tetris/randomizers/bag5.lua create mode 100644 tetris/randomizers/bag5alt.lua create mode 100644 tetris/randomizers/bag8.lua create mode 100644 tetris/randomizers/bag_konoha.lua create mode 100644 tetris/randomizers/recursive_bag.lua create mode 100644 tetris/randomizers/sega.lua create mode 100644 tetris/rulesets/crap.lua create mode 100644 tetris/rulesets/dtet.lua create mode 100644 tetris/rulesets/eheart.lua create mode 100644 tetris/rulesets/pptprs.lua create mode 100644 tetris/rulesets/sega.lua diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..a0c5758 --- /dev/null +++ b/readme.txt @@ -0,0 +1,3 @@ +Cambridge Modpack v1 + +To use, simply extract the zip to the root of your Cambridge folder. Enjoy! \ No newline at end of file diff --git a/tetris/modes/4wide.lua b/tetris/modes/4wide.lua new file mode 100644 index 0000000..3680d0b --- /dev/null +++ b/tetris/modes/4wide.lua @@ -0,0 +1,39 @@ +require 'funcs' + +local SurvivalA3Game = require 'tetris.modes.survival_a3' + +local FourWideGame = SurvivalA3Game:extend() + +FourWideGame.name = "4-wide Simulator" +FourWideGame.hash = "4wide" +FourWideGame.tagline = "The board has gotten narrower! Can you survive the increasing speeds?" + +function FourWideGame:initialize(ruleset) + self.super:initialize(ruleset) + self.grid:applyFourWide() +end + +local cleared_row_levels = {1, 2, 4, 6} + +function FourWideGame:onLineClear(cleared_row_count) + 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.roll_frames = -150 + else + self.game_over = true + end + else + self.level = math.min(new_level, 1300) + end + self:advanceBottomRow(-cleared_row_count) + end + self.grid:applyFourWide() +end + +return FourWideGame diff --git a/tetris/modes/ck.lua b/tetris/modes/ck.lua new file mode 100644 index 0000000..da755b8 --- /dev/null +++ b/tetris/modes/ck.lua @@ -0,0 +1,329 @@ +require 'funcs' + +local GameMode = require 'tetris.modes.gamemode' +local Piece = require 'tetris.components.piece' + +local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls_35bag' + +local SurvivalCKGame = GameMode:extend() + +SurvivalCKGame.name = "Survival CK" +SurvivalCKGame.hash = "SurvivalCK" +SurvivalCKGame.tagline = "An endurance mode created by CylinderKnot! Watch out for the fading pieces..." + +function SurvivalCKGame:new() + SurvivalCKGame.super:new() + + self.garbage = 0 + self.roll_frames = 0 + self.combo = 1 + self.grade = 0 + self.level = 0 + + self.randomizer = History6RollsRandomizer() + + self.lock_drop = true + self.lock_hard_drop = true + self.enable_hold = true + self.next_queue_length = 3 + + self.coolregret_timer = 0 +end + +function SurvivalCKGame:getARE() + if self.level < 100 then return 15 + elseif self.level < 200 then return 14 + elseif self.level < 300 then return 13 + elseif self.level < 400 then return 12 + elseif self.level < 500 then return 11 + elseif self.level < 600 then return 10 + elseif self.level < 700 then return 9 + elseif self.level < 800 then return 8 + elseif self.level < 900 then return 7 + elseif self.level < 1000 then return 6 + elseif self.level < 2500 then return 5 + else return 7 end +end + + +function SurvivalCKGame:getLineARE() + return SurvivalCKGame:getARE() +end + +function SurvivalCKGame:getDasLimit() + if self.level < 700 then return 10 + elseif self.level < 900 then return 9 + elseif self.level < 1100 then return 8 + elseif self.level < 1300 then return 7 + elseif self.level < 1600 then return 6 + else return 5 end +end + +function SurvivalCKGame:getLineClearDelay() + if self.level < 100 then return 10 + elseif self.level < 200 then return 8 + elseif self.level < 300 then return 7 + elseif self.level < 400 then return 6 + else return 5 end +end + +function SurvivalCKGame:getLockDelay() + if self.level < 600 then return 20 + elseif self.level < 700 then return 19 + elseif self.level < 800 then return 18 + elseif self.level < 900 then return 17 + elseif self.level < 1000 then return 16 + elseif self.level < 1200 then return 15 + elseif self.level < 1400 then return 14 + elseif self.level < 1700 then return 13 + elseif self.level < 2100 then return 12 + elseif self.level < 2200 then return 11 + elseif self.level < 2300 then return 10 + elseif self.level < 2400 then return 9 + elseif self.level < 2500 then return 8 + else return 15 end +end + +function SurvivalCKGame:getGravity() + return 20 +end + +function SurvivalCKGame:getGarbageLimit() + if self.level < 1000 then return 20 + elseif self.level < 1100 then return 17 + elseif self.level < 1200 then return 14 + elseif self.level < 1300 then return 11 + else return 8 end +end + +function SurvivalCKGame:getRegretTime() + if self.level < 500 then return frameTime(0,55) + elseif self.level < 1000 then return frameTime(0,50) + elseif self.level < 1500 then return frameTime(0,40) + elseif self.level < 2000 then return frameTime(0,35) + else return frameTime(0,30) end +end + +function SurvivalCKGame:getNextPiece(ruleset) + return { + skin = self.level >= 2000 and "bone" or "2tie", + shape = self.randomizer:nextPiece(), + orientation = ruleset:getDefaultOrientation(), + } +end + +local torikan_times = {300, 330, 360, 390, 420, 450, 478, 504, 528, 550, 570} + +function SurvivalCKGame:hitTorikan(old_level, new_level) + for i = 1, 11 do + if old_level < (900 + i * 100) and new_level >= (900 + i * 100) and self.frames > torikan_times[i] * 60 then + self.level = 900 + i * 100 + return true + end + end + return false +end + +function SurvivalCKGame:advanceOneFrame() + if self.clear then + self.roll_frames = self.roll_frames + 1 + if self.roll_frames < 0 then + if self.roll_frames + 1 == 0 then + switchBGM("credit_roll", "gm3") + return true + end + return false + elseif self.roll_frames > 3238 then + switchBGM(nil) + if self.grade ~= 20 then self.grade = self.grade + 1 end + self.completed = true + end + elseif self.ready_frames == 0 then + self.frames = self.frames + 1 + end + return true +end + +function SurvivalCKGame:onPieceEnter() + if (self.level % 100 ~= 99) and not self.clear and self.frames ~= 0 then + self.level = self.level + 1 + end +end + +function SurvivalCKGame:onLineClear(cleared_row_count) + if not self.clear then + local new_level = self.level + cleared_row_count * 2 + self:updateSectionTimes(self.level, new_level) + if new_level >= 2500 or self:hitTorikan(self.level, new_level) then + self.clear = true + if new_level >= 2500 then + self.level = 2500 + self.grid:clear() + self.big_mode = true + self.roll_frames = -150 + end + else + self.level = math.min(new_level, 2500) + end + self:advanceBottomRow(-cleared_row_count) + end +end + +function SurvivalCKGame:onPieceLock(piece, cleared_row_count) + self.super:onPieceLock() + if cleared_row_count == 0 then self:advanceBottomRow(1) end +end + +function SurvivalCKGame:updateScore(level, drop_bonus, cleared_lines) + if not self.clear then + 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 + ) + else + self.combo = 1 + end + self.drop_bonus = 0 + end +end + +function SurvivalCKGame:updateSectionTimes(old_level, new_level) + if math.floor(old_level / 100) < math.floor(new_level / 100) then + local section = math.floor(old_level / 100) + 1 + section_time = self.frames - self.section_start_time + table.insert(self.section_times, section_time) + self.section_start_time = self.frames + if section_time <= self:getRegretTime(self.level) then + self.grade = self.grade + 1 + else + self.coolregret_message = "REGRET!!" + self.coolregret_timer = 300 + end + end +end + +function SurvivalCKGame:advanceBottomRow(dx) + if self.level >= 1000 and self.level < 1500 then + self.garbage = math.max(self.garbage + dx, 0) + if self.garbage >= self:getGarbageLimit() then + self.grid:copyBottomRow() + self.garbage = 0 + end + end +end + +function SurvivalCKGame:drawGrid() + if self.level >= 1500 and self.level < 1600 then + self.grid:drawInvisible(self.rollOpacityFunction1) + elseif self.level >= 1600 and self.level < 1700 then + self.grid:drawInvisible(self.rollOpacityFunction2) + elseif self.level >= 1700 and self.level < 1800 then + self.grid:drawInvisible(self.rollOpacityFunction3) + elseif self.level >= 1800 and self.level < 1900 then + self.grid:drawInvisible(self.rollOpacityFunction4) + elseif self.level >= 1900 and self.level < 2000 then + self.grid:drawInvisible(self.rollOpacityFunction5) + else + self.grid:draw() + end +end + +-- screw trying to make this work efficiently +-- lua function variables are so garbage + +SurvivalCKGame.rollOpacityFunction1 = function(age) + if age < 420 then return 1 + elseif age > 480 then return 0 + else return 1 - (age - 420) / 60 end +end + +SurvivalCKGame.rollOpacityFunction2 = function(age) + if age < 360 then return 1 + elseif age > 420 then return 0 + else return 1 - (age - 360) / 60 end +end + +SurvivalCKGame.rollOpacityFunction3 = function(age) + if age < 300 then return 1 + elseif age > 360 then return 0 + else return 1 - (age - 300) / 60 end +end + +SurvivalCKGame.rollOpacityFunction4 = function(age) + if age < 240 then return 1 + elseif age > 300 then return 0 + else return 1 - (age - 240) / 60 end +end + +SurvivalCKGame.rollOpacityFunction5 = function(age) + if age < 180 then return 1 + elseif age > 240 then return 0 + else return 1 - (age - 180) / 60 end +end + +local master_grades = { "M", "MK", "MV", "MO", "MM" } + +function SurvivalCKGame:getLetterGrade() + if self.grade == 0 then + return "1" + elseif self.grade < 10 then + return "S" .. tostring(self.grade) + elseif self.grade < 21 then + return "m" .. tostring(self.grade - 9) + elseif self.grade < 26 then + return master_grades[self.grade - 20] + else + return "GM" + end +end + +function SurvivalCKGame:drawScoringInfo() + SurvivalCKGame.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("GRADE", text_x, 120, 40, "left") + love.graphics.printf("SCORE", text_x, 200, 40, "left") + love.graphics.printf("LEVEL", text_x, 320, 40, "left") + + if (self.coolregret_timer > 0) then + love.graphics.printf(self.coolregret_message, 64, 400, 160, "center") + self.coolregret_timer = self.coolregret_timer - 1 + end + + local current_section = math.floor(self.level / 100) + 1 + self:drawSectionTimesWithSplits(current_section) + + love.graphics.setFont(font_3x5_3) + love.graphics.printf(self:getLetterGrade(self.grade), text_x, 140, 90, "left") + love.graphics.printf(self.score, text_x, 220, 90, "left") + love.graphics.printf(self.level, text_x, 340, 50, "right") + if self.clear then + love.graphics.printf(self.level, text_x, 370, 50, "right") + else + love.graphics.printf(math.floor(self.level / 100 + 1) * 100, text_x, 370, 50, "right") + end +end + +function SurvivalCKGame:getHighscoreData() + return { + grade = self.grade, + level = self.level, + frames = self.frames, + } +end + +function SurvivalCKGame:getSectionEndLevel() + return math.floor(self.level / 100 + 1) * 100 +end + +function SurvivalCKGame:getBackground() + return math.min(math.floor(self.level / 100), 19) +end + +return SurvivalCKGame diff --git a/tetris/modes/demon_mode.lua b/tetris/modes/demon_mode.lua new file mode 100644 index 0000000..4e6a918 --- /dev/null +++ b/tetris/modes/demon_mode.lua @@ -0,0 +1,269 @@ +require 'funcs' + +local GameMode = require 'tetris.modes.gamemode' +local Piece = require 'tetris.components.piece' + +local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls' + +local DemonModeGame = GameMode:extend() + +DemonModeGame.name = "Demon Mode" +DemonModeGame.hash = "DemonMode" +DemonModeGame.tagline = "Can you handle the ludicrous speed past level 20?" + +function DemonModeGame:new() + DemonModeGame.super:new() + self.roll_frames = 0 + self.combo = 1 + self.randomizer = History6RollsRandomizer() + + self.grade = 0 + self.section_start_time = 0 + self.section_times = { [0] = 0 } + self.section_tetris_count = 0 + self.section_tries = 0 + + self.enable_hold = true + self.lock_drop = true + self.lock_hard_drop = true + self.next_queue_length = 3 + if math.random() < 1/6.66 then + self.rpc_details = "Suffering" + end +end + +function DemonModeGame:getARE() + if self.level < 500 then return 30 + elseif self.level < 600 then return 25 + elseif self.level < 700 then return 15 + elseif self.level < 800 then return 14 + elseif self.level < 900 then return 12 + elseif self.level < 1000 then return 11 + elseif self.level < 1100 then return 10 + elseif self.level < 1300 then return 8 + elseif self.level < 1400 then return 6 + elseif self.level < 1700 then return 4 + elseif self.level < 1800 then return 3 + elseif self.level < 1900 then return 2 + elseif self.level < 2000 then return 1 + else return 0 end +end + +function DemonModeGame:getLineARE() + return self:getARE() +end + +function DemonModeGame:getDasLimit() + if self.level < 500 then return 15 + elseif self.level < 1000 then return 10 + elseif self.level < 1500 then return 5 + elseif self.level < 1700 then return 4 + elseif self.level < 1900 then return 3 + elseif self.level < 2000 then return 2 + else return 1 end +end + +function DemonModeGame:getLineClearDelay() + if self.level < 600 then return 15 + elseif self.level < 800 then return 10 + elseif self.level < 1000 then return 8 + elseif self.level < 1500 then return 5 + elseif self.level < 1700 then return 3 + elseif self.level < 1900 then return 2 + elseif self.level < 2000 then return 1 + else return 0 end +end + +function DemonModeGame:getLockDelay() + if self.level < 100 then return 30 + elseif self.level < 200 then return 25 + elseif self.level < 300 then return 22 + elseif self.level < 400 then return 20 + elseif self.level < 1000 then return 15 + elseif self.level < 1200 then return 10 + elseif self.level < 1400 then return 9 + elseif self.level < 1500 then return 8 + elseif self.level < 1600 then return 7 + elseif self.level < 1700 then return 6 + elseif self.level < 1800 then return 5 + elseif self.level < 1900 then return 4 + elseif self.level < 2000 then return 3 + else return 2 end +end + +function DemonModeGame:getGravity() + return 20 +end + +local function getSectionForLevel(level) + return math.floor(level / 100) + 1 +end + +local cleared_row_levels = {1, 3, 6, 10} + +function DemonModeGame:advanceOneFrame() + if self.clear then + self.roll_frames = self.roll_frames + 1 + if self.roll_frames < 0 then + return false + elseif self.roll_frames >= 1337 then + self.completed = true + end + elseif self.ready_frames == 0 then + self.frames = self.frames + 1 + end +end + +function DemonModeGame:onPieceEnter() + if (self.level % 100 ~= 99) and self.frames ~= 0 then + self.level = self.level + 1 + end +end + +function DemonModeGame:onLineClear(cleared_row_count) + if cleared_row_count == 4 then + self.section_tetris_count = self.section_tetris_count + 1 + end + local advanced_levels = cleared_row_levels[cleared_row_count] + if not self.clear then + self:updateSectionTimes(self.level, self.level + advanced_levels) + end +end + +function DemonModeGame:updateSectionTimes(old_level, new_level) + local section = math.floor(old_level / 100) + 1 + if math.floor(old_level / 100) < math.floor(new_level / 100) then + -- If at least one Tetris in this section hasn't been made, + -- deny section passage. + if old_level > 500 then + if self.section_tetris_count == 0 then + self.level = 100 * math.floor(old_level / 100) + self.section_tries = self.section_tries + 1 + else + self.level = math.min(new_level, 2500) + -- if this is first try (no denials, add a grade) + if self.section_tries == 0 then + self.grade = self.grade + 1 + end + self.section_tries = 0 + self.section_tetris_count = 0 + -- record new section + section_time = self.frames - self.section_start_time + table.insert(self.section_times, section_time) + self.section_start_time = self.frames + -- maybe clear + if self.level == 2500 and not self.clear then + self.clear = true + self.grid:clear() + self.roll_frames = -150 + end + end + elseif old_level < 100 then + -- If section time is under cutoff, skip to level 500. + if self.frames < frameTime(1,00) then + self.level = 500 + self.grade = 5 + self.section_tries = 0 + self.section_tetris_count = 0 + else + self.level = math.min(new_level, 2500) + self.skip_failed = true + self.grade = self.grade + 1 + end + -- record new section + section_time = self.frames - self.section_start_time + table.insert(self.section_times, section_time) + self.section_start_time = self.frames + else + self.level = math.min(new_level, 2500) + if self.skip_failed and new_level >= 500 then + self.level = 500 + self.game_over = true + end + self.grade = math.min(self.grade + 1, 4) + end + else + self.level = math.min(new_level, 2500) + end +end + +function DemonModeGame:updateScore(level, drop_bonus, cleared_lines) + if not self.clear then + 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 + ) + else + self.combo = 1 + end + self.drop_bonus = 0 + end +end + +local letter_grades = { + [0] = "", "D", "C", "B", "A", + "S", "S-A", "S-B", "S-C", "S-D", + "X", "X-A", "X-B", "X-C", "X-D", + "W", "W-A", "W-B", "W-C", "W-D", + "Master", "MasterS", "MasterX", "MasterW", "Grand Master", + "Demon Master" +} + +function DemonModeGame:getLetterGrade() + return letter_grades[self.grade] +end + +function DemonModeGame:drawGrid() + if self.clear and not (self.completed or self.game_over) then + self.grid:drawInvisible(self.rollOpacityFunction) + else + self.grid:draw() + end +end + +DemonModeGame.rollOpacityFunction = function(age) + if age > 4 then return 0 + else return 1 - age / 4 end +end + +function DemonModeGame:drawScoringInfo() + DemonModeGame.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.grade ~= 0 then love.graphics.printf("GRADE", 240, 120, 40, "left") end + love.graphics.printf("SCORE", 240, 200, 40, "left") + love.graphics.printf("LEVEL", 240, 320, 40, "left") + + -- draw section time data + local current_section = getSectionForLevel(self.level) + self:drawSectionTimesWithSecondary(current_section) + + love.graphics.setFont(font_3x5_3) + love.graphics.printf(self.score, 240, 220, 90, "left") + love.graphics.printf(self:getLetterGrade(), 240, 140, 90, "left") + love.graphics.printf(string.format("%.2f", self.level / 100), 240, 340, 70, "right") +end + +function DemonModeGame:getHighscoreData() + return { + grade = self.grade, + level = self.level, + frames = self.frames, + } +end + +function DemonModeGame:getBackground() + return math.min(math.floor(self.level / 100), 19) +end + +return DemonModeGame diff --git a/tetris/modes/interval_training.lua b/tetris/modes/interval_training.lua new file mode 100644 index 0000000..740bb7e --- /dev/null +++ b/tetris/modes/interval_training.lua @@ -0,0 +1,161 @@ +require 'funcs' + +local GameMode = require 'tetris.modes.gamemode' +local Piece = require 'tetris.components.piece' + +local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls_35bag' + +local IntervalTrainingGame = GameMode:extend() + +IntervalTrainingGame.name = "Interval Training" +IntervalTrainingGame.hash = "IntervalTraining" +IntervalTrainingGame.tagline = "Can you clear the time hurdles when the game goes this fast?" + + + + +function IntervalTrainingGame:new() + self.level = 0 + IntervalTrainingGame.super:new() + self.roll_frames = 0 + self.combo = 1 + self.randomizer = History6RollsRandomizer() + + 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 IntervalTrainingGame:initialize(ruleset) + self.section_time_limit = 1800 + if ruleset.world then self.section_time_limit = 37 * 60 end + self.super.initialize(self, ruleset) +end + +function IntervalTrainingGame:getARE() + return 6 +end + +function IntervalTrainingGame:getLineARE() + return 6 +end + +function IntervalTrainingGame:getDasLimit() + return 7 +end + +function IntervalTrainingGame:getLineClearDelay() + return 4 +end + +function IntervalTrainingGame:getLockDelay() + return 15 +end + +function IntervalTrainingGame:getGravity() + return 20 +end + +function IntervalTrainingGame:getSection() + return math.floor(level / 100) + 1 +end + +function IntervalTrainingGame:advanceOneFrame() + if self.clear then + self.roll_frames = self.roll_frames + 1 + if self.roll_frames > 2968 then + self.completed = true + end + elseif self.ready_frames == 0 then + self.frames = self.frames + 1 + if self:getSectionTime() >= self.section_time_limit then + self.game_over = true + end + end + return true +end + +function IntervalTrainingGame:onPieceEnter() + if (self.level % 100 ~= 99 and self.level ~= 998) and not self.clear and self.frames ~= 0 then + self.level = self.level + 1 + end +end + +function IntervalTrainingGame:onLineClear(cleared_row_count) + local cleared_level_bonus = {1, 2, 4, 6} + if not self.clear then + local new_level = self.level + cleared_level_bonus[cleared_row_count] + self:updateSectionTimes(self.level, new_level) + self.level = math.min(new_level, 999) + if self.level == 999 then + self.clear = true + end + end +end + +function IntervalTrainingGame:getSectionTime() + return self.frames - self.section_start_time +end + +function IntervalTrainingGame: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:getSectionTime()) + self.section_start_time = self.frames + end +end + +function IntervalTrainingGame:drawGrid() + self.grid:draw() +end + +function IntervalTrainingGame:getHighscoreData() + return { + level = self.level, + frames = self.frames, + } +end + +function IntervalTrainingGame: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") + if not self.clear then love.graphics.printf("TIME LEFT", 240, 250, 80, "left") end + 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") + + -- 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 time_left < frameTime(0,10) and time_left % 4 < 2 then + love.graphics.setColor(1, 0.3, 0.3, 1) + end + if not self.clear then love.graphics.printf(formatTime(time_left), 240, 270, 160, "left") end + + love.graphics.setColor(1, 1, 1, 1) + love.graphics.printf(self:getSectionEndLevel(), 240, 370, 40, "right") +end + +function IntervalTrainingGame:getSectionEndLevel() + if self.level >= 900 then return 999 + else return math.floor(self.level / 100 + 1) * 100 end +end + +function IntervalTrainingGame:getBackground() + return math.floor(self.level / 100) +end + +return IntervalTrainingGame diff --git a/tetris/modes/konoha.lua b/tetris/modes/konoha.lua new file mode 100644 index 0000000..401cd38 --- /dev/null +++ b/tetris/modes/konoha.lua @@ -0,0 +1,192 @@ +require 'funcs' + +local GameMode = require 'tetris.modes.gamemode' +local Piece = require 'tetris.components.piece' + +local KonohaRandomizer = require 'tetris.randomizers.bag_konoha' + +local KonohaGame = GameMode:extend() + +KonohaGame.name = "All Clear A4" +KonohaGame.hash = "AllClearA4" +KonohaGame.tagline = "Get as many bravos as you can under the time limit!" + +function KonohaGame:new() + KonohaGame.super:new() + + self.randomizer = KonohaRandomizer() + self.bravos = 0 + self.last_bonus_amount = 0 + self.last_bonus_display_time = 0 + self.time_limit = 10800 + self.big_mode = true + + self.lock_drop = true + self.lock_hard_drop = true + self.enable_hold = true + self.next_queue_length = 3 +end + +function KonohaGame:getARE() + if self.level < 300 then return 30 + elseif self.level < 400 then return 25 + elseif self.level < 500 then return 20 + elseif self.level < 600 then return 17 + elseif self.level < 800 then return 15 + elseif self.level < 900 then return 13 + elseif self.level < 1000 then return 10 + elseif self.level < 1300 then return 8 + else return 6 end +end + +function KonohaGame:getLineARE() + return self:getARE() +end + +function KonohaGame:getDasLimit() + if self.level < 500 then return 10 + elseif self.level < 800 then return 9 + elseif self.level < 1000 then return 8 + else return 7 end +end + +function KonohaGame:getLineClearDelay() + if self.level < 200 then return 14 + elseif self.level < 500 then return 9 + elseif self.level < 800 then return 8 + elseif self.level < 1000 then return 7 + else return 6 end +end + +function KonohaGame:getLockDelay() + if self.level < 500 then return 30 + elseif self.level < 600 then return 25 + elseif self.level < 700 then return 23 + elseif self.level < 800 then return 20 + elseif self.level < 900 then return 17 + elseif self.level < 1000 then return 15 + elseif self.level < 1200 then return 13 + elseif self.level < 1300 then return 10 + else return 8 end +end + +function KonohaGame:getGravity() + if (self.level < 30) then return 4/256 + elseif (self.level < 35) then return 8/256 + elseif (self.level < 40) then return 12/256 + elseif (self.level < 50) then return 16/256 + elseif (self.level < 60) then return 32/256 + elseif (self.level < 70) then return 48/256 + elseif (self.level < 80) then return 64/256 + elseif (self.level < 90) then return 128/256 + elseif (self.level < 100) then return 192/256 + elseif (self.level < 120) then return 1 + elseif (self.level < 140) then return 2 + elseif (self.level < 160) then return 3 + elseif (self.level < 170) then return 4 + elseif (self.level < 200) then return 5 + else return 20 end +end + +function KonohaGame:getSection() + return math.floor(level / 100) + 1 +end + +function KonohaGame:getSectionEndLevel() + return math.floor(self.level / 100 + 1) * 100 +end + +function KonohaGame:advanceOneFrame() + if self.ready_frames == 0 then + self.time_limit = self.time_limit - 1 + self.frames = self.frames + 1 + end + if self.time_limit <= 0 then + self.game_over = true + end + self.last_bonus_display_time = self.last_bonus_display_time - 1 +end + +function KonohaGame:onPieceEnter() + if (self.level % 100 ~= 99) and self.frames ~= 0 then + self.level = self.level + 1 + end +end + +function KonohaGame:drawGrid(ruleset) + self.grid:draw() + if self.piece ~= nil and self.level < 100 then + self:drawGhostPiece(ruleset) + end +end + +local cleared_row_levels = {2, 4, 6, 12} +local bravo_bonus = {300, 480, 660, 900} +local non_bravo_bonus = {0, 0, 20, 40} +local bravo_ot_bonus = {0, 60, 120, 180} + +function KonohaGame:onLineClear(cleared_row_count) + local oldtime = self.time_limit + + self.level = self.level + cleared_row_levels[cleared_row_count / 2] + if self.grid:checkForBravo(cleared_row_count) then + self.bravos = self.bravos + 1 + if self.level < 1000 then self.time_limit = self.time_limit + bravo_bonus[cleared_row_count / 2] + else self.time_limit = self.time_limit + bravo_ot_bonus[cleared_row_count / 2] + end + if self.bravos == 11 then self.randomizer.allowrepeat = true end + elseif self.level < 1000 then + self.time_limit = self.time_limit + non_bravo_bonus[cleared_row_count / 2] + end + + local bonus = self.time_limit - oldtime + if bonus > 0 then + self.last_bonus_amount = bonus + self.last_bonus_display_time = 120 + end +end + +function KonohaGame:getBackground() + return math.floor(self.level / 100) +end + +function KonohaGame: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 LIMIT", 240, 120, 120, "left") + love.graphics.printf("BRAVOS", 240, 200, 50, "left") + love.graphics.printf("LEVEL", 240, 320, 40, "left") + + love.graphics.setFont(font_3x5_3) + if not self.game_over and self.time_limit < frameTime(0,10) and self.time_limit % 4 < 2 then + love.graphics.setColor(1, 0.3, 0.3, 1) + end + love.graphics.printf(formatTime(self.time_limit), 240, 140, 120, "right") + love.graphics.setColor(1, 1, 1, 1) + if self.last_bonus_display_time > 0 then + love.graphics.printf("+"..formatTime(self.last_bonus_amount), 240, 160, 120, "right") + end + love.graphics.printf(self.bravos, 240, 220, 90, "left") + love.graphics.printf(self.level, 240, 340, 50, "right") + love.graphics.printf(self:getSectionEndLevel(), 240, 370, 50, "right") + + love.graphics.setFont(font_8x11) + love.graphics.printf(formatTime(self.frames), 64, 420, 160, "center") +end + +function KonohaGame:getHighscoreData() + return { + bravos = self.bravos, + level = self.level, + frames = self.frames, + } +end + +return KonohaGame diff --git a/tetris/modes/marathon_c88.lua b/tetris/modes/marathon_c88.lua new file mode 100644 index 0000000..db027fb --- /dev/null +++ b/tetris/modes/marathon_c88.lua @@ -0,0 +1,150 @@ +require 'funcs' + +local GameMode = require 'tetris.modes.gamemode' +local Piece = require 'tetris.components.piece' + +local SegaRandomizer = require 'tetris.randomizers.sega' + +local MarathonC88Game = GameMode:extend() + +MarathonC88Game.name = "Marathon C88" +MarathonC88Game.hash = "MarathonC88" +MarathonC88Game.tagline = "An old Japanese game! Can you hit the max score?" + +function MarathonC88Game:new() + self.super:new() + + self.level_timer = 0 + self.level_lines = 0 + self.last_cleared = 0 + + self.randomizer = SegaRandomizer() + + self.lock_drop = false + self.enable_hard_drop = false + self.enable_hold = false + self.next_queue_length = 1 + + self.grid:applyCeiling(4) +end + +function MarathonC88Game:getARE() return 30 end +function MarathonC88Game:getLineARE() return 30 end +function MarathonC88Game:getDasLimit() return 20 end +function MarathonC88Game:getLineClearDelay() return 42 end +function MarathonC88Game:getLockDelay() return 30 end + +function MarathonC88Game:getGravity() + if self.level == 0 then return 1/30 + elseif self.level == 1 then return 1/15 + elseif self.level == 2 then return 1/12 + elseif self.level == 3 then return 1/10 + elseif self.level == 4 then return 1/8 + elseif self.level == 5 then return 1/6 + elseif self.level == 6 then return 1/4 + elseif self.level == 7 then return 1/2 + elseif self.level <= 9 then return 1 + elseif self.level == 10 then return 1/8 + elseif self.level == 11 then return 1/6 + elseif self.level == 12 then return 1/4 + elseif self.level == 13 then return 1/2 + else return 1 end +end + +function MarathonC88Game:getLevelTimerLimit() + if self.level == 0 then return 3480 + elseif self.level <= 8 then return 2320 + elseif self.level <= 10 then return 3480 + elseif self.level <= 14 then return 1740 + else return 3480 end +end + +function MarathonC88Game:getScoreMultiplier() + if self.level <= 1 then return 1 + elseif self.level <= 3 then return 2 + elseif self.level <= 5 then return 3 + elseif self.level <= 7 then return 4 + else return 5 end +end + +function MarathonC88Game:advanceOneFrame() + if not (self.piece == nil and self.level_timer == 0) and self.ready_frames == 0 then + self.level_timer = self.level_timer + 1 + end + if self.ready_frames == 0 then self.frames = self.frames + 1 end + if self.drop_bonus > 0 then + self.score = self.score + self.drop_bonus * self:getScoreMultiplier() + self.drop_bonus = 0 + end +end + +function MarathonC88Game:onPieceEnter() + for row = 5, 4 + self.last_cleared do self.grid:clearSpecificRow(row) end + self.grid:applyCeiling(4) +end + +local score_table = {[0] = 0, 1, 4, 9, 20} + +function MarathonC88Game:updateScore(level, drop_bonus, cleared_lines) + local bravo = self.grid:checkForBravo(cleared_lines) and 10 or 1 + self.score = self.score + score_table[cleared_lines] * 100 * self:getScoreMultiplier() * bravo + self.level_lines = self.level_lines + cleared_lines + self.lines = self.lines + cleared_lines + if (cleared_lines == 0 and self.level_timer >= self:getLevelTimerLimit()) or (self.level_lines >= 4) then + self.level = self.level + 1 + self.level_lines = 0 + self.level_timer = 0 + end + self.last_cleared = cleared_lines +end + +MarathonC88Game.opacityFunction = function(age) + return 1 +end + +MarathonC88Game.ceilingOpacityFunction = function(age) + return 0 +end + +function MarathonC88Game:drawGrid() + self.grid:drawInvisible(self.opacityFunction, self.ceilingOpacityFunction) +end + +function MarathonC88Game:drawScoringInfo() + MarathonC88Game.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") + love.graphics.printf("SCORE", 240, 120, 40, "left") + love.graphics.printf("LINES", 240, 200, 40, "left") + love.graphics.printf("LEVEL", 240, 280, 40, "left") + + love.graphics.setFont(font_3x5_3) + love.graphics.printf(self.score, 240, 140, 90, "left") + love.graphics.printf(self.lines, 240, 220, 90, "left") + love.graphics.printf(self.level, 240, 300, 90, "left") + + love.graphics.setFont(font_8x11) + love.graphics.printf(formatTime(self.frames), 64, 420, 160, "center") +end + +function MarathonC88Game:getBackground() + return math.min(math.floor(self.level / 2), 19) +end + +function MarathonC88Game:getHighscoreData() + return { + score = self.score, + level = self.level, + lines = self.lines, + frames = self.frames, + } +end + +return MarathonC88Game diff --git a/tetris/modes/marathon_c89.lua b/tetris/modes/marathon_c89.lua new file mode 100644 index 0000000..1c0e82b --- /dev/null +++ b/tetris/modes/marathon_c89.lua @@ -0,0 +1,186 @@ +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.super: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 .. " " .. + strTrueValues(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/phantom_mania_n.lua b/tetris/modes/phantom_mania_n.lua new file mode 100644 index 0000000..e39d03d --- /dev/null +++ b/tetris/modes/phantom_mania_n.lua @@ -0,0 +1,22 @@ +local PhantomManiaGame = require 'tetris.modes.phantom_mania' + +local PhantomManiaNGame = PhantomManiaGame:extend() + +PhantomManiaNGame.name = "Phantom Mania N" +PhantomManiaNGame.hash = "PhantomManiaN" +PhantomManiaNGame.tagline = "The old mode from Nullpomino, for Ti-ARS and SRS support." + +function PhantomManiaNGame:new() + PhantomManiaNGame.super:new() + + self.SGnames = { + "M1", "M2", "M3", "M4", "M5", "M6", "M7", "M8", "M9", + "M10", "M11", "M12", "M13", "M14", "M15", "M16", "M17", "M18", + "GM" + } + + self.next_queue_length = 3 + self.enable_hold = true +end + +return PhantomManiaNGame diff --git a/tetris/modes/race_40.lua b/tetris/modes/race_40.lua new file mode 100644 index 0000000..a192b6a --- /dev/null +++ b/tetris/modes/race_40.lua @@ -0,0 +1,151 @@ +require 'funcs' + +local GameMode = require 'tetris.modes.gamemode' +local Piece = require 'tetris.components.piece' + +local Bag7Randomiser = require 'tetris.randomizers.bag7noSZOstart' + +local Race40Game = GameMode:extend() + +Race40Game.name = "Race 40" +Race40Game.hash = "Race40" +Race40Game.tagline = "How fast can you clear 40 lines?" + + +function Race40Game:new() + Race40Game.super:new() + + self.lines = 0 + self.line_goal = 40 + self.pieces = 0 + self.randomizer = Bag7Randomiser() + + self.roll_frames = 0 + + self.SGnames = { + [0] = "", + "9", "8", "7", "6", "5", "4", "3", "2", "1", + "S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9", + "GM" + } + self.upstacked = false + + self.lock_drop = true + self.lock_hard_drop = true + self.instant_hard_drop = true + self.instant_soft_drop = false + self.enable_hold = true + self.next_queue_length = 3 +end + +function Race40Game:getDropSpeed() + return 20 +end + +function Race40Game:getARR() + return 1 +end + +function Race40Game:getARE() + return 0 +end + +function Race40Game:getLineARE() + return self:getARE() +end + +function Race40Game:getDasLimit() + return 10 +end + +function Race40Game:getLineClearDelay() + return 0 +end + +function Race40Game:getLockDelay() + return 30 +end + +function Race40Game:getGravity() + return 1/64 +end + +function Race40Game:advanceOneFrame() + if self.clear then + self.roll_frames = self.roll_frames + 1 + if self.roll_frames > 150 then + self.completed = true + end + return false + elseif self.ready_frames == 0 then + self.frames = self.frames + 1 + end + return true +end + +function Race40Game:onPieceLock() + self.super:onPieceLock() + self.pieces = self.pieces + 1 +end + +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 + self.clear = true + end + end +end + +function Race40Game:drawGrid(ruleset) + self.grid:draw() + if self.piece ~= nil then + self:drawGhostPiece(ruleset) + end +end + +function Race40Game:getHighscoreData() + return { + level = self.level, + frames = self.frames, + } +end + +function Race40Game:getSecretGrade(sg) + if sg == 19 then self.upstacked = true end + if self.upstacked then return self.SGnames[14 + math.floor((20 - sg) / 4)] + else return self.SGnames[math.floor((sg / 19) * 14)] end +end + +function Race40Game:drawScoringInfo() + Race40Game.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, 320, 40, "left") + love.graphics.printf("line/min", text_x, 160, 80, "left") + love.graphics.printf("piece/sec", text_x, 220, 80, "left") + local sg = self.grid:checkSecretGrade() + if sg >= 7 or self.upstacked then + love.graphics.printf("SECRET GRADE", 240, 430, 180, "left") + end + + love.graphics.setFont(font_3x5_3) + love.graphics.printf(string.format("%.02f", self.lines / math.max(1, self.frames) * 3600), text_x, 180, 80, "left") + love.graphics.printf(string.format("%.04f", self.pieces / math.max(1, self.frames) * 60), text_x, 240, 80, "left") + if sg >= 7 or self.upstacked then + love.graphics.printf(self:getSecretGrade(sg), 240, 450, 180, "left") + end + + love.graphics.setFont(font_3x5_4) + love.graphics.printf(math.max(0, self.line_goal - self.lines), text_x, 340, 40, "left") +end + +function Race40Game:getBackground() + return 2 +end + +return Race40Game diff --git a/tetris/modes/scoredrain.lua b/tetris/modes/scoredrain.lua new file mode 100644 index 0000000..fb8402b --- /dev/null +++ b/tetris/modes/scoredrain.lua @@ -0,0 +1,189 @@ +require 'funcs' + +local GameMode = require 'tetris.modes.gamemode' +local Piece = require 'tetris.components.piece' + +local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls_35bag' + +local ScoreDrainGame = GameMode:extend() + +ScoreDrainGame.name = "Score Drain" +ScoreDrainGame.hash = "ScoreDrain" +ScoreDrainGame.tagline = "Your score goes down over time! Avoid hitting 0 points, or your game is over!" + +function ScoreDrainGame:new() + self.super:new() + + self.score = 2500 + self.drain_rate = 50 + self.combo = 1 + self.randomizer = History6RollsRandomizer() + + self.lock_drop = true + self.lock_hard_drop = true + self.enable_hold = true + self.next_queue_length = 3 +end + +function ScoreDrainGame:getARE() + if self.level < 700 then return 27 + elseif self.level < 800 then return 18 + elseif self.level < 1000 then return 14 + elseif self.level < 1100 then return 8 + elseif self.level < 1200 then return 7 + else return 6 end +end + +function ScoreDrainGame:getLineARE() + if self.level < 600 then return 27 + elseif self.level < 700 then return 18 + elseif self.level < 800 then return 14 + elseif self.level < 1100 then return 8 + elseif self.level < 1200 then return 7 + else return 6 end +end + +function ScoreDrainGame:getDasLimit() + if self.level < 500 then return 15 + elseif self.level < 900 then return 9 + else return 7 end +end + +function ScoreDrainGame:getLineClearDelay() + if self.level < 500 then return 40 + elseif self.level < 600 then return 25 + elseif self.level < 700 then return 16 + elseif self.level < 800 then return 12 + elseif self.level < 1100 then return 6 + elseif self.level < 1200 then return 5 + else return 4 end +end + +function ScoreDrainGame:getLockDelay() + if self.level < 900 then return 30 + elseif self.level < 1100 then return 17 + else return 15 end +end + +function ScoreDrainGame:getGravity() + if (self.level < 30) then return 4/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 < 120) then return 80/256 + elseif (self.level < 140) then return 96/256 + elseif (self.level < 160) then return 112/256 + elseif (self.level < 170) then return 128/256 + elseif (self.level < 200) then return 144/256 + elseif (self.level < 220) then return 4/256 + elseif (self.level < 230) then return 32/256 + elseif (self.level < 233) then return 64/256 + elseif (self.level < 236) then return 96/256 + elseif (self.level < 239) then return 128/256 + elseif (self.level < 243) then return 160/256 + elseif (self.level < 247) then return 192/256 + elseif (self.level < 251) then return 224/256 + elseif (self.level < 300) then return 1 + elseif (self.level < 330) then return 2 + elseif (self.level < 360) then return 3 + elseif (self.level < 400) then return 4 + elseif (self.level < 420) then return 5 + elseif (self.level < 450) then return 4 + elseif (self.level < 500) then return 3 + else return 20 + end +end + +function ScoreDrainGame:advanceOneFrame() + if self.ready_frames == 0 then + self.frames = self.frames + 1 + self.score = math.max(0, self.score - self.drain_rate / 60) + self.game_over = self.score <= 0 and true or false + end + return true +end + +function ScoreDrainGame:onPieceEnter() + if (self.level % 100 ~= 99) and self.frames ~= 0 then + self.level = self.level + 1 + end +end + +local cleared_row_levels = {1, 2, 4, 6} + +function ScoreDrainGame:onLineClear(cleared_row_count) + local new_level = self.level + cleared_row_levels[cleared_row_count] + self.drain_rate = math.floor(self.level / 100) < math.floor(new_level / 100) and self.drain_rate * 1.5 or self.drain_rate + self.level = new_level +end + +function ScoreDrainGame:updateScore(level, drop_bonus, cleared_lines) + 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 + ) + else + self.combo = 1 + end + self.drop_bonus = 0 +end + +function ScoreDrainGame:drawGrid() + self.grid:draw() + if self.piece ~= nil and self.level < 100 then + self:drawGhostPiece(ruleset) + end +end + +function ScoreDrainGame: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("DRAIN RATE", 240, 90, 80, "left") + love.graphics.printf("SCORE", 240, 170, 40, "left") + love.graphics.printf("TIME LEFT", 240, 250, 80, "left") + love.graphics.printf("LEVEL", 240, 320, 40, "left") + + love.graphics.setFont(font_3x5_3) + love.graphics.printf(math.floor(self.drain_rate).."/s", 240, 110, 120, "left") + local frames_left = self.score / self.drain_rate * 60 + if frames_left <= 600 and frames_left % 4 < 2 and not self.game_over then love.graphics.setColor(1, 0.3, 0.3, 1) end + love.graphics.printf(formatTime(frames_left), 240, 270, 120, "left") + love.graphics.setColor(1, 1, 1, 1) + love.graphics.printf(math.floor(self.score), 240, 190, 90, "left") + love.graphics.printf(self.level, 240, 340, 50, "right") + love.graphics.printf(self:getSectionEndLevel(), 240, 370, 50, "right") + + love.graphics.setFont(font_8x11) + love.graphics.printf(formatTime(self.frames), 64, 420, 160, "center") +end + +function ScoreDrainGame:getHighscoreData() + return { + level = self.level, + frames = self.frames, + } +end + +function ScoreDrainGame:getSectionEndLevel() + return math.floor(self.level / 100 + 1) * 100 +end + +function ScoreDrainGame:getBackground() + return math.floor(self.level / 100) +end + +return ScoreDrainGame diff --git a/tetris/modes/tgmplus.lua b/tetris/modes/tgmplus.lua new file mode 100644 index 0000000..8c43136 --- /dev/null +++ b/tetris/modes/tgmplus.lua @@ -0,0 +1,214 @@ +require 'funcs' + +local GameMode = require 'tetris.modes.gamemode' +local Piece = require 'tetris.components.piece' + +local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls' + +local TGMPlusGame = GameMode:extend() + +TGMPlusGame.name = "Marathon A2+" +TGMPlusGame.hash = "A2Plus" +TGMPlusGame.tagline = "The garbage rises steadily! Can you make it to level 999?" + +function TGMPlusGame:new() + TGMPlusGame.super:new() + + self.roll_frames = 0 + self.combo = 1 + + self.SGnames = { + "9", "8", "7", "6", "5", "4", "3", "2", "1", + "S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9", + "GM" + } + + self.randomizer = History6RollsRandomizer() + + self.lock_drop = false + self.lock_hard_drop = false + self.enable_hold = false + self.next_queue_length = 1 + + self.garbage_queue = 0 + self.garbage_pos = 0 + self.garbage_rows = { + [0] = + {"e", "b", "b", "b", "b", "b", "b", "b", "b", "b"}, + {"e", "b", "b", "b", "b", "b", "b", "b", "b", "b"}, + {"e", "b", "b", "b", "b", "b", "b", "b", "b", "b"}, + {"e", "b", "b", "b", "b", "b", "b", "b", "b", "b"}, + {"b", "b", "b", "b", "b", "b", "b", "b", "b", "e"}, + {"b", "b", "b", "b", "b", "b", "b", "b", "b", "e"}, + {"b", "b", "b", "b", "b", "b", "b", "b", "b", "e"}, + {"b", "b", "b", "b", "b", "b", "b", "b", "b", "e"}, + {"e", "e", "b", "b", "b", "b", "b", "b", "b", "b"}, + {"e", "b", "b", "b", "b", "b", "b", "b", "b", "b"}, + {"e", "b", "b", "b", "b", "b", "b", "b", "b", "b"}, + {"b", "b", "b", "b", "b", "b", "b", "b", "e", "e"}, + {"b", "b", "b", "b", "b", "b", "b", "b", "b", "e"}, + {"b", "b", "b", "b", "b", "b", "b", "b", "b", "e"}, + {"b", "b", "e", "b", "b", "b", "b", "b", "b", "b"}, + {"b", "e", "e", "b", "b", "b", "b", "b", "b", "b"}, + {"b", "e", "b", "b", "b", "b", "b", "b", "b", "b"}, + {"b", "b", "b", "b", "b", "b", "b", "e", "b", "b"}, + {"b", "b", "b", "b", "b", "b", "b", "e", "e", "b"}, + {"b", "b", "b", "b", "b", "b", "b", "b", "e", "b"}, + {"b", "b", "b", "b", "e", "e", "b", "b", "b", "b"}, + {"b", "b", "b", "b", "e", "e", "b", "b", "b", "b"}, + {"b", "b", "b", "b", "e", "b", "b", "b", "b", "b"}, + {"b", "b", "b", "e", "e", "e", "b", "b", "b", "b"}, + } +end + +function TGMPlusGame:getARE() return 25 end +function TGMPlusGame:getDasLimit() return 15 end +function TGMPlusGame:getLockDelay() return 30 end +function TGMPlusGame:getLineClearDelay() return 40 end + +function TGMPlusGame:getGravity() + if (self.level < 30) then return 4/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 < 120) then return 80/256 + elseif (self.level < 140) then return 96/256 + elseif (self.level < 160) then return 112/256 + elseif (self.level < 170) then return 128/256 + elseif (self.level < 200) then return 144/256 + elseif (self.level < 220) then return 4/256 + elseif (self.level < 230) then return 32/256 + elseif (self.level < 233) then return 64/256 + elseif (self.level < 236) then return 96/256 + elseif (self.level < 239) then return 128/256 + elseif (self.level < 243) then return 160/256 + elseif (self.level < 247) then return 192/256 + elseif (self.level < 251) then return 224/256 + elseif (self.level < 300) then return 1 + elseif (self.level < 330) then return 2 + elseif (self.level < 360) then return 3 + elseif (self.level < 400) then return 4 + elseif (self.level < 420) then return 5 + elseif (self.level < 450) then return 4 + elseif (self.level < 500) then return 3 + else return 20 + end +end + +function TGMPlusGame:getGarbageLimit() return 13 - math.floor(self.level / 100) end + +function TGMPlusGame:advanceOneFrame() + if self.clear then + self.roll_frames = self.roll_frames + 1 + if self.roll_frames > 3694 then + self.completed = true + end + elseif self.ready_frames == 0 then + self.frames = self.frames + 1 + end + return true +end + +function TGMPlusGame:onPieceEnter() + if (self.level % 100 ~= 99 and self.level ~= 998) and not self.clear and self.frames ~= 0 then + self.level = self.level + 1 + end +end + +function TGMPlusGame:onPieceLock(piece, cleared_row_count) + self.super:onPieceLock() + if cleared_row_count == 0 then self:advanceBottomRow() end +end + +function TGMPlusGame:onLineClear(cleared_row_count) + self.level = math.min(self.level + cleared_row_count, 999) + if self.level == 999 and not self.clear then self.clear = true end + self.lock_drop = self.level >= 900 + self.lock_hard_drop = self.level >= 900 +end + +function TGMPlusGame:advanceBottomRow() + self.garbage_queue = self.garbage_queue + 1 + if self.garbage_queue >= self:getGarbageLimit() then + self.grid:garbageRise(self.garbage_rows[self.garbage_pos]) + self.garbage_queue = 0 + self.garbage_pos = (self.garbage_pos + 1) % 24 + end +end + +function TGMPlusGame: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 + ) + else + self.combo = 1 + end + self.drop_bonus = 0 + end +end + +function TGMPlusGame:drawGrid(ruleset) + self.grid:draw() + if self.piece ~= nil and self.level < 100 then + self:drawGhostPiece(ruleset) + end +end + +function TGMPlusGame:getHighscoreData() + return { + score = self.score, + level = self.level, + frames = self.frames, + } +end + +function TGMPlusGame:getSectionEndLevel() + if self.level >= 900 then return 999 + else return math.floor(self.level / 100 + 1) * 100 end +end + +function TGMPlusGame:getBackground() + return math.floor(self.level / 100) +end + +function TGMPlusGame: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("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(self:getSectionEndLevel(), 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 TGMPlusGame diff --git a/tetris/randomizers/bag5.lua b/tetris/randomizers/bag5.lua new file mode 100644 index 0000000..7344bcb --- /dev/null +++ b/tetris/randomizers/bag5.lua @@ -0,0 +1,17 @@ +local Randomizer = require 'tetris.randomizers.randomizer' + +local Bag5Randomizer = Randomizer:extend() + +function Bag5Randomizer:initialize() + self.bag = {"I", "J", "L", "O", "T"} +end + +function Bag5Randomizer:generatePiece() + if next(self.bag) == nil then + self.bag = {"I", "J", "L", "O", "T"} + end + local x = math.random(table.getn(self.bag)) + return table.remove(self.bag, x) +end + +return Bag5Randomizer diff --git a/tetris/randomizers/bag5alt.lua b/tetris/randomizers/bag5alt.lua new file mode 100644 index 0000000..90cd16e --- /dev/null +++ b/tetris/randomizers/bag5alt.lua @@ -0,0 +1,24 @@ +local Randomizer = require 'tetris.randomizers.randomizer' + +local Bag5AltRandomizer = Randomizer:extend() + +function Bag5AltRandomizer:initialize() + self.bag = {"I", "J", "L", "O", "T"} + self.prev = nil +end + +function Bag5AltRandomizer:generatePiece() + if next(self.bag) == nil then + self.bag = {"I", "J", "L", "O", "T"} + end + local x = math.random(table.getn(self.bag)) + local temp = table.remove(self.bag, x) + if temp == self.prev then + local y = math.random(table.getn(self.bag)) + temp = table.remove(self.bag, y) + end + self.prev = temp + return temp +end + +return Bag5AltRandomizer diff --git a/tetris/randomizers/bag8.lua b/tetris/randomizers/bag8.lua new file mode 100644 index 0000000..59c7dc8 --- /dev/null +++ b/tetris/randomizers/bag8.lua @@ -0,0 +1,24 @@ +local Randomizer = require 'tetris.randomizers.randomizer' + +local Bag7Randomizer = Randomizer:extend() + +function Bag7Randomizer:initialize() + self.bag = {"I", "J", "L", "O", "S", "T", "Z"} + self.extra = {"I", "J", "L", "O", "S", "T", "Z"} + table.insert(self.bag, table.remove(self.extra, math.random(table.getn(self.extra)))) +end + +function Bag7Randomizer:generatePiece() + if next(self.extra) == nil then + self.extra = {"I", "J", "L", "O", "S", "T", "Z"} + end + if next(self.bag) == nil then + self.bag = {"I", "J", "L", "O", "S", "T", "Z"} + table.insert(self.bag, table.remove(self.extra, math.random(table.getn(self.extra)))) + end + local x = math.random(table.getn(self.bag)) + --print("Bag: "..table.concat(self.bag, ", ").." | Extra: "..table.concat(self.extra, ", ")) + return table.remove(self.bag, x) +end + +return Bag7Randomizer diff --git a/tetris/randomizers/bag_konoha.lua b/tetris/randomizers/bag_konoha.lua new file mode 100644 index 0000000..cb9d78f --- /dev/null +++ b/tetris/randomizers/bag_konoha.lua @@ -0,0 +1,28 @@ +local Randomizer = require 'tetris.randomizers.randomizer' + +local BagKonoha = Randomizer:extend() + +function BagKonoha:initialize() + self.bag = {"I", "J", "L", "O", "T"} + self.prev = nil + self.allowrepeat = false + self.generated = 0 +end + +function BagKonoha:generatePiece() + self.generated = self.generated + 1 + if #self.bag == 0 then + self.bag = {"I", "J", "L", "O", "T"} + end + local x = math.random(#self.bag) + local temp = table.remove(self.bag, x) + if temp == self.prev and not self.allowrepeat then + local y = math.random(#self.bag) + table.insert(self.bag, temp) -- should insert at the end of the bag, bag[y] doesnt change + temp = table.remove(self.bag, y) + end + self.prev = temp + return temp +end + +return BagKonoha diff --git a/tetris/randomizers/recursive_bag.lua b/tetris/randomizers/recursive_bag.lua new file mode 100644 index 0000000..4f496f4 --- /dev/null +++ b/tetris/randomizers/recursive_bag.lua @@ -0,0 +1,30 @@ +local Randomizer = require 'tetris.randomizers.randomizer' + +local RecursiveRandomizer = Randomizer:extend() + +function RecursiveRandomizer:initialize() + self.bag = {"I", "J", "L", "O", "S", "T", "Z"} +end + +function RecursiveRandomizer:generatePiece() + --if next(self.bag) == nil then + -- self.bag = {"I", "J", "L", "O", "S", "T", "Z"} + --end + local x = math.random(table.getn(self.bag) + 1) + while x == table.getn(self.bag) + 1 do + --print("Refill piece pulled") + table.insert(self.bag, "I") + table.insert(self.bag, "J") + table.insert(self.bag, "L") + table.insert(self.bag, "O") + table.insert(self.bag, "S") + table.insert(self.bag, "T") + table.insert(self.bag, "Z") + x = math.random(table.getn(self.bag) + 1) + end + --print("Number of pieces in bag: "..table.getn(self.bag)) + --print("Bag: "..table.concat(self.bag, ", ")) + return table.remove(self.bag, x) +end + +return RecursiveRandomizer diff --git a/tetris/randomizers/sega.lua b/tetris/randomizers/sega.lua new file mode 100644 index 0000000..48c5e1c --- /dev/null +++ b/tetris/randomizers/sega.lua @@ -0,0 +1,19 @@ +local Randomizer = require 'tetris.randomizers.randomizer' + +local SegaRandomizer = Randomizer:extend() + +function SegaRandomizer:initialize() + self.bag = {"I", "J", "L", "O", "S", "T", "Z"} + self.sequence = {} + for i = 1, 1000 do + self.sequence[i] = self.bag[math.random(table.getn(self.bag))] + end + self.counter = 0 +end + +function SegaRandomizer:generatePiece() + self.counter = self.counter + 1 + return self.sequence[self.counter % 1000 + 1] +end + +return SegaRandomizer diff --git a/tetris/rulesets/crap.lua b/tetris/rulesets/crap.lua new file mode 100644 index 0000000..1a3c331 --- /dev/null +++ b/tetris/rulesets/crap.lua @@ -0,0 +1,173 @@ +local Piece = require 'tetris.components.piece' +local Ruleset = require 'tetris.rulesets.ruleset' + +local CRAP = Ruleset:extend() + +CRAP.name = "C.R.A.P." +CRAP.hash = "Completely Random Auto-Positioner" +CRAP.world = true +CRAP.colors={"C","O","M","R","G","Y","B"} +CRAP.colourscheme = { + I = CRAP.colors[math.ceil(math.random(7))], + L = CRAP.colors[math.ceil(math.random(7))], + J = CRAP.colors[math.ceil(math.random(7))], + S = CRAP.colors[math.ceil(math.random(7))], + Z = CRAP.colors[math.ceil(math.random(7))], + O = CRAP.colors[math.ceil(math.random(7))], + T = CRAP.colors[math.ceil(math.random(7))], +} +CRAP.softdrop_lock = true +CRAP.harddrop_lock = false + +CRAP.enable_IRS_wallkicks = true + +CRAP.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 }, +} + +CRAP.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 }, +} + +CRAP.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 CRAP: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) + + self:attemptWallkicks(piece, new_piece, rot_dir, grid) +end + +function CRAP:attemptWallkicks(piece, new_piece, rot_dir, grid) + + for i=1,20 do + 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) + return + end + end + +end + +function CRAP:onPieceCreate(piece, grid) + CRAP:randomizeColours() + piece.manipulations = 0 + piece.rotations = 0 +end + +function CRAP:onPieceDrop(piece, grid) + CRAP:randomizeColours() + piece.lock_delay = 0 -- step reset +end + +function CRAP:onPieceMove(piece, grid) + CRAP:randomizeColours() + piece.lock_delay = 0 -- move reset + if piece:isDropBlocked(grid) then + piece.manipulations = piece.manipulations + 1 + if piece.manipulations >= 10 then + piece.locked = true + end + end +end + +function CRAP:onPieceRotate(piece, grid) + CRAP:randomizeColours() + piece.lock_delay = 0 -- rotate reset + if piece:isDropBlocked(grid) then + piece.rotations = piece.rotations + 1 + if piece.rotations >= 8 then + piece.locked = true + end + end +end + +function CRAP:get180RotationValue() return 2 end + +function CRAP:randomizeColours() + CRAP.colourscheme = { + I = CRAP.colors[math.ceil(math.random(7))], + L = CRAP.colors[math.ceil(math.random(7))], + J = CRAP.colors[math.ceil(math.random(7))], + S = CRAP.colors[math.ceil(math.random(7))], + Z = CRAP.colors[math.ceil(math.random(7))], + O = CRAP.colors[math.ceil(math.random(7))], + T = CRAP.colors[math.ceil(math.random(7))], + } +end + +return CRAP diff --git a/tetris/rulesets/dtet.lua b/tetris/rulesets/dtet.lua new file mode 100644 index 0000000..84f3920 --- /dev/null +++ b/tetris/rulesets/dtet.lua @@ -0,0 +1,153 @@ +local Piece = require 'tetris.components.piece' +local Ruleset = require 'tetris.rulesets.ruleset' + +local DTET = Ruleset:extend() + +DTET.name = "D.R.S." +DTET.hash = "DTET" + +DTET.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 }, +} + +DTET.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 }, +} + +DTET.block_offsets = { + I={ + { {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} }, + }, + 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} }, + }, + 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=-2}, {x=-1, y=-1}, {x=0, y=-1}, {x=0, y=0} }, + { {x=-1, y=0}, {x=0, y=0}, {x=0, y=-1}, {x=1, y=-1} }, + { {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} }, + }, + Z={ + { {x=1, y=0}, {x=0, y=0}, {x=0, y=-1}, {x=-1, y=-1} }, + { {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1}, {x=0, y=-2} }, + { {x=1, y=0}, {x=0, y=0}, {x=0, y=-1}, {x=-1, y=-1} }, + { {x=1, y=-2}, {x=1, y=-1}, {x=0, y=-1}, {x=0, y=0} }, + } +} + +-- 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}}, + }, +}; + +function DTET:attemptWallkicks(piece, new_piece, rot_dir, grid) + + local kicks + if piece.shape == "O" then + return + elseif piece.shape == "I" then + kicks = DTET.wallkicks_line[piece.rotation][new_piece.rotation] + else + kicks = DTET.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 DTET:onPieceDrop(piece, grid) + piece.lock_delay = 0 -- step reset +end + +function DTET:getDefaultOrientation() return 1 end + +return DTET \ No newline at end of file diff --git a/tetris/rulesets/eheart.lua b/tetris/rulesets/eheart.lua new file mode 100644 index 0000000..18397d4 --- /dev/null +++ b/tetris/rulesets/eheart.lua @@ -0,0 +1,45 @@ +local Piece = require 'tetris.components.piece' +local ARS = require 'tetris.rulesets.arika' + +local EHeart = ARS:extend() + +EHeart.name = "E-Heart ARS" +EHeart.hash = "EHeartARS" + +function EHeart:attemptWallkicks(piece, new_piece, rot_dir, grid) + + -- I and O don't kick + if (piece.shape == "I" or piece.shape == "O") then return end + + -- center column rule (kicks) + local offsets = new_piece:getBlockOffsets() + table.sort(offsets, function(A, B) return A.y < B.y or A.y == B.y and A.x < B.y end) + for index, offset in pairs(offsets) do + if grid:isOccupied(piece.position.x + offset.x, piece.position.y + offset.y) then + -- individual checks for all 9 cells, in the given order + if offset.y < 0 then + if offset.x < 0 then self:lateralKick(1, piece, new_piece, rot_dir, grid) + elseif offset.x == 0 then return + elseif offset.x > 0 then self:lateralKick(-1, piece, new_piece, rot_dir, grid) end + elseif offset.y == 0 then + if offset.x < 0 then self:lateralKick(1, piece, new_piece, rot_dir, grid) + elseif offset.x == 0 then return + elseif offset.x > 0 then self:lateralKick(-1, piece, new_piece, rot_dir, grid) end + elseif offset.y > 0 then + if offset.x < 0 then self:lateralKick(1, piece, new_piece, rot_dir, grid) + elseif offset.x == 0 then return + elseif offset.x > 0 then self:lateralKick(-1, piece, new_piece, rot_dir, grid) end + end + end + end + +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) + end +end + +return EHeart diff --git a/tetris/rulesets/pptprs.lua b/tetris/rulesets/pptprs.lua new file mode 100644 index 0000000..f2e138c --- /dev/null +++ b/tetris/rulesets/pptprs.lua @@ -0,0 +1,102 @@ +local Piece = require 'tetris.components.piece' +local SRS = require 'tetris.rulesets.standard_exp' + +local PPTPRS = SRS:extend() + +PPTPRS.name = "PPTPRS" +PPTPRS.hash = "Puyo Tetris Pentos" + +PPTPRS.block_offsets = { + I={ + { {x=0, y=0}, {x=-1, y=0}, {x=-2, y=0}, {x=1, y=0}, {x=-3, y=0} }, + { {x=0, y=0}, {x=0, y=-1}, {x=0, y=1}, {x=0, y=2}, {x=0, y=-2} }, + { {x=0, y=1}, {x=-1, y=1}, {x=-2, y=1}, {x=1, y=1}, {x=2, y=1} }, + { {x=-1, y=0}, {x=-1, y=-1}, {x=-1, y=1}, {x=-1, y=2}, {x=-1, y=3} }, + }, + J={ + { {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=-1, y=-1}, {x=0, y=1} }, + { {x=0, y=0}, {x=0, y=-1}, {x=0, y=1} , {x=1, y=-1}, {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=0, y=0}, {x=0, y=1}, {x=0, y=-1}, {x=-1, y=1}, {x=1, y=0} }, + }, + L={ + { {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=1, y=-1}, {x=-1, y=1} }, + { {x=0, y=0}, {x=0, y=-1}, {x=0, y=1}, {x=1, y=1}, {x=-1, y=-1} }, + { {x=0, y=0}, {x=1, y=0}, {x=-1, y=0}, {x=-1, y=1}, {x=1, y=-1} }, + { {x=0, y=0}, {x=0, y=1}, {x=0, y=-1}, {x=-1, 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=1} }, + { {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1}, {x=-2, y=-1} }, + { {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1}, {x=0, y=-2} }, + { {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1}, {x=1, y=0} }, + }, + S={ + { {x=1, y=-1}, {x=0, y=-1}, {x=0, y=0}, {x=-1, y=0}, {x=-2, y=0} }, + { {x=1, y=1}, {x=1, y=0}, {x=0, y=0}, {x=0, y=-1}, {x=0, y=-2} }, + { {x=-1, y=1}, {x=0, y=1}, {x=0, y=0}, {x=1, y=0}, {x=2, y=0} }, + { {x=-1, y=-1}, {x=-1, y=0}, {x=0, y=0}, {x=0, y=1}, {x=0, y=2} }, + }, + T={ + { {x=0, y=0}, {x=-1, 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=1}, {x=1, y=0}, {x=2, y=0} }, + { {x=0, y=0}, {x=1, 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=-1}, {x=-1, y=0}, {x=-2, 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=-1}, {x=1, y=0}, {x=0, y=0}, {x=0, y=1}, {x=-1, 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=1}, {x=-1, y=0}, {x=0, y=0}, {x=0, y=-1}, {x=1, y=-1} }, + } +} + +PPTPRS.wallkicks_O = { + [0]={ + [1]={{x=0, y=1}}, + [2]={{x=0, y=1}}, + [3]={{x=-1, y=0}}, + }, + [1]={ + [0]={{x=0, y=-1}}, + [2]={{x=-1, y=0}}, + [3]={{x=-1, y=0}}, + }, + [2]={ + [0]={{x=0, y=-1}}, + [1]={{x=1, y=0}}, + [3]={{x=0, y=-1}}, + }, + [3]={ + [0]={{x=1, y=0}}, + [1]={{x=1, y=0}}, + [2]={{x=0, y=1}}, + }, +} + +function PPTPRS:attemptWallkicks(piece, new_piece, rot_dir, grid) + local kicks + + if piece.shape == "O" then + kicks = PPTPRS.wallkicks_O[piece.rotation][new_piece.rotation] + 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 + +return PPTPRS diff --git a/tetris/rulesets/sega.lua b/tetris/rulesets/sega.lua new file mode 100644 index 0000000..2dbe1f2 --- /dev/null +++ b/tetris/rulesets/sega.lua @@ -0,0 +1,10 @@ +local ARS = require 'tetris.rulesets.arika' + +local Sega = ARS:extend() + +Sega.name = "Sega Rotation" +Sega.hash = "Sega" + +function Sega:attemptWallkicks() end + +return Sega