In my Tetris clone, after some amount of play time, my board state will become corrupted so that when a Tetromino is placed, all the tiles in that Tetromino's columns will act as if they are filled despite no direct calls to fill the tiles. Once the bug occurs, any new Tetromino placed will cause the bug to repeat. (In that columns will continue to fill until game over) Note the state shift below between two pieces being locked. The right most column is suddenly completely filled even though we can see that we only locked a Tetromino made from 4 tiles.
Code: Select all
[Locking] X: 2, Y: 10
[Locking] X: 3, Y: 10
[Locking] X: 4, Y: 10
[Locking] X: 4, Y: 11
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0
0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0
0 | 1 | 1 | 1 | 0 | 1 | 0 | 1 | 1 | 0
0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1
0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1
0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1
0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1
0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1
1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0
0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1
0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1
0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1
0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1
Gameboard W: 10, H: 20
[Locking] X: 9, Y: 8
[Locking] X: 10, Y: 8
[Locking] X: 10, Y: 9
[Locking] X: 10, Y: 10
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1
0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1
0 | 1 | 1 | 1 | 0 | 1 | 0 | 1 | 1 | 1
0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1
0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1
0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1
0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1
0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1
1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0
0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1
0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1
0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1
0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1
Gameboard W: 10, H: 20
Code: Select all
-- In tetromino.lua
local function lockTetromino(tetromino, gameBoard)
for y, row in ipairs(tetromino.blueprint) do
for x, value in ipairs(row) do
if value == 1 then
local boardX = tetromino.x + x - 1
local boardY = tetromino.y + y - 1
print("[Locking] X: " .. boardX .. ", Y: " .. boardY)
if boardX >= 1 and boardX <= gameBoard.width and boardY >= 1 and boardY <= gameBoard.height then
gameBoard.data[boardY][boardX] = { filled = true, color = tetromino.color }
end
end
end
end
tetromino.isLocked = true
end
Code: Select all
function gameBoard:clearRows()
local clearable = {}
-- Identify full rows starting from the bottom
for y = self.height, 1, -1 do
local isFull = true
for x = 1, self.width do
if not self.data[y][x].filled then
isFull = false
break
end
end
if isFull then
table.insert(clearable, y)
end
end
local clearCount = 0
-- Clear rows and shift everything down
for _, rowIndex in ipairs(clearable) do
local adjustedIndex = rowIndex + clearCount
for i = adjustedIndex, 2, -1 do
self.data[i] = self.data[i - 1]
end
-- Clear the top row after shifting everything down
for j = 1, self.width do
self.data[1][j] = { filled = false, color = nil } -- Clear the top row correctly
end
clearCount = clearCount + 1
end
return #clearable
end
Code: Select all
function game:draw()
gameBoard:draw()
if ActivePiece and not ActivePiece.isLocked then
ActivePiece:draw(gameBoard)
end
end
function game:update(dt, state)
-- Tetromino:update returns true if piece locked
local hasControl = true
if ActivePiece:update(dt, gameBoard, state) then
hasControl = false
gameBoard:clearRows()
gameBoard:print()
print("Gameboard W: " .. gameBoard.width .. ", H: " .. gameBoard.height)
print("\n")
local r = love.math.random(1, 7)
ActivePiece = tetromino:new(r, math.floor(gameBoard.width / 2))
hasControl = true
end
if hasControl then
if love.keyboard.isDown("a") then
ActivePiece:move(dt, -1, gameBoard)
elseif love.keyboard.isDown("d") then
ActivePiece:move(dt, 1, gameBoard)
elseif love.keyboard.isDown("s") then
ActivePiece:increaseDropSpeed()
end
end
end
Code: Select all
-- from tetromino.lua
function Tetromino:draw(gameBoard)
love.graphics.setColor(table.unpack(self.color))
for y, row in ipairs(self.blueprint) do
for x, val in ipairs(row) do
if val == 1 then
-- Calculate the actual position on the screen
local drawX = gameBoard.offsetX + (self.x + (x - 2)) * gameBoard.tileSize
local drawY = gameBoard.offsetY + (self.y + (y - 2)) * gameBoard.tileSize
love.graphics.rectangle("fill", drawX, drawY, gameBoard.tileSize, gameBoard.tileSize)
end
end
end
end
--from game_board.lua
function gameBoard:draw()
-- First: draw the background grid
love.graphics.setColor(1, 1, 1)
for y = 1, self.height do
for x = 1, self.width do
-- Draw the grid lines. Reduce x and y offset by 1 for correct alignment
love.graphics.rectangle(
"line",
self.offsetX + (x - 1) * self.tileSize,
self.offsetY + (y - 1) * self.tileSize,
self.tileSize,
self.tileSize
)
-- Draw the locked Tetromino blocks if filled
if self.data[y][x].filled then
local drawX = self.offsetX + (x - 1) * self.tileSize
local drawY = self.offsetY + (y - 1) * self.tileSize
love.graphics.setColor(table.unpack(self.data[y][x].color))
love.graphics.rectangle("fill", drawX, drawY, self.tileSize, self.tileSize)
love.graphics.setColor(1, 1, 1)
end
end
end
end