Tetris in 5 minutes

About

Finally this project is here!
I tried to pospone it as long as I can because I was not sure that it can be done in 5 minutes. But after several attempts and with some mini-cheating (like speeding up part of the video) I almost managed to fit into 5 minutes.
Long story short - today we will talk about Tetris! It's a classical game originating from 1984. It was created by Soviet programmer and quite quickly it gained a lot of popularity and fans all over the world.
In our project we will implement a simplified clone of this game using JavaScript. If you followed other projects on this site or on Youtube channel then you will find a lot of familiar stuff.

Link to Wikipedia: https://en.wikipedia.org/wiki/Tetris

Live coding

Here is a live coding session that shows how it can be done.

Duration: 5:30 (coding - 5:18)

Disclaimer: never use this code in production. It was created for fun.

Breakdown

Let's break down the solution and comment on some complex or interesting things.

0:05 - basis of the game field is an HTML table with 20 rows and 10 columns. We are putting a 5 pixel border around it and define the style for a cell.
Few variables are defined:


Next - the game field. Obviously we can create all tr and td elements manually, but let's do it using JavaScript in init function.
Few words about this function. What it does is just creates HTML tr elements (they represent table's rows) and attaches them to the table. Same is done for cells - td elements are created and attached to corresponding tr elements (rows).
The function returns a two-dimensional array (20 by 10 - same as HTML table) that will represent the game field where each element is an object with 2 fields: Note that this part of the video is speed up 4 times - mainly to save time on boilerplate code.

0:40 - next thing we need to do is to get ability to draw the current game position. For that purpose we define draw function with the following arguments:

Here we do some very simple stuff - just update every table's cell with a corresponding color. Remember that each element of field has element that is a reference to corresponding HTML element. Also it has value - it is index of a color from colors array.
First we need to draw the field and then draw the current piece. To save some code and time a helper function drawCell can be useful.
When drawing the piece we need to take care of 2 things:

1:27 - now we can get to main game loop. It will be imlemented in game function.
It does 2 things now - initializes the game field (by calling init function) and running a scheduled task that is started using setInterval.
Most interesting things are happening inside callback of setInterval that is executed every 300ms.
First of all - it calls draw function to render the current game state.
Also it checks if the current piece exists or not. If not - it creates a random piece. Why do we need it? You'll see in a bit.

1:56 - next building block we need to get is how to determine if a piece can be located on a game field. It is important for different scenarios. For example - when a piece is moving down we need to detect a moment when it cannot move any more. Same for moving left or right. As you see it's crucial block of our implementation.
Actually it's not that complex as it might seem. Imagine we have a piece which top left corner is (pX, pY). What we need to check if all non-empty cells of a piece are within game field (i.e. do not go out left, right or bottom) and don't clash with existing filled cells on the game field.
Once we understand this logics the implementation becomes easier.

2:25 - with validPosition function at hand we can finally implement some dynamic parts.
We will start with moving the piece down until it reaches bottom of the game field.
To do it we just need to increment pY by one. Once we detect that we reached the bottom of the field (or in other words - validPosition call returns false) it means that we need to start with a new piece from the top of the game field.

2:38 - right now our pieces just move down with a constant speed. Next step would be able to move them left or right and drop down.
Here we will implement a keydown handler that is attached to the HTML document. We will handle key codes 37 (left), 39 (right) and 32 (space).
In all cases we are using validPosition to check if the move we are intending to do are valid.
After that we can check in the browser that movements are working on.

3:09 - right now when a piece has reached bottom of the game field it just disappears. Time to fix it.
When a piece has reached its terminal position we need to save it on the game field. This is done in putPiece function.
The logics is quite similar to validPosition - we need to care about game field boundaries and ignore empty cells of a piece.
After this step the pieces can be stacked up and the gameplay starts to look like real Tetris game!

3:45 - next quick thing we can do is to check if game is over or not. Actually is't very simple to do by just reusing validPosition function.
Indeed, when a time comes to generate new piece (and put it to the top of the screen) and it comes to "invalid" position, i.e. clashing with already placed pieces - this is a "Game over" situation.
In this simplified implementation we just show window.alert and reload browser window. Pretty easy solution.

4:06 - at this stage we cannot win the game. Or at least make it last for some significant time. It's because rows are not disappearing. Time to fix it.
clearLines function does exactly this. It uses 2 helper functions:

With these 2 helper functions it becomes an easy task:

4:40 - and probably the last remaining bit of the game - ability to rotate the piece.
Note that all pieces can be fit into 2 by 2 or 3 by 3 matrix. So we just need to rotate them somehow. Let's take a look at the following rotation scheme.
Initial position:

A
B
CD
After rotation:
CBA
D
The logics is clear - left column becomes top row, but inverted.
Or we can write the following transformation:

5:15 - time to check the final result in the browser and make sure that everything is working fine.

This was a fun project and I really enjoyed working on it.
At some point of time I realized that it is more simple that it looks from the first sight - it's more about properly dividing it into smaller tasks and effectively implementing them.
There are few things that can be improved though - I will leave it as homework for you:

Resources

Sources: https://github.com/5minute/tetris

See live results:https://jsfiddle.net/3u6yLna1/