mirror of
https://github.com/SashLilac/cambridge.git
synced 2024-11-16 21:29:02 -06:00
96ac054cf6
Resolves #15. 1) Cleared row count is marked before the onPieceLock method is called, letting the piece lock procedure react to the count of rows the piece is about to clear. (In practice, only 0 and non-0 will be different.) 2) The modes with bottom-row garbage will not advance the garbage counter when the piece is about to clear lines, as should be the case. Also included: 3) Changed the Always O Randomizer to the Always Randomizer that takes which piece it should "always" produce as an argument in the constructor. 4) Fixed the torikan for level 800 in Phantom Mania 2. It should have been 4:45, not 4:40.
459 lines
12 KiB
Lua
459 lines
12 KiB
Lua
local Object = require 'libs.classic'
|
|
require 'funcs'
|
|
|
|
local Grid = require 'tetris.components.grid'
|
|
local Randomizer = require 'tetris.randomizers.randomizer'
|
|
|
|
local GameMode = Object:extend()
|
|
|
|
GameMode.rollOpacityFunction = function(age) return 0 end
|
|
|
|
function GameMode:new()
|
|
self.grid = Grid()
|
|
self.randomizer = Randomizer()
|
|
self.piece = nil
|
|
self.ready_frames = 100
|
|
self.frames = 0
|
|
self.game_over_frames = 0
|
|
self.score = 0
|
|
self.level = 0
|
|
self.lines = 0
|
|
self.drop_bonus = 0
|
|
self.are = 0
|
|
self.lcd = 0
|
|
self.das = { direction = "none", frames = -1 }
|
|
self.move = "none"
|
|
self.prev_inputs = {}
|
|
self.next_queue = {}
|
|
self.game_over = false
|
|
self.clear = false
|
|
self.completed = false
|
|
-- configurable parameters
|
|
self.lock_drop = false
|
|
self.lock_hard_drop = false
|
|
self.instant_hard_drop = false
|
|
self.instant_soft_drop = true
|
|
self.enable_hold = false
|
|
self.enable_hard_drop = true
|
|
self.next_queue_length = 1
|
|
self.draw_section_times = false
|
|
self.draw_secondary_section_times = false
|
|
-- variables related to configurable parameters
|
|
self.drop_locked = false
|
|
self.hard_drop_locked = false
|
|
self.hold_queue = nil
|
|
self.held = false
|
|
self.section_start_time = 0
|
|
self.section_times = { [0] = 0 }
|
|
self.secondary_section_times = { [0] = 0 }
|
|
end
|
|
|
|
function GameMode:initialize()
|
|
-- after all the variables are initialized, run initialization procedures
|
|
for i = 1, 30 do
|
|
table.insert(self.next_queue, self:getNextPiece(ruleset))
|
|
end
|
|
end
|
|
|
|
function GameMode:getARR() return 1 end
|
|
function GameMode:getDropSpeed() return 1 end
|
|
function GameMode:getARE() return 25 end
|
|
function GameMode:getLineARE() return 25 end
|
|
function GameMode:getLockDelay() return 30 end
|
|
function GameMode:getLineClearDelay() return 40 end
|
|
function GameMode:getDasLimit() return 15 end
|
|
|
|
function GameMode:getNextPiece(ruleset)
|
|
return {
|
|
skin = "2tie",
|
|
shape = self.randomizer:nextPiece(),
|
|
orientation = ruleset:getDefaultOrientation(),
|
|
}
|
|
end
|
|
|
|
function GameMode:initialize(ruleset)
|
|
-- generate next queue
|
|
for i = 1, self.next_queue_length do
|
|
table.insert(self.next_queue, self:getNextPiece(ruleset))
|
|
end
|
|
end
|
|
|
|
function GameMode:update(inputs, ruleset)
|
|
if self.game_over then
|
|
self.game_over_frames = self.game_over_frames + 1
|
|
if self.game_over_frames >= 60 then
|
|
self.completed = true
|
|
end
|
|
return
|
|
end
|
|
if self.completed then return end
|
|
|
|
-- advance one frame
|
|
if self:advanceOneFrame(inputs) == false then return end
|
|
|
|
self:chargeDAS(inputs, self:getDasLimit(), self.getARR())
|
|
|
|
if self.piece == nil then
|
|
self:processDelays(inputs, ruleset)
|
|
else
|
|
-- perform active frame actions such as fading out the next queue
|
|
self:whilePieceActive()
|
|
local gravity = self:getGravity()
|
|
|
|
if self.enable_hold and inputs["hold"] == true and self.held == false then
|
|
self:hold(inputs, ruleset)
|
|
self.prev_inputs = inputs
|
|
return
|
|
end
|
|
|
|
if self.lock_drop and inputs["down"] ~= true then
|
|
self.drop_locked = false
|
|
end
|
|
|
|
if self.lock_hard_drop and inputs["up"] ~= true then
|
|
self.hard_drop_locked = false
|
|
end
|
|
|
|
local piece_y = self.piece.position.y
|
|
|
|
ruleset:processPiece(
|
|
inputs, self.piece, self.grid, self:getGravity(), self.prev_inputs,
|
|
self.move, self:getLockDelay(), self:getDropSpeed(),
|
|
self.drop_locked, self.hard_drop_locked, self.enable_hard_drop
|
|
)
|
|
|
|
local piece_dy = self.piece.position.y - piece_y
|
|
|
|
if inputs["up"] == true and
|
|
self.piece:isDropBlocked(self.grid) and
|
|
not self.hard_drop_locked then
|
|
self:onHardDrop(piece_dy)
|
|
if self.instant_hard_drop then
|
|
self.piece.locked = true
|
|
end
|
|
end
|
|
|
|
if inputs["down"] == true then
|
|
self:onSoftDrop(piece_dy)
|
|
if self.piece:isDropBlocked(self.grid) and
|
|
not self.drop_locked and
|
|
self.instant_soft_drop
|
|
then
|
|
self.piece.locked = true
|
|
end
|
|
end
|
|
|
|
if self.piece.locked == true then
|
|
self.grid:applyPiece(self.piece)
|
|
self.grid:markClearedRows()
|
|
|
|
local cleared_row_count = self.grid:getClearedRowCount()
|
|
|
|
self:onPieceLock(self.piece, cleared_row_count)
|
|
self:updateScore(self.level, self.drop_bonus, cleared_row_count)
|
|
|
|
self.piece = nil
|
|
if self.enable_hold then
|
|
self.held = false
|
|
end
|
|
|
|
if cleared_row_count > 0 then
|
|
self.lcd = self:getLineClearDelay()
|
|
self.are = self:getLineARE()
|
|
if self.lcd == 0 then
|
|
self.grid:clearClearedRows()
|
|
if self.are == 0 then
|
|
self:initializeOrHold(inputs, ruleset)
|
|
end
|
|
end
|
|
self:onLineClear(cleared_row_count)
|
|
else
|
|
if self:getARE() == 0 then
|
|
self:initializeOrHold(inputs, ruleset)
|
|
else
|
|
self.are = self:getARE()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
self.prev_inputs = inputs
|
|
end
|
|
|
|
function GameMode:updateScore() end
|
|
|
|
function GameMode:advanceOneFrame()
|
|
if self.clear then
|
|
self.completed = true
|
|
elseif self.ready_frames == 0 then
|
|
self.frames = self.frames + 1
|
|
end
|
|
end
|
|
|
|
-- event functions
|
|
function GameMode:whilePieceActive() end
|
|
function GameMode:onPieceLock(piece, cleared_row_count) end
|
|
function GameMode:onLineClear(cleared_row_count) end
|
|
function GameMode:onPieceEnter() end
|
|
function GameMode:onHold() end
|
|
|
|
function GameMode:onSoftDrop(dropped_row_count)
|
|
self.drop_bonus = self.drop_bonus + 1 * dropped_row_count
|
|
end
|
|
|
|
function GameMode:onHardDrop(dropped_row_count)
|
|
self.drop_bonus = self.drop_bonus + 2 * dropped_row_count
|
|
end
|
|
|
|
function GameMode:onGameOver()
|
|
switchBGM(nil)
|
|
end
|
|
|
|
function GameMode:chargeDAS(inputs)
|
|
if inputs[self.das.direction] == true 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.move = "right"
|
|
self.das = { direction = "right", frames = 0 }
|
|
elseif inputs["left"] == true then
|
|
self.move = "left"
|
|
self.das = { direction = "left", frames = 0 }
|
|
else
|
|
self.move = "none"
|
|
self.das = { direction = "none", frames = -1 }
|
|
end
|
|
end
|
|
|
|
function GameMode:processDelays(inputs, ruleset, drop_speed)
|
|
if self.ready_frames > 0 then
|
|
self.ready_frames = self.ready_frames - 1
|
|
if self.ready_frames == 0 then
|
|
self:initializeOrHold(inputs, ruleset)
|
|
end
|
|
elseif self.lcd > 0 then
|
|
self.lcd = self.lcd - 1
|
|
if self.lcd == 0 then
|
|
self.grid:clearClearedRows()
|
|
if self.are == 0 then
|
|
self:initializeOrHold(inputs, ruleset)
|
|
end
|
|
end
|
|
elseif self.are > 0 then
|
|
self.are = self.are - 1
|
|
if self.are == 0 then
|
|
self:initializeOrHold(inputs, ruleset)
|
|
end
|
|
end
|
|
end
|
|
|
|
function GameMode:initializeOrHold(inputs, ruleset)
|
|
if self.enable_hold and inputs["hold"] == true then
|
|
self:hold(inputs, ruleset)
|
|
else
|
|
self:initializeNextPiece(inputs, ruleset, self.next_queue[1])
|
|
end
|
|
self:onPieceEnter()
|
|
if not self.grid:canPlacePiece(self.piece) then
|
|
self:onGameOver()
|
|
self.game_over = true
|
|
end
|
|
end
|
|
|
|
function GameMode:hold(inputs, ruleset)
|
|
local data = copy(self.hold_queue)
|
|
if self.piece == nil then
|
|
self.hold_queue = self.next_queue[1]
|
|
table.remove(self.next_queue, 1)
|
|
table.insert(self.next_queue, self:getNextPiece(ruleset))
|
|
else
|
|
self.hold_queue = {
|
|
skin = self.piece.skin,
|
|
shape = self.piece.shape,
|
|
orientation = ruleset:getDefaultOrientation(),
|
|
}
|
|
end
|
|
if data == nil then
|
|
self:initializeNextPiece(inputs, ruleset, self.next_queue[1])
|
|
else
|
|
self:initializeNextPiece(inputs, ruleset, data, false)
|
|
end
|
|
self.held = true
|
|
self:onHold()
|
|
end
|
|
|
|
function GameMode:initializeNextPiece(inputs, ruleset, piece_data, generate_next_piece)
|
|
local gravity = self:getGravity()
|
|
self.piece = ruleset:initializePiece(
|
|
inputs, piece_data, self.grid, gravity,
|
|
self.prev_inputs, self.move,
|
|
self:getLockDelay(), self:getDropSpeed(),
|
|
self.lock_drop, self.lock_hard_drop
|
|
)
|
|
if self.lock_drop then
|
|
self.drop_locked = true
|
|
end
|
|
if self.lock_hard_drop then
|
|
self.hard_drop_locked = true
|
|
end
|
|
if generate_next_piece == nil then
|
|
table.remove(self.next_queue, 1)
|
|
table.insert(self.next_queue, self:getNextPiece(ruleset))
|
|
end
|
|
self:playNextSound()
|
|
end
|
|
|
|
function GameMode:playNextSound()
|
|
playSE("blocks", self.next_queue[1].shape)
|
|
end
|
|
|
|
function GameMode:getHighScoreData()
|
|
return {
|
|
score = self.score
|
|
}
|
|
end
|
|
|
|
function GameMode:drawPiece()
|
|
if self.piece ~= nil then
|
|
self.piece:draw(
|
|
1,
|
|
self:getLockDelay() == 0 and 1 or
|
|
(0.25 + 0.75 * math.max(1 - self.piece.gravity, 1 - (self.piece.lock_delay / self:getLockDelay()))),
|
|
self.grid
|
|
)
|
|
end
|
|
end
|
|
|
|
function GameMode:drawGhostPiece(ruleset)
|
|
if self.piece == nil then return end
|
|
local ghost_piece = self.piece:withOffset({x=0, y=0})
|
|
ghost_piece.ghost = true
|
|
ghost_piece:dropToBottom(self.grid)
|
|
ghost_piece:draw(0.5)
|
|
end
|
|
|
|
function GameMode:drawNextQueue(ruleset)
|
|
function drawPiece(piece, skin, offsets, pos_x, pos_y)
|
|
for index, offset in pairs(offsets) do
|
|
local x = ruleset.spawn_positions[piece].x + offset.x
|
|
local y = ruleset.spawn_positions[piece].y + offset.y
|
|
love.graphics.draw(blocks[skin][piece], pos_x+x*16, pos_y+y*16)
|
|
end
|
|
end
|
|
for i = 1, self.next_queue_length do
|
|
self:setNextOpacity(i)
|
|
local next_piece = self.next_queue[i].shape
|
|
local skin = self.next_queue[i].skin
|
|
local rotation = self.next_queue[i].orientation
|
|
if config.side_next then -- next at side
|
|
drawPiece(next_piece, skin, ruleset.block_offsets[next_piece][rotation], 192, -16+i*48)
|
|
else -- next at top
|
|
drawPiece(next_piece, skin, ruleset.block_offsets[next_piece][rotation], -16+i*80, -32)
|
|
end
|
|
end
|
|
if self.hold_queue ~= nil then
|
|
self:setHoldOpacity()
|
|
drawPiece(
|
|
self.hold_queue.shape,
|
|
self.hold_queue.skin,
|
|
ruleset.block_offsets[self.hold_queue.shape][self.hold_queue.orientation],
|
|
-16, -32
|
|
)
|
|
end
|
|
return false
|
|
end
|
|
|
|
function GameMode:setNextOpacity(i) love.graphics.setColor(1, 1, 1, 1) end
|
|
function GameMode:setHoldOpacity() love.graphics.setColor(1, 1, 1, 1) end
|
|
|
|
function GameMode:drawScoringInfo()
|
|
love.graphics.setColor(1, 1, 1, 1)
|
|
love.graphics.setFont(font_3x5_2)
|
|
|
|
if config["side_next"] then
|
|
love.graphics.printf("NEXT", 240, 72, 40, "left")
|
|
else
|
|
love.graphics.printf("NEXT", 64, 40, 40, "left")
|
|
end
|
|
|
|
love.graphics.print(
|
|
self.das.direction .. " " ..
|
|
self.das.frames .. " " ..
|
|
st(self.prev_inputs) ..
|
|
self.drop_bonus
|
|
)
|
|
|
|
love.graphics.setFont(font_8x11)
|
|
love.graphics.printf(formatTime(self.frames), 64, 420, 160, "center")
|
|
end
|
|
|
|
function GameMode:drawSectionTimes(current_section)
|
|
local section_x = 530
|
|
|
|
for section, time in pairs(self.section_times) do
|
|
if section > 0 then
|
|
love.graphics.printf(formatTime(time), section_x, 40 + 20 * section, 90, "left")
|
|
end
|
|
end
|
|
|
|
love.graphics.printf(formatTime(self.frames - self.section_start_time), section_x, 40 + 20 * current_section, 90, "left")
|
|
end
|
|
|
|
function GameMode:drawSectionTimesWithSecondary(current_section)
|
|
local section_x = 530
|
|
local section_secondary_x = 440
|
|
|
|
for section, time in pairs(self.section_times) do
|
|
if section > 0 then
|
|
love.graphics.printf(formatTime(time), section_x, 40 + 20 * section, 90, "left")
|
|
end
|
|
end
|
|
|
|
for section, time in pairs(self.secondary_section_times) do
|
|
if section > 0 then
|
|
love.graphics.printf(formatTime(time), section_secondary_x, 40 + 20 * section, 90, "left")
|
|
end
|
|
end
|
|
|
|
local current_x
|
|
if table.getn(self.section_times) < table.getn(self.secondary_section_times) then
|
|
current_x = section_x
|
|
else
|
|
current_x = section_secondary_x
|
|
end
|
|
|
|
love.graphics.printf(formatTime(self.frames - self.section_start_time), current_x, 40 + 20 * current_section, 90, "left")
|
|
end
|
|
|
|
function GameMode:drawSectionTimesWithSplits(current_section)
|
|
local section_x = 440
|
|
local split_x = 530
|
|
|
|
local split_time = 0
|
|
|
|
for section, time in pairs(self.section_times) do
|
|
if section > 0 then
|
|
love.graphics.printf(formatTime(time), section_x, 40 + 20 * section, 90, "left")
|
|
split_time = split_time + time
|
|
love.graphics.printf(formatTime(split_time), split_x, 40 + 20 * section, 90, "left")
|
|
end
|
|
end
|
|
|
|
love.graphics.printf(formatTime(self.frames - self.section_start_time), section_x, 40 + 20 * current_section, 90, "left")
|
|
love.graphics.printf(formatTime(self.frames), split_x, 40 + 20 * current_section, 90, "left")
|
|
end
|
|
|
|
function GameMode:drawCustom() end
|
|
|
|
return GameMode
|