mirror of
https://github.com/SashLilac/cambridge.git
synced 2025-04-19 20:22:56 -05:00
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.
467 lines
14 KiB
Lua
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
|