Problem with eventual corrupted state in tetris
Posted: Sun Aug 18, 2024 6:22 pm
The Problem:
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.
For some more info. This function is what locks the tetromino. It's fired when the piece can no longer fall.
This function is responsible for clearing rows as they fill, and it is called whenever a piece is locked to the game board
And that function is only called once a lock is detected from the active tetromino piece in my game state:
The Tetromino itself only draws itself when it is the active tetromino. Otherwise, the board handles drawing the already locked pieces.
I'm really confused by this bug. Am I missing something obvious? The game works fine for awhile, but then out of the the blue this bug occurs. It doesn't seem to involved the clearRows function as that function seems to correct the corrupted board state until a new piece is added. This is my first post and only my third (simple) love2d game, so lua and love is new to me. I wanted to ask you all if you have ever experienced issues when updating 2D arrays, working in tile based systems like this, or if there is some Lua quirks I should be aware of. Also any debugging tips would be appreciated, as of right now the only strategy I really I have for this is print statements.
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