cambridge/tetris/components/grid.lua
2023-12-31 08:50:23 -08:00

601 lines
16 KiB
Lua

local Object = require 'libs.classic'
local Grid = Object:extend()
local drawBlock = require 'tetris.components.draw_block'
local empty = { skin = "", colour = "" }
local oob = { skin = "", colour = "" }
local block = { skin = "2tie", colour = "A" }
function Grid:new(width, height)
self.grid = {}
self.grid_age = {}
self.width = width
self.height = height
for y = 1, self.height do
self.grid[y] = {}
self.grid_age[y] = {}
for x = 1, self.width do
self.grid[y][x] = empty
self.grid_age[y][x] = 0
end
end
end
function Grid:clear()
for y = 1, self.height do
for x = 1, self.width do
self.grid[y][x] = empty
self.grid_age[y][x] = 0
end
end
end
function Grid:getCell(x, y)
if x < 1 or x > self.width or y > self.height then return oob
elseif y < 1 then return empty
else return self.grid[y][x]
end
end
function Grid:isOccupied(x, y)
return self:getCell(x+1, y+1) ~= empty
end
function Grid:isRowFull(row)
for index, square in pairs(self.grid[row]) do
if square == empty then return false end
end
return true
end
function Grid:canPlacePiece(piece)
if piece.big then
return self:canPlaceBigPiece(piece)
end
local offsets = piece:getBlockOffsets()
for index, offset in pairs(offsets) do
local x = piece.position.x + offset.x
local y = piece.position.y + offset.y
if self:isOccupied(x, y) then
return false
end
end
return true
end
function Grid:canPlaceBigPiece(piece)
local offsets = piece:getBlockOffsets()
for index, offset in pairs(offsets) do
local x = piece.position.x + offset.x
local y = piece.position.y + offset.y
if (
self:isOccupied(x * 2 + 0, y * 2 + 0)
or self:isOccupied(x * 2 + 1, y * 2 + 0)
or self:isOccupied(x * 2 + 0, y * 2 + 1)
or self:isOccupied(x * 2 + 1, y * 2 + 1)
) then
return false
end
end
return true
end
function Grid:canPlacePieceInVisibleGrid(piece)
if piece.big then
return self:canPlaceBigPiece(piece)
-- forget canPlaceBigPieceInVisibleGrid for now
end
local offsets = piece:getBlockOffsets()
for index, offset in pairs(offsets) do
local x = piece.position.x + offset.x
local y = piece.position.y + offset.y
if y < 4 or self:isOccupied(x, y) ~= empty then
return false
end
end
return true
end
function Grid:getClearedRowCount()
local count = 0
local cleared_row_table = {}
for row = 1, self.height do
if self:isRowFull(row) then
count = count + 1
table.insert(cleared_row_table, row)
end
end
return count, cleared_row_table
end
function Grid:markClearedRows()
local block_table = {}
for row = 1, self.height do
if self:isRowFull(row) then
block_table[row] = {}
for x = 1, self.width do
block_table[row][x] = {
skin = self.grid[row][x].skin,
colour = self.grid[row][x].colour,
}
self.grid[row][x] = {
skin = self.grid[row][x].skin,
colour = "X"
}
--self.grid_age[row][x] = 0
end
end
end
return block_table
end
function Grid:clearClearedRows()
for row = 1, self.height do
if self:isRowFull(row) then
for above_row = row, 2, -1 do
self.grid[above_row] = self.grid[above_row - 1]
self.grid_age[above_row] = self.grid_age[above_row - 1]
end
self.grid[1] = {}
self.grid_age[1] = {}
for i = 1, self.width do
self.grid[1][i] = empty
self.grid_age[1][i] = 0
end
end
end
return true
end
function Grid:copyBottomRow()
for row = 1, self.height - 1 do
self.grid[row] = self.grid[row+1]
self.grid_age[row] = self.grid_age[row+1]
end
self.grid[self.height] = {}
self.grid_age[self.height] = {}
for i = 1, self.width do
self.grid[self.height][i] = (self.grid[self.height - 1][i] == empty) and empty or block
self.grid_age[self.height][i] = 0
end
return true
end
function Grid:garbageRise(row_vals)
for row = 1, self.height - 1 do
self.grid[row] = self.grid[row+1]
self.grid_age[row] = self.grid_age[row+1]
end
self.grid[self.height] = {}
self.grid_age[self.height] = {}
for i = 1, self.width do
self.grid[self.height][i] = (row_vals[i] == "e") and empty or block
self.grid_age[self.height][i] = 0
end
end
function Grid:clearSpecificRow(row)
for col = 1, self.width do
self.grid[row][col] = empty
end
end
function Grid:clearBlock(x, y)
self.grid[x+1][y+1] = empty
end
function Grid:clearBottomRows(num)
local old_isRowFull = self.isRowFull
self.isRowFull = function(self, row)
return row >= self.height + 1 - num
end
self:clearClearedRows()
self.isRowFull = old_isRowFull
end
function Grid:applyPiece(piece)
if piece.big then
self:applyBigPiece(piece)
return
end
offsets = piece:getBlockOffsets()
for index, offset in pairs(offsets) do
x = piece.position.x + offset.x
y = piece.position.y + offset.y
if y + 1 > 0 and y < self.height then
self.grid[y+1][x+1] = {
skin = piece.skin,
colour = piece.colour
}
end
end
end
function Grid:applyBigPiece(piece)
offsets = piece:getBlockOffsets()
for index, offset in pairs(offsets) do
x = piece.position.x + offset.x
y = piece.position.y + offset.y
for a = 1, 2 do
for b = 1, 2 do
if y*2+a > 0 and y*2 < self.height then
self.grid[y*2+a][x*2+b] = {
skin = piece.skin,
colour = piece.colour
}
end
end
end
end
end
-- places where you see this take an argument used the old, buggy method
function Grid:checkForBravo()
for i = 0, self.height - 1 do
if not self:isRowFull(i+1) then
for j = 0, self.width - 1 do
if self:isOccupied(j, i) then return false end
end
end
end
return true
end
function Grid:checkStackHeight()
for i = 0, self.height - 1 do
for j = 0, self.width - 1 do
if self:isOccupied(j, i) then return self.height - i end
end
end
return 0
end
function Grid:checkSecretGrade()
local sgrade = 0
for i=23,5,-1 do
local validLine = true
local emptyCell = 0
if i > 13 then
emptyCell = 23-i
end
if i <= 13 then
emptyCell = i-5
end
for j=0,9 do
if (not self:isOccupied(j,i) and j ~= emptyCell) or (j == emptyCell and self:isOccupied(j,i)) then
validLine = false
end
end
if not self:isOccupied(emptyCell,i-1) then
validLine = false
end
if(validLine) then
sgrade = sgrade + 1
else
return sgrade
end
end
--[[
if(sgrade == 0) then return ""
elseif(sgrade < 10) then return 10-sgrade
elseif(sgrade < 19) then return "S"..(sgrade-9) end
return "GM"
--]]
return sgrade
end
function Grid:hasGemBlocks()
for y = 1, self.height do
for x = 1, self.width do
if self.grid[y][x].skin == "gem" then
return true
end
end
end
return false
end
function Grid:mirror()
local new_grid = {}
for y = 1, self.height do
new_grid[y] = {}
for x = 1, self.width do
new_grid[y][x] = empty
end
end
for y = 1, self.height do
for x = 1, self.width do
new_grid[y][x] = self.grid[y][self.width + 1 - x]
end
end
self.grid = new_grid
end
function Grid:applyMap(map)
for y, row in pairs(map) do
for x, block in pairs(row) do
self.grid_age[y][x] = 0
self.grid[y][x] = block
end
end
end
-- inefficient algorithm for squares
function Grid:markSquares()
-- goes up by 1 for silver, 2 for gold
local square_count = 0
for i = 1, 2 do
for y = 5, self.height - 3 do
for x = 1, self.width - 3 do
local age_table = {}
local age_count = 0
local colour_table = {}
local is_square = true
for j = 0, 3 do
for k = 0, 3 do
if self.grid[y+j][x+k].skin == "" or self.grid[y+j][x+k].skin == "square" then
is_square = false
end
if age_table[self.grid_age[y+j][x+k]] == nil then
age_table[self.grid_age[y+j][x+k]] = 1
age_count = age_count + 1
else
age_table[self.grid_age[y+j][x+k]] = age_table[self.grid_age[y+j][x+k]] + 1
end
if age_count > 4 or age_table[self.grid_age[y+j][x+k]] > 4 then
is_square = false
end
if not table.contains(colour_table, self.grid[y+j][x+k].colour) then
table.insert(colour_table, self.grid[y+j][x+k].colour)
end
end
end
if is_square then
if i == 1 and #colour_table == 1 then
for j = 0, 3 do
for k = 0, 3 do
self.grid[y+j][x+k].colour = "Y"
self.grid[y+j][x+k].skin = "square"
end
end
square_count = square_count + 2
elseif i == 2 then
for j = 0, 3 do
for k = 0, 3 do
self.grid[y+j][x+k].colour = "W"
self.grid[y+j][x+k].skin = "square"
end
end
square_count = square_count + 1
end
end
end
end
end
return square_count
end
-- square scan
function Grid:scanForSquares()
local table = {}
for row = 1, self.height do
local silver = 0
local gold = 0
for col = 1, self.width do
local colour = self.grid[row][col].colour
if self.grid[row][col].skin == "square" then
if colour == "Y" then gold = gold + 1
else silver = silver + 1 end
end
end
table[row] = gold * 2.5 + silver * 1.25
end
return table
end
function Grid:update()
for y = 1, self.height do
for x = 1, self.width do
if self.grid[y][x] ~= empty then
self.grid_age[y][x] = self.grid_age[y][x] + 1
end
end
end
end
function Grid:draw()
is_3d = is_3d == nil and false or is_3d
for y = 5, self.height do
for x = 1, self.width do
if blocks[self.grid[y][x].skin] and
blocks[self.grid[y][x].skin][self.grid[y][x].colour] then
if self.grid_age[y][x] < 2 then
drawBlock(1, 1, 1, 1, blocks[self.grid[y][x].skin]["F"], 48+x*16, y*16,
48, 48+(self.width+1)*16,
5*16, (self.height+1)*16
)
else
local R, G, B, A
if self.grid[y][x].colour == "X" then
R = 0
G = 0
B = 0
A = 0
elseif self.grid[y][x].skin == "bone" then
r = 1
G = 1
B = 1
A = 1
else
R = 0.5
G = 0.5
B = 0.5
A = 1
end
drawBlock(R, G, B, A, blocks[self.grid[y][x].skin][self.grid[y][x].colour], 48+x*16, y*16,
48, 48+(self.width+1)*16,
5*16, (self.height+1)*16
)
end
end
end
end
-- everything that needs to be drawn over the blocks *must* be drawn after all blocks have been drawn, due to how 3D works
for y = 5, self.height do
for x = 1, self.width do
if blocks[self.grid[y][x].skin] and
blocks[self.grid[y][x].skin][self.grid[y][x].colour] then
if self.grid[y][x].skin ~= "bone" and self.grid[y][x].colour ~= "X" then
love.graphics.setColor(0.8, 0.8, 0.8, 1)
love.graphics.setLineWidth(1)
if y > 5 and self.grid[y-1][x] == empty or self.grid[y-1][x].colour == "X" then
love.graphics.line(48.0+x*16, -0.5+y*16, 64.0+x*16, -0.5+y*16)
end
if y < self.height and self.grid[y+1][x] == empty or
(y + 1 <= self.height and self.grid[y+1][x].colour == "X") then
love.graphics.line(48.0+x*16, 16.5+y*16, 64.0+x*16, 16.5+y*16)
end
if x > 1 and self.grid[y][x-1] == empty then
love.graphics.line(47.5+x*16, -0.0+y*16, 47.5+x*16, 16.0+y*16)
end
if x < self.width and self.grid[y][x+1] == empty then
love.graphics.line(64.5+x*16, -0.0+y*16, 64.5+x*16, 16.0+y*16)
end
end
end
end
end
end
function Grid:drawOutline()
for y = 5, self.height do
for x = 1, self.width do
if self.grid[y][x] ~= empty and self.grid[y][x].colour ~= "X" then
love.graphics.setColor(0.8, 0.8, 0.8, 1)
love.graphics.setLineWidth(1)
if y > 5 and self.grid[y-1][x] == empty or self.grid[y-1][x].colour == "X" then
love.graphics.line(48.0+x*16, -0.5+y*16, 64.0+x*16, -0.5+y*16)
end
if y < self.height and self.grid[y+1][x] == empty or
(y + 1 <= self.height and self.grid[y+1][x].colour == "X") then
love.graphics.line(48.0+x*16, 16.5+y*16, 64.0+x*16, 16.5+y*16)
end
if x > 1 and self.grid[y][x-1] == empty then
love.graphics.line(47.5+x*16, -0.0+y*16, 47.5+x*16, 16.0+y*16)
end
if x < self.width and self.grid[y][x+1] == empty then
love.graphics.line(64.5+x*16, -0.0+y*16, 64.5+x*16, 16.0+y*16)
end
end
end
end
end
function Grid:drawInvisible(opacity_function, garbage_opacity_function, lock_flash, brightness, is_3d)
lock_flash = lock_flash == nil and true or lock_flash
brightness = brightness == nil and 0.5 or brightness
is_3d = is_3d == nil and false or is_3d
for y = 5, self.height do
for x = 1, self.width do
if self.grid[y][x] ~= empty then
if self.grid[y][x].colour == "X" then
opacity = 0
elseif garbage_opacity_function and self.grid[y][x].colour == "A" then
opacity = garbage_opacity_function(self.grid_age[y][x])
else
opacity = opacity_function(self.grid_age[y][x])
end
drawBlock(brightness, brightness, brightness, opacity, blocks[self.grid[y][x].skin][self.grid[y][x].colour], 48+x*16, y*16,
48, 48+(self.width+1)*16,
5*16, (self.height+1)*16
)
end
end
end
-- everything that needs to be drawn over the blocks *must* be drawn after all blocks have been drawn, due to how 3D works
for y = 5, self.height do
for x = 1, self.width do
if self.grid[y][x] ~= empty then
if lock_flash then
if opacity > 0 and self.grid[y][x].colour ~= "X" then
love.graphics.setColor(0.64, 0.64, 0.64)
love.graphics.setLineWidth(1)
if y > 5 and self.grid[y-1][x] == empty or self.grid[y-1][x].colour == "X" then
love.graphics.line(48.0+x*16, -0.5+y*16, 64.0+x*16, -0.5+y*16)
end
if y < self.height and self.grid[y+1][x] == empty or
(y + 1 <= self.height and self.grid[y+1][x].colour == "X") then
love.graphics.line(48.0+x*16, 16.5+y*16, 64.0+x*16, 16.5+y*16)
end
if x > 1 and self.grid[y][x-1] == empty then
love.graphics.line(47.5+x*16, -0.0+y*16, 47.5+x*16, 16.0+y*16)
end
if x < self.width and self.grid[y][x+1] == empty then
love.graphics.line(64.5+x*16, -0.0+y*16, 64.5+x*16, 16.0+y*16)
end
end
end
end
end
end
end
function Grid:drawCustom(colour_function, gamestate, is_3d)
--[[
colour_function: (game, block, x, y, age) -> (R, G, B, A, outlineA)
When called, calls the supplied function on every block passing the block itself as argument
as well as coordinates and the grid_age value of the same cell.
Should return a RGBA colour for the block, as well as the opacity of the stack outline (0 for no outline).
gamestate: the gamemode instance itself to pass in colour_function
]]
is_3d = is_3d == nil and false or is_3d
for y = 5, self.height do
for x = 1, self.width do
local block = self.grid[y][x]
if block ~= empty then
local R, G, B, A, outline = colour_function(gamestate, block, x, y, self.grid_age[y][x])
if self.grid[y][x].colour == "X" then
A = 0
end
drawBlock(R, G, B, A, blocks[self.grid[y][x].skin][self.grid[y][x].colour], 48+x*16, y*16,
48, 48+(self.width+1)*16,
5*16, (self.height+1)*16
)
end
end
end
-- everything that needs to be drawn over the blocks *must* be drawn after all blocks have been drawn, due to how 3D works
for y = 5, self.height do
for x = 1, self.width do
local block = self.grid[y][x]
if block ~= empty then
if outline > 0 and self.grid[y][x].colour ~= "X" then
love.graphics.setColor(0.64, 0.64, 0.64, outline)
love.graphics.setLineWidth(1)
if y > 5 and self.grid[y-1][x] == empty or self.grid[y-1][x].colour == "X" then
love.graphics.line(48.0+x*16, -0.5+y*16, 64.0+x*16, -0.5+y*16)
end
if y < self.height and self.grid[y+1][x] == empty or
(y + 1 <= self.height and self.grid[y+1][x].colour == "X") then
love.graphics.line(48.0+x*16, 16.5+y*16, 64.0+x*16, 16.5+y*16)
end
if x > 1 and self.grid[y][x-1] == empty then
love.graphics.line(47.5+x*16, -0.0+y*16, 47.5+x*16, 16.0+y*16)
end
if x < self.width and self.grid[y][x+1] == empty then
love.graphics.line(64.5+x*16, -0.0+y*16, 64.5+x*16, 16.0+y*16)
end
end
end
end
end
end
return Grid