JavaScript Tutorial: Build Tetris with modern JavaScript

May 11, 2021 - 9 min read
Ryan Thelin
editor-page-cover

Learning a new programming language is difficult and not everyone learns the same way. For many, hands-on exploration on a project or interactive tutorial is the key to lasting learning.

Especially for intuitive languages like JavaScript, building projects helps to provide context for how and why to use fundamental concepts in different applications. It also helps build your resume because you can show recruiters a collection of awesome projects to demonstrate your drive and developer skills.

Today, we’ll help you get started on a JavaScript game development project to build Tetris. By the end of the article, you’ll have all the foundational pieces you need to continue building on your own.

Here’s what we’ll cover today:


Learn JavaScript your way, in half the time

Pick up modern JavaScript skills while building a fun and fully featured project for your portfolio.

Game Development with JavaScript: Creating Tetris


What is Tetris?

Tetris is a classic arcade game created in 1984 by Alexey Pajitnov. The game requires players to rotate and move falling Tetris pieces. Players clear lines by completing horizontal rows of blocks without empty cells. If the pieces reach the top, the game is over.

Tetris is a popular project that lets aspiring game developers practice their skills in a simple environment. For example, you’ll get hands-on practice with essential game design concepts, such as gameplay loop design, and implementing common game mechanics like user controls, score tracking, and collision detection. Unlike more complicated games, the behavior and visuals for Tetris are very simple. This lets you practice applying JavaScript basics for game development rather than figuring out animation or complex player behavior flowcharts.

For this example, we’ll start by using the simplest tools and build the complexity towards the end.

To make Tetris, you’ll need to know:

  • HTML/CSS styling
  • JavaScript classes, variables, and scope
  • Arrow functions
  • Template literals
  • Spread vs Rest
  • Destructuring

Styling the game

First, let’s set up the basic UI elements: the play area, a start button, and readouts to track score, lines, and level. We’ll use Flexbox and CSS Grid to position the elements correctly.

index.html
styles.css
// styles.css
.grid {
  display: grid;
  grid-template-columns: 320px 200px;
}

.right-column {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}

.game-board {
  border: solid 2px;
}
 
.play-button {
  background-color: #4caf50;
  font-size: 16px;
  padding: 15px 30px;
  cursor: pointer;
}

A fundamental part of 80s games was the highly recognizable bitmap font. Press start 2P is a free font from Google we can use to simulate the feeling.

To add the font, we need to link to it in the <head> section of our HTML document and set it to our desired font in the CSS style sheet.

 <!--index.html-->
<link
  href="https://fonts.googleapis.com/css?family=Press+Start+2P"
  rel="stylesheet"
/>
// styles.css
* {
  font-family: 'Press Start 2P', cursive;
}

Finally, code the infrastructure of the game board using JavaScript. You’ll need to add <script> elements at the end of our HTML document to import our JavaScript.

The bottom of your HTML document should look like this:

    <script type="text/javascript" src="constants.js"></script>
    <script type="text/javascript" src="board.js"></script>
    <script type="text/javascript" src="piece.js"></script>
    <script type="text/javascript" src="main.js"></script>
  </body>
</html>

constants.js will contain the code for our static play board. These values will never change regardless of player actions. The playboard will consist of 10 columns and 20 rows, with a block size of 30.

//constants.js
const COLS = 10;
const ROWS = 20;
const BLOCK_SIZE = 30;

Then, in the main.js file we’ll include some code to manipulate the document object, which provides a programmable interface for the HTML document. This type of document is called a Document Object Model (DOM).

We can use the DOM to call the getElementByID to let us target specific elements and automatically scale our game to match the size of the user’s browser window. This uses the canvas element new with HTML5, which allows us to create and use 2D shapes with ease.

The main.js file should look like this:

//main.js
const canvas = document.getElementById('board');
const ctx = canvas.getContext('2d');

// Calculate size of canvas from constants.
ctx.canvas.width = COLS * BLOCK_SIZE;
ctx.canvas.height = ROWS * BLOCK_SIZE;

// Scale blocks
ctx.scale(BLOCK_SIZE, BLOCK_SIZE);

By the end, you’ll have the following files:

index.html
main.js
constants.js
styles.css
//styles.css
* {
  font-family: 'Press Start 2P', cursive;
}
 
.grid {
  display: grid;
  grid-template-columns: 320px 200px;
}
 
.right-column {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}
 
.game-board {
  border: solid 2px;
}
 
.play-button {
  background-color: #4caf50;
  font-size: 16px;
  padding: 15px 30px;
  cursor: pointer;
}
Completed UI
Completed UI

Keep learning JS with hands-on projects.

Learn in-demand JavaScript skills without the bookwork. Educative’s text-based courses are easy to skim and feature hands-on project environments to help you learn your way, in half the time.

Game Development with JavaScript: Creating Tetris


Designing the board

Now that we have created a container for our game, it’s time to start coding the logic. First, we need the board to be able to draw the falling pieces and keep track of the game state.

The board and pieces are both good candidates for a class. We can create a new instance of Board when starting a new game and a new instance of Piece every time a new piece enters the game.

