Coding Tetris In JavaScript Part 1

Written by
Published on
Modified on

Tetris is one of the first games that many of us played growing up. It's fun and challenging and playing a single level can take you from anywhere to a minute to forever if you so play your cards right. So in honor of the game, in this blog post, we'll go over how to build a Tetris clone in JavaScript without using any 3rd party libraries. Just plain old vanilla JavaScript and CSS.

To keep the example as simple as possible, this will be a single level of Tetris game that will reset after a game over state has been achieved.

With that, let's get started. The following is the full "skeleton" view of the game board and the underlying coordinate system that we will be building, just to give you a pre-emtive look at what is to come.

Building Tetris In JavaScript Part 1

Step 0. Defining variables

You can never have too many variables. A motto to code by. This isn't 1950, so memory is more than plentiful. Here are all the variables that will be defined in the making of this game.

var shapes = new Array();
var currentShape;
var height = 15;
var width = 10;
var state = 1;      // 1 running - 0 paused - 2 game over
var colors = ['black', 'orange', 'red', 'blue'];
var move = 0;
var occupiedblocks = new Array();
var direction = "";
var points = 0;

Here is a breakdown of each of the variables and what they are designed to do.

- shapes => a collection of the possible shapes that can be drawn.
- currentShape => the current shape in play
- height => height of the game board
- width => width of the game board
- state => used to monitor the current state of the gameplay
- colors => available colors to use when rendering shapes
- move => tracks the number of moves the user has made so far
- occupiedBlocks => array containing a list of all current blocks on the board that are filled
- direction => 'up', 'down', 'left', 'right' string used to track where to draw the falling block next
- points => running tally of total points

Step 1. Making the Tetris board

First off, let's make the game board where the game will take place. Based on the actual game specs for Tetris, the number of horizontal running boxes is 10 and we can take some liberty with the height. The board itself will be comprised of div elements created dynamically in JavaScript.

HTML
<div class="tetris-board"></div>
JavaScript
function createBoard()
{
    var board = document.getElementsByClassName('tetris-board')[0];
    board.innerHTML = '';
    var counter = 0;
    for (var y = 0; y < height; y++)
    {
        var row = document.createElement('div');
        row.className = 'row';
        row.dataset.row = y;
        
        for (var x = 0; x < width; x++)
        {
            var block = document.createElement('div');
            block.className = 'block';
            block.dataset.x = x;
            block.dataset.y = y;
            block.dataset.index = counter;
            block.dataset.state = 0;
            block.innerHTML = "0 : " + counter;
            row.appendChild(block);
            counter++;
        }
        
        board.appendChild(row);
    }
}

Each block on the grid will contain several dataset properties that will be used later to determine whether the block is occupied and also the index of said block.

In this particular case, we'll set the state to '0' if the block is empty.

Step 2. Creating the Tetris shapes

A Tetris block is essentially a series of 0s and 1s on a coordinate system. You can think of it as something like the following:

[0, 0]
[1, 0]
[2, 0]
[0, 1]
[1, 1]
[2, 1]

If we treat the board as a series of [X, Y] coordinates, then we'll know exactly how to draw each of the shapes based on their own predefined coordinates. Continuing with this logic, we can create the shapes as follows:

function createShapes()
{
    var other = [[1, 0], [0,1], [1,1],[2,1]]; // 't' shape
    var line = [[0, 0], [0, 1], [0, 2], [0, 3]]; // line
    var square = [[0, 0], [0, 1], [1, 0], [1, 1]]; // 4 x 4 square
    var l = [[2,0], [0, 1], [1, 1], [2,1]]; // 'L' shape

shapes.push(square); shapes.push(line); shapes.push(other); shapes.push(l);
}

We run this function once on page load, which will populate the shapes global variable. We'll be able to pull from this array a random index when we render it onto the game board.

Step 3. Draw a shape onto the board

Next up we can begin to draw elements on to the board. Because we can only have one active shape at a time on our board, we can store this in a variable, which I've dubbed currentShape. This variable will get instantiated with a random shape, selected from the shapes array, a random color, selected from the colors array, the location variable which is an array of default [x,y] coordinates, and indexes, which represent all of the blocks that the shape will take up.

function createShape()
{
    var randomShape = Math.floor(Math.random() * shapes.length);
    var randomColor = Math.floor(Math.random() * colors.length);
    var center = Math.floor(width / 2);
    var shape = shapes[randomShape];
    var location = [center, 0];

    currentShape = {
        shape: shape,
        color: colors[randomColor],
        location: location,
        indexes: getBlockNumbers(shape, location)
    };
}

In order to maintain the shapes spatial positioning on the board, we're going to be keeping track of the top-left corner block on the board as the offset. By default, this will be set with a 'y' coordinate of 0 for the top of the board. We won't have to worry about the indexes property for now as that will be used for collision detection later on in the process. That will be covered in Part 2 of this post, which is linked to down below.

