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
|
|
|
|
self.cool_timer = 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 = {
|
|
frameTime(0,45,00), frameTime(0,41,50), frameTime(0,38,50), frameTime(0,35,00), frameTime(0,32,50),
|
|
frameTime(0,29,20), frameTime(0,27,20), frameTime(0,24,80), frameTime(0,22,80), frameTime(0,20,60),
|
|
frameTime(0,19,60), frameTime(0,19,40), frameTime(0,19,40), frameTime(0,18,40), frameTime(0,18,20),
|
|
frameTime(0,16,20), frameTime(0,16,20), frameTime(0,16,20), frameTime(0,16,20), frameTime(0,16,20),
|
|
frameTime(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 < frameTime(6,00,00) then self.torikan_passed[500] = true end
|
|
if section == 9 and self.frames < frameTime(8,30,00) then self.torikan_passed[900] = true end
|
|
if section == 10 and self.frames < frameTime(8,45,00) then self.torikan_passed[1000] = true end
|
|
if section == 15 and self.frames < frameTime(11,30,00) then self.torikan_passed[1500] = true end
|
|
if section == 19 and self.frames < frameTime(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()
|
|
self.section_cool_count = self.section_cool_count + 1
|
|
self.delay_level = math.min(20, self.delay_level + 1)
|
|
table.insert(self.section_status, "cool")
|
|
self.cool_timer = 300
|
|
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 > 4 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()
|
|
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()
|
|
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: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)
|
|
|
|
if (self.cool_timer > 0) then
|
|
love.graphics.printf("COOL!!", 64, 400, 160, "center")
|
|
self.cool_timer = self.cool_timer - 1
|
|
end
|
|
|
|
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
|