How can I move values down in this table of arrays?

Hey developers! I’m currently working on a remake of Tetris in Roblox, and the game-board logic is built on a table of arrays.

This table is essentially a grid that recreates the original Tetris board inside the script. A single point can be indexed by using table[num]. (9’s are the borders)

	local board = {
		{9,9,9,9,9,9,9,9,9,9,9,9},
		{9,0,0,0,0,0,0,0,0,0,0,9},
		{9,0,0,0,0,0,0,0,0,0,0,9},
		{9,0,0,0,0,0,0,0,0,0,0,9},
		{9,0,0,0,0,0,0,0,0,0,0,9},
		{9,0,0,0,0,1,0,0,0,0,0,9},
		{9,0,0,0,0,1,0,0,0,0,0,9},
		{9,0,0,0,0,1,0,0,0,0,0,9},
		{9,0,0,0,0,1,0,0,0,0,0,9},
		{9,0,0,0,0,0,0,0,0,0,0,9},
		{9,0,0,0,0,0,0,0,0,0,0,9},
		{9,0,0,0,0,0,0,0,0,0,0,9},
		{9,0,0,0,0,0,0,0,0,0,0,9},
		{9,0,0,0,0,0,0,0,0,0,0,9},
		{9,0,0,0,0,0,0,0,0,0,0,9},
		{9,0,0,0,0,0,0,0,0,0,0,9},
		{9,0,0,0,0,0,0,0,0,0,0,9},
		{9,0,0,0,0,0,0,0,0,0,0,9},
		{9,0,0,0,0,0,0,0,0,0,0,9},
		{9,0,0,0,0,0,0,0,0,1,0,9},
		{9,0,0,0,0,0,0,0,1,1,1,9},
		{9,9,9,9,9,9,9,9,9,9,9,9}
	}

The problem with this method is there’s no way to physically move values down as a group. This same issue comes up regarding rotation of the tetrominoes. I modified a few parts of the table as an example, which are marked as 1’s.

If there’s any better and/or easier alternative methods, feel free to let me know… I’ve never really done anything like this before :sweat_smile:

1 Like
-- so the moving peace should have its own table like this
local peace = {
    positionX = 6,
    positionY = 0,
    rotation = 1
    shapes = {
        {
            {0, 1, 0},
            {1, 1, 1},
            {0, 0, 0},
        },{
            {0, 1, 0},
            {0, 1, 1},
            {0, 1, 0},
        },{
            {0, 0, 0},
            {1, 1, 1},
            {0, 1, 0},
        },{
            {0, 1, 0},
            {1, 1, 0},
            {0, 1, 0},
        }
    }
}

-- and the board like this
local board = {
	{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,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,0,0,0,0,0,0},
	{0,0,0,0,0,0,0,0,1,0},
	{0,0,0,0,0,0,0,1,1,1},
}

so if the game is updating every second
every second you reduce the peace positionY value by 1
then using the peace position to check for collisions if the peace has collided you move it back to the previous position
if the peace has reached the bottom you add the peace shape to the board
if the peace has not reached the bottom you render the board and peace together without merging them

the rotation value can be from 1 to 4 and that will tell you what shape to use

notice how the shape is 3x3 the centre should be the positionX and positionY so that when you rotate it will rotate around the centre

so the long stick will look like this but i dont think this is how the stick rotates in tetras

shapes = {
        {
            {1},
            {1},
            {1},
            {1},
            {0},
        },{
            {0, 1, 1, 1, 1},
        },{
            {0},
            {1},
            {1},
            {1},
            {1},
        },{
            {1, 1, 1, 1, 0},
        }
}

I see how this works, but I have a few questions.

  • How can I easily detect collision?
  • How can I prevent tetrominoes from rotating into other pieces or out of bounds?
  • How can these smaller tables (tetrominoes) be moved into the game board and move down the board easily?
  • How can I prevent the values of 0 in the tetrominoes from tampering with other tetrominoes as they collide?

so to check collisions you do something like this

local function CheckCollision()
    local offsetX = positionX - math.floor(shapeWidth / 2)
    local offsetY = positionY - math.floor(shapeHeight / 2)
    for x = 1, shapeWidth do
        for y = 1, shapeHeight do
            if board[x + offsetX][y + offsetY] == 1 and shape[x][y] == 1 then return true end
        end
    end
    return false
end

after you rotate you check for collisions if there is a collision then you rotate it back

so you will have a list of all the shapes then you just clone a random shape into the peace table and set the positionX and positionY to the start position and simply change the positionX and positionY to move it around you need to check collisions after every movement and rotation

value 0 are ignored in the collision function that i showed above and when you add the shape to the board you only add the 1’s you ignore the 0’s

As a tetromino falls, how can I ensure that the last position resets to 0’s?

Also, what method should be used to put the smaller tables into the game board, while also ignoring the 0’s inside the piece table?

-- move peace down
positionY -= 1
if CheckCollision() == true then
    positionY += 1
    AddPeaceToBoard()
end

-- if the player presses right key
positionX += 1
if CheckCollision() == true then
    positionX -= 1
end

-- if the player presses rotate key
local oldRotation = rotation
rotation += 1
if rotation > 4 then rotation = 1 end
if CheckCollision() == true then
    rotation = oldRotation
end

