diff --git a/tetris/modes/interval_training.lua b/tetris/modes/interval_training.lua index 740bb7e..786a8da 100644 --- a/tetris/modes/interval_training.lua +++ b/tetris/modes/interval_training.lua @@ -15,8 +15,8 @@ IntervalTrainingGame.tagline = "Can you clear the time hurdles when the game goe function IntervalTrainingGame:new() - self.level = 0 IntervalTrainingGame.super:new() + self.roll_frames = 0 self.combo = 1 self.randomizer = History6RollsRandomizer() @@ -29,12 +29,6 @@ function IntervalTrainingGame:new() 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 @@ -63,7 +57,7 @@ function IntervalTrainingGame:getSection() return math.floor(level / 100) + 1 end -function IntervalTrainingGame:advanceOneFrame() +function IntervalTrainingGame:advanceOneFrame(inputs, ruleset) if self.clear then self.roll_frames = self.roll_frames + 1 if self.roll_frames > 2968 then @@ -74,6 +68,8 @@ function IntervalTrainingGame:advanceOneFrame() if self:getSectionTime() >= self.section_time_limit then self.game_over = true end + else + self.section_time_limit = ruleset.world and 37 * 60 or 1800 end return true end diff --git a/tetris/modes/joker.lua b/tetris/modes/joker.lua index 99feee4..1ced15b 100644 --- a/tetris/modes/joker.lua +++ b/tetris/modes/joker.lua @@ -3,7 +3,7 @@ require 'funcs' local GameMode = require 'tetris.modes.gamemode' local Piece = require 'tetris.components.piece' -local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls' +local DTETRandomizer = require 'tetris.randomizers.dtet' local JokerGame = GameMode:extend() @@ -14,7 +14,7 @@ JokerGame.tagline = "One of the hardest modes! Can you retain your stock to leve function JokerGame:new() self.super:new() - self.randomizer = History6RollsRandomizer() + self.randomizer = DTETRandomizer() self.level = 50 self.stock = 0 diff --git a/tetris/modes/marathon_c89.lua b/tetris/modes/marathon_c89.lua index c3cfafd..d9bfab9 100644 --- a/tetris/modes/marathon_c89.lua +++ b/tetris/modes/marathon_c89.lua @@ -42,7 +42,7 @@ function MarathonC89Game:getDasLimit() return 16 end function MarathonC89Game:getARR() return 6 end function MarathonC89Game:getARE() - if self.last_row > 22 then return 10 + if self.last_row > 22 then return 10 elseif self.last_row > 18 then return 12 elseif self.last_row > 14 then return 14 elseif self.last_row > 10 then return 16 diff --git a/tetris/modes/marathon_gf.lua b/tetris/modes/marathon_gf.lua new file mode 100644 index 0000000..941225d --- /dev/null +++ b/tetris/modes/marathon_gf.lua @@ -0,0 +1,240 @@ +require 'funcs' + +local GameMode = require 'tetris.modes.gamemode' +local Bag7Randomizer = require 'tetris.randomizers.bag7' + +local MarathonGFGame = GameMode:extend() + +MarathonGFGame.name = "Marathon GF" +MarathonGFGame.hash = "MarathonGF" +MarathonGFGame.tagline = "The old puzzle mode from Tetris Friends!" + +function MarathonGFGame:new() + self.super:new() + + self.back_to_back = false + self.roll_limit = 10 + self.roll_frames = 0 + self.message = "" + self.message_timer = 0 + self.randomizer = Bag7Randomizer() + self.combo = 0 + + 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 = 5 +end + +function MarathonGFGame:getARE() return 6 end +function MarathonGFGame:getLineARE() return 6 end +function MarathonGFGame:getLineClearDelay() return 24 end +function MarathonGFGame:getDasLimit() return config.das end +function MarathonGFGame:getARR() return config.arr end + +function MarathonGFGame:getGravity() + if self.lines < 180 then + return (0.8 - (math.floor(self.lines / 10) * 0.007)) ^ -math.floor(self.lines / 10) / 60 + elseif self.lines < 200 then return 20 + else return 1/4 end +end + +function MarathonGFGame:getDropSpeed() + return self:getGravity() * 20 +end + +function MarathonGFGame:advanceOneFrame() + if self.clear then + self.roll_frames = self.roll_frames + 1 + if self.roll_frames < 0 then return false + else self.frames = self.frames + 1 end + if self.roll_frames >= self.roll_limit + 10 then + self.roll_frames = 0 + self.roll_limit = self.roll_limit + 5 + end + elseif 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.drop_bonus = 0 + end + return true +end + +function MarathonGFGame: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.clear = true + self.roll_frames = -150 + end + end +end + +function MarathonGFGame:getSectionTime() + return self.frames - self.section_start_time +end + +function MarathonGFGame:updateSectionTimes(old_lines, new_lines) + if math.floor(old_lines / 10) < math.floor(new_lines / 10) then + -- record new section + table.insert(self.section_times, self:getSectionTime()) + self.section_start_time = self.frames + end +end + +function MarathonGFGame:updateScore(level, drop_bonus, cleared_lines) + local normal_table = {[0] = 0, 1, 3, 5, 8} + local spin_score = 4 * (cleared_lines + 1) + local all_clear_table = {8, 12, 18, 20} + + if self.grid:checkForBravo(cleared_lines) then + self.score = self.score + ( + ((self.back_to_back and cleared_lines == 4) + and 32 or all_clear_table[cleared_lines]) * + 100 * (math.floor(self.lines / 10) + 1) + ) + end + + if cleared_lines > 0 then + self.score = self.score + ( + self.combo * 50 * (math.floor(self.lines / 10) + 1) + ) + end + + if self.piece.spin then + self.score = self.score + ( + spin_score * 100 * + (math.floor(self.lines / 10) + 1) * + ((self.back_to_back and cleared_lines ~= 0) and 1.5 or 1) + ) + if cleared_lines ~= 0 then + self.back_to_back = true + self.combo = self.combo + 1 + else self.combo = 0 end + elseif cleared_lines > 0 then + self.score = self.score + ( + normal_table[cleared_lines] * 100 * + (math.floor(self.lines / 10) + 1) * + ((self.back_to_back and cleared_lines == 4) and 1.5 or 1) + ) + if cleared_lines == 4 then self.back_to_back = true + else self.back_to_back = false end + self.combo = self.combo + 1 + else self.combo = 0 end +end + +function MarathonGFGame:onAttemptPieceMove(piece) + if self.piece ~= nil then + if not piece:isMoveBlocked(self.grid, { x=-1, y=0 }) and + not piece:isMoveBlocked(self.grid, { x=1, y=0 }) then + piece.spin = false + end + end +end + +function MarathonGFGame:onAttemptPieceRotate(piece) + if self.piece ~= nil and piece:isDropBlocked(self.grid) and + piece:isMoveBlocked(self.grid, { x=-1, y=0 }) and + piece:isMoveBlocked(self.grid, { x=1, y=0 }) and + piece:isMoveBlocked(self.grid, { x=0, y=-1 }) then + piece.spin = true + end +end + +function MarathonGFGame:onPieceLock(piece, cleared_row_count) + self.super:onPieceLock() + if self.grid:checkForBravo(cleared_row_count) then + self.message = "ALL CLEAR!" + elseif piece.spin then + self.message = (self.back_to_back and cleared_row_count ~= 0 and "B2B " or "") .. + piece.shape .. "-SPIN " .. + cleared_row_count .. "!" + elseif cleared_row_count == 4 then + self.message = (self.back_to_back and "B2B " or "") .. + "QUADRA!" + elseif cleared_row_count ~= 0 and self.combo > 0 then + self.message = "COMBO " .. self.combo .. "!" + else + self.message = "" + end + self.message_timer = 60 +end + +MarathonGFGame.rollOpacityFunction = function(age) + return 0.5 +end + +MarathonGFGame.mRollOpacityFunction = function(age) + return 0 +end + +function MarathonGFGame:drawGrid(ruleset) + if not self.game_over then + if self.lines >= 200 and self.roll_frames >= self.roll_limit then + self.grid:drawInvisible(self.rollOpacityFunction) + elseif self.lines >= 200 then + self.grid:drawInvisible(self.mRollOpacityFunction) + else self.grid:draw() end + else self.grid:draw() end + self:drawGhostPiece(ruleset) +end + +function MarathonGFGame:getHighscoreData() + return { + score = self.score, + frames = self.frames, + } +end + +function MarathonGFGame:getBackground() + return math.min(19, math.floor(self.lines / 10)) +end + +function MarathonGFGame: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, 180, 80, "left") + love.graphics.printf("LEVEL", 240, 250, 80, "left") + love.graphics.printf("LINES", 240, 320, 40, "left") + + local current_section = math.floor(self.lines / 10) + 1 + self:drawSectionTimesWithSplits(current_section) + + if self.message_timer > 0 then + love.graphics.printf(self.message, 64, 400, 160, "center") + self.message_timer = self.message_timer - 1 + end + + love.graphics.setFont(font_3x5_3) + love.graphics.printf(self.score, 240, 200, 120, "left") + love.graphics.printf(self.lines, 240, 340, 40, "right") + love.graphics.printf(self.clear and self.lines or self:getSectionEndLines(), 240, 370, 40, "right") + + if not self.game_over and self.clear and self.roll_frames % 4 < 2 then + love.graphics.setColor(1, 1, 0.3, 1) + end + love.graphics.printf(math.floor(self.lines / 10) + 1, 240, 270, 160, "left") + love.graphics.setColor(1, 1, 1, 1) + + love.graphics.setFont(font_8x11) + love.graphics.printf(formatTime(self.frames), 64, 420, 160, "center") +end + +function MarathonGFGame:getSectionEndLines() + return math.floor(self.lines / 10 + 1) * 10 +end + +return MarathonGFGame \ No newline at end of file diff --git a/tetris/modes/marathon_wcb.lua b/tetris/modes/marathon_wcb.lua index 58b578e..535ef23 100644 --- a/tetris/modes/marathon_wcb.lua +++ b/tetris/modes/marathon_wcb.lua @@ -23,7 +23,7 @@ function MarathonWCBGame:new() self.instant_hard_drop = true self.instant_soft_drop = true self.enable_hold = false - self.next_queue_length = 3 + self.next_queue_length = 6 self.piece_is_active = false end diff --git a/tetris/modes/non.lua b/tetris/modes/non.lua index ce1873a..4cb4e38 100644 --- a/tetris/modes/non.lua +++ b/tetris/modes/non.lua @@ -19,6 +19,8 @@ function NightOfNights:new() self.lock_drop = true self.lock_hard_drop = true + self.instant_soft_drop = false + self.instant_hard_drop = false self.enable_hold = true self.next_queue_length = 3 end @@ -26,7 +28,8 @@ 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:getDasLimit() return config.das end +function NightOfNights:getARR() return config.arr end function NightOfNights:getGravity() return 20 end function NightOfNights:whilePieceActive() diff --git a/tetris/modes/pro_tl.lua b/tetris/modes/pro_tl.lua index 0988485..cf8afe1 100644 --- a/tetris/modes/pro_tl.lua +++ b/tetris/modes/pro_tl.lua @@ -13,11 +13,11 @@ function ProGame:new() self.super:new() self.next_queue_length = 6 self.randomizer = TetraRandomizer() - self.active_time = 0 end function ProGame:initialize(ruleset) self.super.initialize(self, ruleset) + ruleset.onPieceDrop = function() end ruleset.onPieceMove = function() end ruleset.onPieceRotate = function() end end @@ -25,12 +25,13 @@ 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:getDasLimit() return config.das end +function ProGame:getARR() return config.arr 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 < 40 then return 2 elseif self.lines < 60 then return 5 else return 20 end end @@ -69,14 +70,18 @@ function ProGame:advanceOneFrame(inputs, ruleset) end function ProGame:onPieceEnter() - self.active_time = 0 + self.section_clear = false + self.piece.lowest_y = -math.huge +end + +function ProGame:onHold() + self.piece.lowest_y = -math.huge end function ProGame:whilePieceActive() - if self.piece:isDropBlocked(self.grid) then - self.active_time = self.active_time + 1 - self.piece.locked = self.active_time >= self:getLockDelay() * 4 - end + self.piece.lock_delay = self.piece.lowest_y < self.piece.position.y + and 0 or self.piece.lock_delay + self.piece.lowest_y = math.max(self.piece.lowest_y, self.piece.position.y) end function ProGame:onLineClear(cleared_row_count) diff --git a/tetris/modes/race_40.lua b/tetris/modes/race_40.lua index 62039f8..b5431ea 100644 --- a/tetris/modes/race_40.lua +++ b/tetris/modes/race_40.lua @@ -36,9 +36,6 @@ function Race40Game:new() self.instant_soft_drop = false self.enable_hold = true self.next_queue_length = 3 - - self.irs = false - self.ihs = false end function Race40Game:getDropSpeed() @@ -46,7 +43,7 @@ function Race40Game:getDropSpeed() end function Race40Game:getARR() - return 1 + return config.arr end function Race40Game:getARE() @@ -58,7 +55,7 @@ function Race40Game:getLineARE() end function Race40Game:getDasLimit() - return 8 + return config.das end function Race40Game:getLineClearDelay() @@ -86,6 +83,11 @@ function Race40Game:advanceOneFrame() return true end +function Race40Game:onPieceEnter() + self.irs = false + self.ihs = false +end + function Race40Game:onPieceLock() self.super:onPieceLock() self.pieces = self.pieces + 1 diff --git a/tetris/modes/speed.lua b/tetris/modes/speed.lua index 73e1416..a9b070e 100644 --- a/tetris/modes/speed.lua +++ b/tetris/modes/speed.lua @@ -40,7 +40,8 @@ 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:getDasLimit() return config.das end +function LudicrousSpeed:getARR() return config.arr end function LudicrousSpeed:getDropSpeed() return 20 end local function mean(t) diff --git a/tetris/randomizers/dtet.lua b/tetris/randomizers/dtet.lua new file mode 100644 index 0000000..ccdcd78 --- /dev/null +++ b/tetris/randomizers/dtet.lua @@ -0,0 +1,66 @@ +local Randomizer = require 'tetris.randomizers.randomizer' + +local DTETRandomizer = Randomizer:extend() + +function DTETRandomizer:initialize() + -- keep a tally of how long each piece has been droughted + self.droughts = { + ["I"] = 0, + ["J"] = 0, + ["L"] = 0, + ["O"] = 0, + ["S"] = 0, + ["T"] = 0, + ["Z"] = 0 + } +end + +function DTETRandomizer:updateDroughts(piece) + -- update drought counters + for k, v in pairs(self.droughts) do + if k == piece then + self.droughts[k] = 0 + else + self.droughts[k] = v + 1 + end + end +end + +function DTETRandomizer:generatePiece() + local droughts = {} + local weights = {} + local bag = {} + + -- copy drought table + for k, v in pairs(self.droughts) do + droughts[k] = v + end + + -- assign weights to each piece + for i = 1, 7 do + local lowest_drought = math.huge + local lowest_drought_piece = "" + for k, v in pairs(droughts) do + if v ~= nil and v < lowest_drought then + lowest_drought = v + lowest_drought_piece = k + end + end + droughts[lowest_drought_piece] = nil + weights[lowest_drought_piece] = i - 1 + end + + -- insert pieces into 21-bag + for k, v in pairs(weights) do + for i = 1, v do + table.insert(bag, k) + end + end + + -- pull piece from 21-bag and update drought counters + local generated = bag[math.random(#bag)] + self:updateDroughts(generated) + return generated +end + +return DTETRandomizer \ No newline at end of file diff --git a/tetris/randomizers/history.lua b/tetris/randomizers/history.lua new file mode 100644 index 0000000..cfa394a --- /dev/null +++ b/tetris/randomizers/history.lua @@ -0,0 +1,38 @@ +local Randomizer = require 'tetris.randomizers.randomizer' + +local HistoryRandomizer = Randomizer:extend() + +function HistoryRandomizer:new(history_length, rolls, allowed_pieces) + self.history = {} + for i = 1, history_length do + table.insert(self.history, '') + end + self.rolls = rolls + self.allowed_pieces = allowed_pieces +end + +function HistoryRandomizer:generatePiece() + for i = 1, self.rolls do + local x = math.random(table.getn(self.allowed_pieces)) + if not inHistory(self.allowed_pieces[x], self.history) or i == self.rolls then + return self:updateHistory(self.allowed_pieces[x]) + end + end +end + +function HistoryRandomizer:updateHistory(shape) + table.remove(self.history, 1) + table.insert(self.history, shape) + return shape +end + +function inHistory(piece, history) + for idx, entry in pairs(history) do + if entry == piece then + return true + end + end + return false +end + +return HistoryRandomizer \ No newline at end of file diff --git a/tetris/rulesets/dtet.lua b/tetris/rulesets/dtet.lua index c7bb811..b522100 100644 --- a/tetris/rulesets/dtet.lua +++ b/tetris/rulesets/dtet.lua @@ -109,4 +109,4 @@ end function DTET:getDefaultOrientation() return 3 end -return DTET +return DTET \ No newline at end of file diff --git a/tetris/rulesets/srs_x.lua b/tetris/rulesets/srs_x.lua index 66b484b..27de630 100644 --- a/tetris/rulesets/srs_x.lua +++ b/tetris/rulesets/srs_x.lua @@ -23,6 +23,7 @@ function SRS:onPieceMove(piece, grid) if piece:isDropBlocked(grid) then piece.manipulations = piece.manipulations + 1 if piece.manipulations >= 24 then + piece:dropToBottom(grid) piece.locked = true end end @@ -33,6 +34,7 @@ function SRS:onPieceRotate(piece, grid) if piece:isDropBlocked(grid) then piece.rotations = piece.rotations + 1 if piece.rotations >= 12 then + piece:dropToBottom(grid) piece.locked = true end end diff --git a/tetris/rulesets/trans.lua b/tetris/rulesets/trans.lua new file mode 100644 index 0000000..33e86e9 --- /dev/null +++ b/tetris/rulesets/trans.lua @@ -0,0 +1,48 @@ +local ARS = require 'tetris.rulesets.arika' +local Trans = ARS:extend() + +Trans.name = "TransRS" +Trans.hash = "TransRS" + +function Trans: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 = 2 + 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) + local pieces = {"I", "J", "L", "O", "S", "T", "Z"} + repeat + new_piece.shape = pieces[math.random(7)] + until piece.shape ~= new_piece.shape + + if (grid:canPlacePiece(new_piece)) then + self:onPieceRotate(piece, grid) + piece:setRelativeRotation(rot_dir) + piece.shape = new_piece.shape + else + if not(initial and self.enable_IRS_wallkicks == false) then + if (grid:canPlacePiece(new_piece:withOffset({x=1, y=0}))) then + self:onPieceRotate(piece, grid) + piece:setRelativeRotation(rot_dir):setOffset({x=1, y=0}) + piece.shape = new_piece.shape + elseif (grid:canPlacePiece(new_piece:withOffset({x=-1, y=0}))) then + self:onPieceRotate(piece, grid) + piece:setRelativeRotation(rot_dir):setOffset({x=-1, y=0}) + piece.shape = new_piece.shape + end + end + end +end + +return Trans \ No newline at end of file