For the Board class, we’ll make a new board.js file. We want the board to reference the canvas every time the game starts so we’ll include ctx in the Board constructor. We’ll also include the this keyword to let us set and access properties within ctx.

//board.js
class Board { 
  constructor(ctx) {
    this.ctx = ctx;    
  } 
}

Tetris boards consist of many individual cells that are either empty or occupied. We’ll represent empty cells with 0 and occupied cells with integers of 1-7 to represent colors.

To represent the rows and columns of the board, we’ll use a 2D array (a matrix). We’ll have arrays of integers to represent a row and an array of rows to represent the full board.

Since all games start with an empty board, we’ll need a method that returns an empty board. We can use the built-in fill() array method to populate all elements of each row to 0. The constructor will call this method so all games start empty.

Our board.js file will now look like this:

//board.js
class Board { 
  constructor(ctx) {
    this.ctx = ctx;
    this.grid = this.getEmptyBoard();
  }
 
  getEmptyBoard() {
    return Array.from(
      {length: ROWS}, () => Array(COLS).fill(0)
    );
  }
}

Finally, we’ll go back to main.js to add this new game functionality to the play button.

function play() {
  board = new Board(ctx);
  console.table(board.grid);
}

Now our game board is set up! You can use console.table() to see the matrix that controls the board.

Board matrix shown via console
Board matrix shown via console

Creating the canvas

Now we’ll make sure our canvas element is ready to use. The canvas provides a blank canvas for our game to sit on.

We can also add a 2D drawing context over the canvas for drawing shapes, text, images, and other objects. It works similarly to programs like MS Paint in that you can choose your brush type and color then draw using code.

First, we want to make sure the canvas is the right size. It is 300x150 pixels by default but we want it to scale using the code we added above.

To do so, we add a canvas element to our index.html:

<canvas id="canvas"></canvas>

Then add a reference to the HTML <canvas> element element in the DOM (Document Object Model) using the getElementById method.

let canvas = document.getElementById('canvas');

Now we’ll use the canvas context to render some pieces.

We can use the HTMLCanvasElement.getContext() method to get the canvas context where we render the graphics. This method needs an argument so we’ll pass '2d' to get the 2D render context.

let ctx = canvas.getContext('2d');

Before we can draw we have to choose a color using the fillStyle() method.

ctx.fillStyle = 'red';

We can then use the fillRect() method from the context API to draw a simple rectangle filled with our chosen red color. fillRect() takes 4 arguments: the x and y coordinates where the shape should start and the width/height of the rectangle.

ctx.fillRect(x, y, width, height);

Since all Tetris pieces are collections of squares, we can use this single draw method for all of our pieces!


Animations

Now that we have the tools to draw our graphics, we have to be able to move them.

Canvas uses immediate rendering: Drawn shapes are immediately rendered on the screen, but are not stored as shape objects. Instead, canvas only recognizes the shape as filled pixels, meaning we cannot move the shape in one piece.

To show a moving shape, we have to delete the old shape using clearRect() and redraw it in a new position using fillRect(). Canvas animations are essentially like stop motion animation because they move a little bit in each frame.

Take a look at this example:

const {width, height} = this.ctx.canvas;
ctx.fillStyle = 'blue';
ctx.fillRect(0, 0, 10, 10);
ctx.clearRect(0, 0, width, height);
ctx.fillRect(1, 1, 10, 10);

Here, we select blue as our color then fill a rectangle at point 0,0. Then we clear the whole canvas using clearRect() and passing the width and height of the whole canvas. Finally, we draw a new rectangle of the same size and color at 1,1.

From the user’s perspective, the rectangle moved down and to the right on the screen.

1 of 2

Now that you’ve got your canvas and drawing tools set up, you’ve got all the tools you need to start coding gameplay and a collection of game pieces!


Next steps for your game

This is a great start to your next portfolio project. Now that you’ve built the foundation of the game, your next step is to create objects that draw each unique Tetris game piece in its own color.

Each piece type will be represented by a 3x3 matrix where the piece is the filled cells and the surrounding empty space helps to rotate around the central cell.

[2, 0, 0],  
[2, 2, 2],  
[0, 0, 0];

The best way to do this is with a Piece class.

You’ll also have to add keyboard support so the user can control the pieces. The best way to do this is to use keyboard event support built-in to modern browsers. You can set the program to listen for keyboard events like keydown, keyup, and so on at the document level using the addEventListener() method.

After these steps, you’ll move onto adding more advanced features like:

  • Add collision detection and piece randomizer
  • Add line clearing if a row is filled
  • Track score, level, and past high scores
  • Increase responsiveness with asynchronous JavaScript

To help complete this project and learn JavaScript with hands-on experience, Educative has created the course Game Development with JavaScript: Creating Tetris. This course helps you pick up foundational skills in JavaScript and game development at the same time by building a fun browser game. By the end of the course, you’ll have a fully-featured Tetris web game and will have gained experience with JavaScript skills from beginner to advanced.

Happy learning!


Continue reading about JavaScript


WRITTEN BYRyan Thelin

Join a community of 270,000 monthly readers. A free, bi-monthly email with a roundup of Educative's top articles and coding tips.