-- spawn a new peace
peace.shapes = allShapes[math.random(#allShapes)]
peace.positionX = 6
peace.positionY = 0
peace.rotation = 1
if CheckCollision() == true then
    -- gameover there was no space to add the peace
end

and to add the peace to the board

local function AddPeaceToBoard()
    local offsetX = positionX - math.floor(shapeWidth / 2)
    local offsetY = positionY - math.floor(shapeHeight / 2)
    for x = 1, shapeWidth do
        for y = 1, shapeHeight do
            if shape[x][y] == 1 then board[x + offsetX][y + offsetY] = 1 end
        end
    end
end

and to get the shapeWidth and shapeHeight you do

local shapeWidth = #peace.shapes[rotation][1]
local shapeHeight = #peace.shapes[rotation]
--going from bottom up, to avoid erasing data which is needed
--#board-2 to ignore the bottom border and the last line(the last line will get overriden anyway)
for i = #board-2, 2, -1 do 
	local line = board[i] 
	for index, value in pairs(line) do 
		--i+1 to move the value one line down
		board[i+1][index] = value 
	end
end

--[OPTIONAL] fill the first line will zeros
local firstLine = board[2] --2 because of the top border
for i = 2, #firstLine-1 do 
	firstLine[i] = 0
end

Thanks to everyone for your ideas and suggestions, I ended up finding a method that works well for my case. (although it took me a whole day to conjure…)

Here’s a portion of the script I wrote that demonstrates the basics of this method:

--This table of arrays is a visual representation of the tetris game board in the code logic.
local board = {
	{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,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,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}
}

--Rotations always turn counter-clockwise from the top!
local tetros = {
	--'I' Tetromino
	{
		{--Rot1
			{1,1,1,1},
		},
		{--Rot2
			{1},
			{1},
			{1},
			{1}
		},
		{--Rot3
			{1,1,1,1},
		},
		{--Rot4
			{1},
			{1},
			{1},
			{1}
		}
	},
	--'L' Tetromino
	{
		{--Rot1
			{2,0,0},
			{2,2,2},
			{0,0,0}
		},
		{--Rot2
			{0,2,0},
			{0,2,0},
			{2,2,0}
		},
		{--Rot3
			{0,0,0},
			{2,2,2},
			{0,0,2}
		},
		{--Rot4
			{0,2,2},
			{0,2,0},
			{0,2,0}
		}
	},
	--'J' Tetromino
	{
		{--Rot1
			{0,0,3},
			{3,3,3},
			{0,0,0}
		},
		{--Rot2
			{3,3,0},
			{0,3,0},
			{0,3,0}
		},
		{--Rot3
			{0,0,0},
			{3,3,3},
			{3,0,0}
		},
		{--Rot4
			{0,3,0},
			{0,3,0},
			{0,3,3}
		}
	},
	--'O' Tetromino
	{
		{--Rot1
			{4,4},
			{4,4}
		},
		{--Rot2
			{4,4},
			{4,4}
		},
		{--Rot3
			{4,4},
			{4,4}
		},
		{--Rot4
			{4,4},
			{4,4}
		}
	},
	--'S' Tetromino
	{
		{--Rot1
			{0,5,5},
			{5,5,0},
			{0,0,0}
		},
		{--Rot2
			{5,0,0},
			{5,5,0},
			{0,5,0}
		},
		{--Rot3
			{0,5,5},
			{5,5,0},
			{0,0,0}
		},
		{--Rot4
			{5,0,0},
			{5,5,0},
			{0,5,0}
		}
	},
	--'Z' Tetromino
	{
		{--Rot1
			{6,6,0},
			{0,6,6},
			{0,0,0}
		},
		{--Rot2
			{0,6,0},
			{6,6,0},
			{6,0,0}
		},
		{--Rot3
			{6,6,0},
			{0,6,6},
			{0,0,0}
		},
		{--Rot4
			{0,6,0},
			{6,6,0},
			{6,0,0}
		}
	},
	--'T' Tetromino
	{
		{--Rot1
			{0,7,0},
			{7,7,7},
			{0,0,0}
		},
		{--Rot2
			{0,7,0},
			{7,7,0},
			{0,7,0}
		},
		{--Rot3
			{0,0,0},
			{7,7,7},
			{0,7,0}
		},
		{--Rot4
			{0,7,0},
			{0,7,7},
			{0,7,0}
		}
	},
}

local tetro = {
	0;--Tetromino value from table:tetros
	0;--Rotation value of tetromino
	0;--Y position of tetromino
	0;--X position of tetromino
}
local nextTetro

local function SpawnTetro()
	for i,v in pairs(tetro[2]) do--Draws tetromino on table
		local line = board[i]
		local offset = 0
		for ii,vv in pairs(v) do
			if vv ~= 0 then line[tetro[4]+offset] = vv end
			offset += 1
		end
	end
end

local function DefineTetro()
	if nextTetro ~= nil then
		tetro[1] = tetros[nextTetro]--Defines the new tetromino
	else
		tetro[1] = tetros[math.random(1, #tetros)]--Defines the new tetromino
	end
	tetro[2] = (tetro[1])[1]--Defines the rotation (DEFAULT 1)
	tetro[3] = 1--Defines the Y position
	tetro[4] = 4--Defines the X position
	print("Defined current tetromino.")
	nextTetro = tetros[math.random(1, #tetros)]--Defines the next tetromino
	print("Defined next tetromino.")
end

Again, thank you to everyone who replied! :grin: