cambridge/tetris/modes/marathon_2020.lua
Joe Z 35f4aea67d Added Ti-SRS and modified delay curve behaviour on Marathon 2020.
I realized that playing at 4/8 for 800 levels straight is probably too much,
so I made it that only the first 10 sections count for advancing the delay
curve faster than it would normally go. Now only the last 500 levels can be
at delay level 20.
2019-06-21 23:44:58 -04:00

467 lines
14 KiB
Lua

require 'funcs'
local GameMode = require 'tetris.modes.gamemode'
local Piece = require 'tetris.components.piece'
local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls'
local Marathon2020Game = GameMode:extend()
Marathon2020Game.name = "Marathon 2020"
Marathon2020Game.hash = "Marathon2020"
Marathon2020Game.tagline = "2020 levels of pure pain! Can you achieve the World Master rank?"
function Marathon2020Game:new()
Marathon2020Game.super:new()
self.lock_drop = true
self.lock_hard_drop = true
self.enable_hold = true
self.next_queue_length = 3
self.delay_level = 0
self.roll_frames = 0
self.no_roll_frames = 0
self.combo = 1
self.randomizer = History6RollsRandomizer()
self.section_cool_count = 0
self.section_status = { [0] = "none" }
self.torikan_passed = {
[500] = false, [900] = false,
[1000] = false, [1500] = false, [1900] = false
}
self.torikan_hit = false
self.grade = 0
self.grade_points = 0
self.grade_point_decay_counter = 0
self.max_grade_points = 0
end
function Marathon2020Game:getARE()
if self.delay_level < 1 then return 27
elseif self.delay_level < 2 then return 24
elseif self.delay_level < 3 then return 21
elseif self.delay_level < 4 then return 18
elseif self.delay_level < 5 then return 16
elseif self.delay_level < 6 then return 14
elseif self.delay_level < 7 then return 12
elseif self.delay_level < 8 then return 10
elseif self.delay_level < 9 then return 8
elseif self.delay_level < 13 then return 6
elseif self.delay_level < 15 then return 5
else return 4 end
end
function Marathon2020Game:getLineARE()
return self:getARE()
end
function Marathon2020Game:getDasLimit()
if self.delay_level < 1 then return 15
elseif self.delay_level < 3 then return 12
elseif self.delay_level < 5 then return 9
elseif self.delay_level < 8 then return 8
elseif self.delay_level < 10 then return 7
elseif self.delay_level < 13 then return 6
elseif self.delay_level < 15 then return 5
elseif self.delay_level < 20 then return 4
else return 3 end
end
function Marathon2020Game:getLineClearDelay()
if self.delay_level < 1 then return 40
elseif self.delay_level < 3 then return 25
elseif self.delay_level < 4 then return 20
elseif self.delay_level < 5 then return 15
elseif self.delay_level < 7 then return 12
elseif self.delay_level < 9 then return 8
elseif self.delay_level < 11 then return 6
elseif self.delay_level < 14 then return 4
else return 2 end
end
function Marathon2020Game:getLockDelay()
if self.delay_level < 6 then return 30
elseif self.delay_level < 7 then return 26
elseif self.delay_level < 8 then return 22
elseif self.delay_level < 9 then return 19
elseif self.delay_level < 10 then return 17
elseif self.delay_level < 16 then return 15
elseif self.delay_level < 17 then return 13
elseif self.delay_level < 18 then return 11
elseif self.delay_level < 19 then return 10
elseif self.delay_level < 20 then return 9
else return 8 end
end
function Marathon2020Game: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
local cleared_row_levels = {1, 2, 4, 6}
function Marathon2020Game:advanceOneFrame()
if self.torikan_hit then
self.no_roll_frames = self.no_roll_frames + 1
if self.no_roll_frames > 120 then
self.completed = true
end
return false
elseif self.clear then
self.roll_frames = self.roll_frames + 1
if self.roll_frames < 0 then
return false
elseif self.roll_frames > 4000 then
self.completed = true
end
elseif self.ready_frames == 0 then
self.frames = self.frames + 1
end
return true
end
local cool_cutoffs = {
sp(0,45,00), sp(0,41,50), sp(0,38,50), sp(0,35,00), sp(0,32,50),
sp(0,29,20), sp(0,27,20), sp(0,24,80), sp(0,22,80), sp(0,20,60),
sp(0,19,60), sp(0,19,40), sp(0,19,40), sp(0,18,40), sp(0,18,20),
sp(0,16,20), sp(0,16,20), sp(0,16,20), sp(0,16,20), sp(0,16,20),
sp(0,15,20)
}
local levels_for_cleared_rows = { 1, 2, 4, 6 }
function Marathon2020Game:onPieceEnter()
self:updateLevel(1, false)
end
function Marathon2020Game:whilePieceActive()
if not self.clear then
self.grade_point_decay_counter = self.grade_point_decay_counter + self.grade + 2
end
if self.grade_point_decay_counter > 240 then
self.grade_point_decay_counter = 0
self.grade_points = math.max(0, self.grade_points - 1)
end
end
function Marathon2020Game:onLineClear(cleared_row_count)
self:updateLevel(levels_for_cleared_rows[cleared_row_count], true)
self:updateGrade(cleared_row_count)
end
function Marathon2020Game:updateLevel(increment, line_clear)
local new_level
if self.torikan_passed[900] == false then
if line_clear == false and (
math.floor((self.level + increment) / 100) > math.floor(self.level / 100) or
self.level == 998
) then
new_level = math.min(998, self.level + (99 - self.level % 100))
else
new_level = math.min(999, self.level + increment)
end
elseif self.torikan_passed[1900] == false then
if line_clear == false and (
math.floor((self.level + increment) / 100) > math.floor(self.level / 100) or
self.level == 1999
) then
new_level = math.min(1999, self.level + (99 - self.level % 100))
else
new_level = math.min(2000, self.level + increment)
end
else
if line_clear == false and (
self.level < 1900 and
math.floor((self.level + increment) / 100) > math.floor(self.level / 100)
) then
new_level = self.level + (99 - self.level % 100)
elseif line_clear == false and self.level + increment > 2019 then
new_level = 2019
else
new_level = math.min(2020, self.level + increment)
end
end
if not self.clear then
self:updateSectionTimes(self.level, new_level)
if not self.clear then
self.level = new_level
end
end
end
local low_cleared_line_points = {10, 20, 30, 40}
local mid_cleared_line_points = {2, 6, 12, 24}
local high_cleared_line_points = {1, 4, 9, 20}
local function getGradeForGradePoints(points)
return math.floor(math.sqrt((points / 50) * 8 + 1) / 2 - 0.5)
-- Don't be afraid of the above function. All it does is make it so that
-- you need 50 points to get to grade 1, 100 points to grade 2, etc.
end
function Marathon2020Game:updateGrade(cleared_lines)
-- update grade points and max grade points
local point_level = math.floor(self.level / 100) + self.delay_level
local plus_points = math.max(
low_cleared_line_points[cleared_lines],
mid_cleared_line_points[cleared_lines] * (1 + point_level / 2),
high_cleared_line_points[cleared_lines] * (point_level - 2),
(self.level >= 1000 and cleared_lines == 4) and self.grade * 30 or 0
)
self.grade_points = self.grade_points + plus_points
if self.grade_points > self.max_grade_points then
self.max_grade_points = self.grade_points
end
self.grade = getGradeForGradePoints(self.max_grade_points)
end
function Marathon2020Game:getTotalGrade()
return self.grade + self.section_cool_count
end
local function getSectionForLevel(level)
if level < 2001 then
return math.floor(level / 100) + 1
else
return 20
end
end
function Marathon2020Game:getEndOfSectionForSection(section)
if self.torikan_passed[900] == false and section == 10 then return 999
elseif self.torikan_passed[1900] == false and section == 20 then return 2000
elseif section == 20 then return 2020
else return section * 100 end
end
function Marathon2020Game:sectionPassed(old_level, new_level)
if self.torikan_passed[900] == false then
return (
(math.floor(old_level / 100) < math.floor(new_level / 100)) or
(new_level >= 999)
)
elseif self.torikan_passed[1900] == false then
return (
(math.floor(old_level / 100) < math.floor(new_level / 100)) or
(new_level >= 2000)
)
else
return (
(new_level < 2001 and math.floor(old_level / 100) < math.floor(new_level / 100)) or
(new_level >= 2020)
)
end
end
function Marathon2020Game:checkTorikan(section)
if section == 5 and self.frames < sp(6,00,00) then self.torikan_passed[500] = true end
if section == 9 and self.frames < sp(8,30,00) then self.torikan_passed[900] = true end
if section == 10 and self.frames < sp(8,45,00) then self.torikan_passed[1000] = true end
if section == 15 and self.frames < sp(11,30,00) then self.torikan_passed[1500] = true end
if section == 19 and self.frames < sp(13,15,00) then self.torikan_passed[1900] = true end
end
function Marathon2020Game:checkClear(level)
if (
self.torikan_passed[500] == false and level >= 500 or
self.torikan_passed[900] == false and level >= 999 or
self.torikan_passed[1000] == false and level >= 1000 or
self.torikan_passed[1500] == false and level >= 1500 or
self.torikan_passed[1900] == false and level >= 2000 or
level >= 2020
) then
if self.torikan_passed[500] == false then self.level = 500
elseif self.torikan_passed[900] == false then self.level = 999
elseif self.torikan_passed[1000] == false then self.level = 1000
elseif self.torikan_passed[1500] == false then self.level = 1500
elseif self.torikan_passed[1900] == false then self.level = 2000
else self.level = 2020 end
self.clear = true
self.grid:clear()
if (
self.torikan_passed[900] == false and level >= 999 or
level >= 2020
) then
self.roll_frames = -150
else
self.torikan_hit = true
self.no_roll_frames = -150
end
end
end
function Marathon2020Game:updateSectionTimes(old_level, new_level)
function sectionCool(section)
self.section_cool_count = self.section_cool_count + 1
if section < 10 then self.delay_level = math.min(20, self.delay_level + 1) end
table.insert(self.section_status, "cool")
end
local section = getSectionForLevel(old_level)
if section <= 19 and old_level % 100 < 70 and new_level >= math.floor(old_level / 100) * 100 + 70 then
-- record section 70 time
section_70_time = self.frames - self.section_start_time
table.insert(self.secondary_section_times, section_70_time)
end
if self:sectionPassed(old_level, new_level) then
-- record new section
section_time = self.frames - self.section_start_time
table.insert(self.section_times, section_time)
self.section_start_time = self.frames
if section > 5 then self.delay_level = math.min(20, self.delay_level + 1) end
self:checkTorikan(section)
self:checkClear(new_level)
if (
section <= 19 and self.section_status[section - 1] == "cool" and
self.secondary_section_times[section] < self.secondary_section_times[section - 1] + 120 and
self.secondary_section_times[section] < cool_cutoffs[section]
) then
sectionCool(section)
elseif self.section_status[section - 1] == "cool" then
table.insert(self.section_status, "none")
elseif section <= 19 and self.secondary_section_times[section] < cool_cutoffs[section] then
sectionCool(section)
else
table.insert(self.section_status, "none")
end
end
end
function Marathon2020Game:updateScore(level, drop_bonus, cleared_lines)
if cleared_lines > 0 then
self.score = self.score + (
(math.ceil((level + cleared_lines) / 4) + drop_bonus) *
cleared_lines * (cleared_lines * 2 - 1) * (self.combo * 2 - 1)
)
self.lines = self.lines + cleared_lines
self.combo = self.combo + cleared_lines - 1
else
self.drop_bonus = 0
self.combo = 1
end
end
Marathon2020Game.rollOpacityFunction = function(age)
if age > 300 then return 0
elseif age < 240 then return 1
else return (300 - age) / 60 end
end
Marathon2020Game.mRollOpacityFunction = function(age)
if age > 4 then return 0
else return 1 - age / 4 end
end
function Marathon2020Game:qualifiesForMRoll()
return false -- until I actually have grading working
--[[
GM-roll requirements
You qualify for the GM roll if you:
- Reach level 2020
- with a grade of 50
- in less than 13:30.00 total.
]]--
end
function Marathon2020Game:drawGrid()
if self.clear and not (self.completed or self.game_over) then
if self:qualifiesForMRoll() then
self.grid:drawInvisible(self.mRollOpacityFunction)
else
self.grid:drawInvisible(self.rollOpacityFunction)
end
else
self.grid:draw()
if self.piece ~= nil and self.level < 100 then
self:drawGhostPiece(ruleset)
end
end
end
function Marathon2020Game:sectionColourFunction(section)
if self.section_status[section] == "cool" then
return { 0, 1, 0, 1 }
else
return { 1, 1, 1, 1 }
end
end
function Marathon2020Game:drawScoringInfo()
Marathon2020Game.super.drawScoringInfo(self)
local current_section = getSectionForLevel(self.level)
local text_x = config["side_next"] and 320 or 240
love.graphics.setFont(font_3x5_2)
love.graphics.printf("GRADE", text_x, 100, 40, "left")
love.graphics.printf("GRADE PTS.", text_x, 200, 90, "left")
love.graphics.printf("LEVEL", text_x, 320, 40, "left")
self:drawSectionTimesWithSecondary(current_section, self.sectionColourFunction)
love.graphics.setFont(font_3x5_3)
love.graphics.printf(self:getTotalGrade(), text_x, 120, 90, "left")
love.graphics.printf(self.grade_points, 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(self:getEndOfSectionForSection(current_section), text_x, 370, 50, "right")
end
end
function Marathon2020Game:getHighscoreData()
return {
grade = self.grade,
level = self.level,
frames = self.frames,
}
end
function Marathon2020Game:getBackground()
return math.min(19, math.floor(self.level / 100))
end
return Marathon2020Game