The Board

The board in Tetris consists of cells, which are either occupied or not. My first thought was to represent a cell with boolean values. But, we can do better by using numbers. We can represent an empty cell with 0, and the colors with numbers 1–7.

The next concept is representing the rows and columns of the game board. We can use an array of numbers to represent a row. And the board is an array of rows. In other words, a two dimensional (2D) array or what we call a matrix.

The board is a good candidate for a class . We probably wan’t to create a new Board when starting a new game. If you wan’t to learn more about classes I have written a short article about them:

Let’s create a function in board.js that returns an empty board with all cells set to zero. The fill() method comes in handy here:

We can call this function in main.js when we press play:

By using console.table we see the representation of the board in numbers:

The X and Y coordinates represent the cells of the board. Now that we have the board, let’s take a look at the moving parts.

Tetrominos

A piece in Tetris is a shape consisting of four blocks that move as a unit. They are often called tetrominos and come in seven different patterns and colors. The names I, J, L, O, S, T, and Z are from the resemblance in their shape.

We represent the J tetromino as a matrix where the number two represents the colored cells. We add the row of zeros to get a center to rotate around:

[2, 0, 0],

[2, 2, 2],

[0, 0, 0];

The tetrominos spawn horizontally with J, L, and T spawning flat-side first.

We want the Piece class to know its position on the board, what color it has, and its shape. So to be able to draw itself on the board, it needs a reference to the canvas context.

For starters, we can hard-code the values of our piece in the constructor of the Piece class:

To draw the tetromino on the board, we loop through all the cells of the shape. If the value in the cell is greater than zero, then we color that block.

The board keeps track of the tetromino on the board so we can create and paint it when we press the play button:

The blue J tetromino appears!

Next, let’s make magic happen through the keyboard.

Keyboard input

We need to connect the keyboard events to move the piece on the board. The move function changes the x or y variable of the current piece to change its position on the board.

move(p) {

this.x = p.x;

this.y = p.y;

}

Enums

Next, we map the keys to the key codes in constants.js . For this, it would be nice to have an enum.

Enum (enumeration) is a special type used to define collections of constants.

There are no built-in enums in JavaScript so let’s make one by creating an object with the values:

The const can be a bit misleading when working with objects and arrays and does not actually make them immutable. To achieve this, we can use Object.freeze(). A couple of gotchas here are:

For this to work properly, we need to use strict mode.

This only works one level down. In other words, if we have an array or object inside our object, then this does not freeze them.

Object literals

To match the key events to actions, we can use object literal lookups.

ES6 allows property keys of object literals to use expressions, making them computed property keys.

We need the brackets to get computed property names so that we can use our constants. This is a simplified example of how it works:

const X = 'x';

const a = { [X]: 5 };

console.log(a.x); // 5

We want to send in the current tetromino and return a copy of it together with the change in coordinates. For this, we can use the spread operator to get a shallow copy and then change the coordinates to our desired position.

In JavaScript, we can use shallow copying to copy primitive data types like numbers and strings. In our case, the coordinates are numbers. ES6 offers two shallow copy mechanisms: Object.assign() and the spread operator.

In other words, a lot is going on in this code snippet:

Which we can use with the code beneath to get the new state without mutating the original piece. It’s important because we don’t always want to move to a new position.

const p = this.moves[event.key](this.piece);

Next, we add an event listener that listens to keydown events:

Now we are listening to the keyboard events, and if we press left, right, or down arrows, then we can see the piece moving.

We have a movement! However, ghost pieces going through walls are not what we want.