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
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.
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:
w
and h
- dimensions of the game fieldpieces
- array of all possible pieces that participate in the game. Originally Tetris is played with 7 tetraminos - connected pieces that contain 4 elements. I somehow missed one of them and also made a bar containing 3 elements only.colors
- array of used colors. Here you can see connection between pieces
and colors
- numbers in pieces
are mapped to index of a color in colors
array. This way we can make every piece in different colortr
and td
elements manually, but let's do it using JavaScript in init
function.
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).
value
- actual value of a cell of the game field. Note that 0
represents empty cellelement
- referece to td
HTML element. We will need it to paint it to different colors
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:
field
- our game field (the one created in init
function)piece
- current piece that is moving downpX
and pY
- coordinates of top-left corner of the moving piecefield
has element
that is a reference to corresponding HTML element. Also it has value
- it is index of a color from colors
array.
drawCell
can be useful.
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:
fullRow
returns true
if all elements of row
array are not zero. In other words it returns true
when there are no empty cells in the rowmoveDown
function replaces current row with index y
with a row just above it
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 | ||
C | D |
C | B | A |
D | ||
newX = 3 - Y
newY = X
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:
Sources: https://github.com/5minute/tetris
See live results:https://jsfiddle.net/3u6yLna1/