For now add the following placeholder function in lieu of that implementation:

function getBlockNumbers(a, b){
	// tba
}
Building Tetris In JavaScript Part 1

Shape Coordinates + offset = actual position

Given the shape coordinates and offset, we can now draw our shape onto the board. This isn't too difficult and will essentially come down to the following steps:

  1. Calculate the board coordinates based on offset and shape selected
  2. For each block in the given shape, set the state of the board block to "occupied", or 1
  3. That's it!

That's pretty much it for actually drawing the shape onto the board. Tetris is a game of toggling 1's and 0's on a timer tick and checking for certain 'collisions'.

The drawShape() function is as follows:

function drawShape()
{
    // draw the current shape onto board
    var shape = currentShape.shape;
    var location = currentShape.location;

    // update status to unoccupied of current block
    clearCurrent();

    // based on direction of block, set the offset
    if (direction=="down")
        currentShape.location[1]++;
    else if(direction=="left")
        currentShape.location[0]--;
    else if (direction=="right")
        currentShape.location[0]++;
    
    // redraw the shape onto the board
    for(var i = 0; i < shape.length; i++)
    {
        var x = shape[i][0] + location[0];    //  x + offset
        var y = shape[i][1] + location[1];    // y + offset
        var block = document.querySelector('[data-x="' + x + '"][data-y="' + y + '"]');
        block.classList.add('filled');
        block.style.backgroundColor = currentShape.color;
    }

    currentShape.indexes = getBlockNumbers(currentShape.shape, currentShape.location);
}

Let's run through a quick breakdown of the function. We start off by keeping a local variable for the currentShape's shape and location. Recall that this variable was initialized above in the createShape() function.

Next up we are making a call to the clearCurrent() function, whose job it is clear the current location of the shape, in order to draw it at its next location.

And clearCurrent() is as follows:

function clearCurrent()
{
    // reset all blocks
    var shape = currentShape.shape;
    var location = currentShape.location;
    
    for(var i = 0; i < shape.length; i++)
    {
        var x = shape[i][0] + location[0];
        var y = shape[i][1] + location[1];
        var block = document.querySelector('[data-x="' + x + '"][data-y="' + y + '"]');
        
        block.classList.remove('filled');
        block.style.backgroundColor="";
    }
}

After clearing the current blocks, we need to update the current shapes coordinates to its new location. This will vary depending on the direction that the block is currently moving in.

// based on direction of block, set the offset
    if (direction=="down")
        currentShape.location[1]++;
    else if(direction=="left")
        currentShape.location[0]--;
    else if (direction=="right")
        currentShape.location[0]++;

The movement really comes down to updating the current shapes X and/or Y coordinates and then redrawing the shape there.

// redraw the shape onto the board
    for(var i = 0; i < shape.length; i++)
    {
        var x = shape[i][0] + location[0];    //  x + offset
        var y = shape[i][1] + location[1];    // y + offset
        var block = document.querySelector('[data-x="' + x + '"][data-y="' + y + '"]');
        block.classList.add('filled');
        block.style.backgroundColor = currentShape.color;
    }

Occupying a space will come down to adding the 'filled' class to the game boards blocks and setting that particular blocks backgroundColor to whatever current color is in play.

Next up, let's handle the keyboard events in order to move our shape around.

Step 5. Keyboard events

Moving the shape around is relatively simple thanks to the offset logic discussed above. We specify which direction we expect the shape to go, and we update the offset accordingly by updating the X or Y coordinates. The drawShape() function will handle rendering the shape after.

function checkKey(e) {
    e.preventDefault();

    e = e || window.event;

    if (e.keyCode == '40') {
        // down arrow
        direction="down";
    }
    else if (e.keyCode == '37') {
        // left arrow
        direction="left";
    }
    else if (e.keyCode == '39') {
        // right arrow
        direction="right";
    }

    drawShape();
}

And lastly, but firstly, we will define the function that will start and initialize the game.


function start()
{
    createBoard();
    createShapes();
    createShape();
    drawShape();
    document.onkeydown = checkKey;
}

window.addEventListener('load', function(){
    start();
});

Let's see it up and running.

You should be able to move a random shape around the board at this point. But the foundation for the rest of the game has been taken care of at this point.

That's it for this part 1 of the Tetris tutorial.

You can download the full source along with the CSS right over here.

In the next part, we're going to go over collisions, both with the surrounding walls and with other shapes, and we can start to get into some of the more complex game elements of Tetris. Happy coding!

Check out part 2 right over here!

New articles published each week. Sign up for my newsletter and stay up to date.

Sign up

Find Developer Jobs

Search through 1000's of programmmer jobs in your area or remotely.

Find a coding job

Comments & Questions

Walter
6/27/2020 7:01:53 PM
Any questions/comments, leave them down below!

Add a comment

Send me your weekly newsletter filled with awesome ideas
Post