Although it seems simple to do, it is one of the jobs that has cost me the most and of which I am most proud. It was complex (for me) to understand all the logic for collisions, rotations, row deletion, part movement, limits, etc.
Among the features of the game we find:
- Sounds: background music, sound when the piece cannot be rotated, when a complete row is made and when the tetromino touches the ground
- Colors: each piece has a random color chosen at runtime
- Rotations: pieces can be rotated to accommodate them and accumulate points
- Mobile compatible: because it is web, I have added some buttons to be able to play it on mobile phones and tablets, but it can also be played with the keyboard
- Open source: you can modify the game, the board, the length, speed, pieces, rotations, etc.
- Tetris port: behaves like any normal tetris game
- Game pause: the game can be paused or resumed at any time
Let’s see then the details of this game programmed in JS. Throughout the post I will show you how this game is programmed, I will also leave you a demo and the complete code which is FOSS.
Note: figure, piece and tetromino will be used synonymously in this post.
General algorithm of the Tetris game
The algorithm is simple. We have a Cartesian plane where the coordinates
Y exist. We have 3 things:
- Game board: the board where everything will be drawn or displayed
- Existing pieces: the points or pieces that have already fallen before; that is, the figures that stayed there
- Current piece: the piece that is currently going down and that the player can move or rotate
Now we do the following: we draw the game board (empty), then we overlap the existing pieces and finally we place the current piece (the one that is going down).
In each movement or attempted rotation, we check if the piece does not collapse with the wall or with another figure below. When the part is rotated, we do a simulation to see if the points, after being rotated, will not collapse with another part.
Furthermore, when it is detected that the piece has hit the ground, a timer is started that will put the next piece in certain milliseconds (this way the player has time to move the piece).
Before displaying another shape, the points of the current shape are moved to the existing parts.
The rest are collisions and work with arrays. For example, to check if a row is full, we go through each point of the array at a certain position of Y and check if it is taken.
All the game drawing is done in a
The game has various sounds. All sounds are injected and hidden in the DOM. They are init like this:
In this case the background sound is reproduced in a loop, so that it repeats infinitely. Then, we reproduce it like this (line 1):
A point has two things: X and Y coordinates. And a Tetromino has several points that make it up.
Like I said, a tetris figure is made up of several points. In addition, it has several rotations. The Z, for example, has only 2, but the J has 4. The rotations are also defined as a Tetromino, and they are exchanged when rotating.
To keep track of the rotation the figure is in, an index is kept. The rotations are nothing more than an array that has several Tetrominos, which represents all possible rotations.
As you can see, the color is chosen in line 6. At the moment of choosing the figure, each point of each rotation of the same is colored with the random color.
Before continuing, let’s look at the useful functions. Among them we have the function that loads the sound, the one that chooses a random color or the one that chooses a random number to know which figure to choose:
For example, the
getRandomColor function chooses a random color from the static array of the
Game class that we will see next. And this function reuses the function called
getRandomNumberInRange which returns a number in a certain range.
Let’s start by looking at the constants of the game, such as the size of the board, the colors, the score that is given to the user when he makes a row of points or the random colors to choose from:
The color array can be modified to your liking, either by changing the colors or adding more, to add randomness.
We also have certain interesting parameters such as the size of each square in pixels (this affects the size of the board) or the milliseconds to indicate the speed at which the piece moves down, the duration of the animation or the time the player has to move the piece if it has touched the ground.
Game init function
It all starts in the init function, but the constructor where we define several things is also important:
The game receives the id of the canvas where it is going to be drawn. In this way we could make them two Tetris game boards, using the same code.
We also have various flags, the definition of the board, the sounds, and so on. Now let’s see the init and restart of this open source tetris game:
One important thing to note here is the
syncExistingPiecesWithBoard function. What this function does is clean the board and place on it (that is, modify the indexes of the array) the existing pieces.
Draw on canvas
The function that draws the entire tetris game on the canvas is the following. It will be invoked every 17 milliseconds using
This function is the one that you can modify if you want to draw the game in another place; for example in a table, with SVG, in the console, and so on.
Get random figure
We have the function where we define the figures and their rotations. This method will return a random Tetromino on each invocation:
Right here is where we are using the
Point class and the
Tetromino class. Remember that each Tetromino will receive an array of all possible rotations. And each rotation is in turn an array of points that have different coordinates.
These coordinates are not modified internally at the point, but are placed from a global X and Y on the board.
If you want to define other shapes or modify the rotations, this is where you have to make the changes.
Collisions and movements
I know that there are engines for video game development but in this case I wanted to do everything by hand. Therefore I have created my own functions to know if a point is out of bounds, if a point is valid, and so on.
Let’s first look at the point collision functions that check the existing board and pieces:
We are using the global coordinates of X and also of Y, because remember that each point in the figure is independent.
Now let’s see the functions to move or rotate the figure:
What we do is simulate the movement and check if each point is still valid. We also play a sound in case the figure cannot be rotated.
Deleting full rows
This is one of the functions that took me the most work to program. What it does is check and remove the filled rows. It looks like this:
We must obtain the Y coordinate of all the rows that are already filled. For each one, we check if we can move all the points down. In case they are, we lower them, but we do not lower them beyond the number of deleted rows.
Also, this feature increases the score. The function that refreshes it is:
The score is calculated according to the number of points that were removed multiplied by the score per square removed.
Now let’s look at the game loop. According to the interval in milliseconds defined above, we are lowering the tetromino while possible. In case the piece can no longer be moved, a timer is started to give the player an opportunity to move the piece.
When the timer runs out, if the tetromino still can’t move, we add the current part to the existing parts and then select another shape:
It is also in this loop where we check if the player loses.
I must admit that this feature needs to be improved as it currently checks for pieces in the second row, but it should be smarter.
Each button has an id, which I then retrieve within the game using
Now let’s see the controls, both the keyboard and the buttons. Each invokes the “try to move right” functions and all other positions:
Here you can see that we use the
canPlay flag, which we deactivate or activate according to our convenience. For example, it cannot be played while the game is paused or while the row removal animation is playing.
Putting it all together
I can’t put or explain all the code here. Remember that the only thing we need is a canvas where we can draw the whole set of blocks. The rest is in the code, especially in the
Game class. You are free to explore it.
It really took me a lot of time and effort to make this game; you can see all its evolution through commits.
I leave you a YouTube video for the demonstration (in spanish):