Build a Tetris game with HTML Canvas, CSS, and JavaScript on Autocode

Janeth Graziani
Level Up Coding
Published in
15 min readJul 7, 2021

Hello, aspiring software engineer! If you’re reading this, you’re likely on your journey to becoming a developer. Congrats on making the commitment to start a new career! 👏🏼

I committed to blog and write tutorials at the start of my journey at General Assemblies 12-week Software Engineering Bootcamp. I learned HTML, CSS, Javascript, DOM manipulation, and APIs during the first two weeks. During our third week, we were given five days to apply our learnings and build a game of our choice. I knew this would be a tough but fun challenge. We learned a lot in two weeks, and I felt like I hadn’t had enough time to separate concerns and synthesize the material. I dedicated two days out of the 5 to review, read online tutorials, and plan my approach. In this way, I was able to recreate my favorite childhood game — Tetris!

In this guide, I will walk you through the first part of building a Tetris game so that you can recreate your own and continue practicing your JavaScript, DOM manipulation, and styling skills.

We will first set up our CSS, HTML, and JavaScript files, then I will explain the code to our JavaScript file line by line.

Make sure to look through the following resources that helped me tackle this challenge before you begin:

Prerequisites:

  • 👉🏽 HTML — HyperText Markup language tells the browser what content to display on our website through elements that label titles, headings, paragraphs, links, buttons, etc.
  • 👉🏽 HTML5 Canvas<canvas> is an HTML tag used to draw graphics, on the fly, via scripting (usually JavaScript). This is an awesome tutorial from W3schools that helped me uncover more methods built into the canvas tag.
  • 👉🏽 CSS — Cascading Style Sheets. If HTML is a set of instructions telling the browser what to display, CSS tells it how to display it. CSS can change text font, size, color, and layout of various elements on the page, and more.
  • 👉🏽The HTML DOM (Document Object Model) — When a web page is loaded, the browser creates a Document Object Model of the page. With the HTML DOM, JavaScript can access and change all the elements of an HTML document.
  • 👉🏽 JavaScript — Javascript is a powerful language used for a variety of purposes. In this tutorial, we’ll use JavaScript to select and manipulate the HTML elements via the DOM and attach event listeners that will run JavaScript functions when a button or key is pressed. I’d recommend checking out my previous tutorial 👉🏽 Solving Coding Challenges with JavaScript Functions on Autocode, beforehand.

What We Will Do:

-Step 1: Set up a free account on Autocode
-Step 2: Set up the index.html file on
Autocode
-Step 3: Set up the style.css file on
Autocode
-Step 4: Set up the tetris.js file on
Autocode
-Review of index.html and style.css code
-Review of tetris.js code

After we complete Steps 1 through 4, we will then review the code. I will focus on explaining our tetris.js file since I expect that you have a basic knowledge of HTML and CSS.

If you are entirely new, please check out the resources I’ve listed under prerequisites. If you have any questions about any piece of code, please reach out! I will be happy to answer.

Let’s get to coding! 🧑🏽‍💻👩🏽‍💻🚀

Step 1: Set up a free account on Autocode

We will build our game on Autocode. While Autocode is optimized for building backend, APIs, and automation, it is also a complete online code editor that will render and host our HTML, CSS, and JavaScript files. Autocode makes it very simple to publish our game and make it accessible via the web without worrying about setting up hosting through third-party services.

Head on over to Autocode.com and sign up for a free account. Choose a subdomain where your projects will be hosted and set a password.

Once you’ve signed up, navigate to your Project Management dashboard and click New Project.

You’ll be brought into Autocode’s IDE. You will see a project scaffold on the left bar menu that already includes a directory (folder) titled www to add our project assets and webpages. Files in this directory will automatically be converted into static web pages and resources.

Let’s begin by clicking the samplehello.html file.

Step 2: Set up the index.html file on Autocode

Highlight and delete all the sample code inside the hello.html file. We will be adding our code.

Let’s rename our HTML file to index.html. Right-click the hello.html file and select rename file.

Enter the full pathname like so: www/index.html

Now copy and paste the following code to your index.html file.

Like so:

Select the green Run button. Autocode will prompt you to name your project.

Keep in mind that the name you give your project will be part of the autogenerated URL to access your game via the web; I’d recommend a name like so: tetris-game..

Click Save new project.

Congrats! You’ve deployed the HTML page for your game. Autocode’s tools allow you to view a preview of the page you created inside the Endpoint viewer window:

You can also check out your page live on the web by clicking the link on the bottom left of the code editor.

Great, we’ve created the basic view of our game, but now we need to give it some color and style.

Let’s build our CSS page!

Step 3: Set up the style.css file on Autocode

Right-click the www directory on the right-hand side and select New file.

Enter full pathname when naming your file www/style.css . Like so:

Select Create File. You should see an empty .css file ready for CSS code.

Copy and paste the following code to your style.css file.

Your Autocode’s IDE should look something like this :

Great! Save these changes by clicking the orange Save button on the bottom right corner.

Move back into your index.html file.

Click the green Run button once inside index.html. We’re ready to preview the styles we’ve made to our index.html page.

When you click the autogenerated URL for your project, you should see your styled HTML page pop up in a different tab. Like so:

Step 4: Set up the tetris.js file on Autocode

Great! We now have a styled HTML page to display our game. You’ll notice that our buttons aren’t working. That’s because we haven’t added our Javascript logic.

It’s time to add some JavaScript to create our game.

Create a tetris.js file inside your www directory as we did for our index.html and style.css file. Add the full pathname when naming your file like so: www/tetris.js

Copy and paste the following code into the file.

Your tetris.js file should look like this:

Save your tetris.js file by clicking the orange Save button on the bottom right corner. We’re ready to preview the functionality we have added to the game. Return to the index.html.Click the green Run button.

You can test the functionality of your game by clicking the Start button on the Endpoint viewer window.

Alternatively, you can click on the autogenerated URL to test your game from a new tab. Use the A and D keys to rotate your Tetris piece and down, left, and right arrow keys to move the pieces accordingly.

Great job for keeping up and making it this far into the tutorial! It’s time to review our code!

Review of index.html and style.css code

Linking index.html to style.css and tetris.js :

We must link our .css .js files to the .html file for them to talk to one another.

We have the .css file in the same folder as the HTML file, so we link CSS to HTML by using the <link> element inside the <head> element like this:

<link rel="stylesheet" href="style.css">

We also import the arcade-style font Press Start 2p on line 10. This font is now available to use in the style.css file after importing and linking our files.

At the end of our index.html file on line 40, we added a reference to our JavaScript file using a <script> element:

<script src="tetris.js"></script>

Now head on over to you style.css file:

Inside our style.css file we define the family-fontproperty as Press Start 2P, monospace for the <h2> <h3> <h4> <p> elements; as well as the button elements with id attributes of #instructions2 #start #reset.

Review of our tetris.js code

Line 1–7 Using HTML DOM methods to access elements:

Let’s start by analyzing the first seven lines of code inside our tetris.js file.

Here we define variables for our canvas, buttons, and modals to manipulate the DOM via Javascript.

To access elements in our HTML page, we always start by accessing the document object. The document object acts as an interface that allows us to program our HTML document.

We use the HTML DOM methodgetElementById() to access our canvas.

const canvas = document.getElementById("tetris");

We then use the built-in object getContext() that will give us properties and methods for drawing in our canvas.

const ctx = canvas.getContext(“2d”)

We use the method getElementById() to access our <button> and <modal> elements. The method getElementByClassName() gives us access to the first <close> element.

const span = document.getElementsByClassName("close")[0];

On line 9, we scale the width and height of our canvas by 20 using the method scale() . Try commenting it out and deploy again to test what happens! *You might have to zoom in to see the falling Tetris pieces.

ctx.scale(20,20);

Line 11–32 Using AddEventListener() method

We use the method addEventListener() to program our start, reset, instructions, and closing <button> elements. Let’s analyze each event listener:

Line 11 -14 startButton.addEventListener()

startButton.addEventListener("click", () => {
update();
startButton.style.display = "none";
});

Here we add an event listener to the startButton so that the function is called every time the button is clicked. This update() function is responsible for drawing and dropping the Tetris pieces. Once the startButton is clicked, the button will disappear because we have set the display property to none .

Line 16–18 resetButton.addEventListener()

resetButton.addEventListener("click", () => {
location.reload();
});

Here we are adding an event listener to the resetButton so that thereload() method is called every time the button is clicked. The reload() method is used to reload the current document.

Line 20–22 instructionsButton.addEventListener()

instructionsButton.addEventListener("click", () => {
modal.style.display = "block";
});

We add an event listener to the instructionsButton so that every time the button is clicked, the modal will appear because we have set the display property to block.

Line 24–32 span.addEventListener()

Here we have two ways of closing the modal that appears when the instructions button is clicked.

span.addEventListener("click", () => {
modal.style.display = "none";
});
  1. We add an event listener for when the <close> element is clicked and set the modal display style to none.
window.onclick = function (event) {
if (event.target == modal) {
modal.style.display = "none";
}
};

2. We use .onClick() method to detect when any area in our window is clicked, and we add the if statement to check if our modal is open. If it’s open, we set modal display style to none when any part of our window is clicked.

Line 34–38: Creating Tetris pieces

The letters I, L, J, T, O, S, and Z resemble the shape of the Tetris pieces. Eventually, we will add all seven pieces; for now, we begin by drawing the functionality of our game with the S-shaped Tetris piece.

const piece = [
[0, 0, 0],
[0, 1, 1],
[1, 1, 0],
]

We can build this piece by declaring a variable piece and assigning it a value of an array of arrays filled with 0 and 1. The number 1 will represent the colored cells. We add an extra row of 0’s to make it easier for us to rotate the pieces.

Line 40–53 Creating the field for our game

Next, we create a field that will allow us to draw the falling pieces and keep track of our pieces stack.

We use a 2D array, also known as a matrix, to represent our field. Our field is made up of cells, which will be filled with numbers 0-7. We will represent our empty cells with 0 and use 1–7 to represent each Tetris piece/color.

First, write a function to create our matrix that takes in width and height and fills our rows with 0.

We initiate an empty array for matrix, and we use a while loop to tell our function that as long as the height is not 0, we will decrease the height by one and push in a new array of width w and fill it with 0. We use thefill() array method to populate our cells with a static value.

function createMatrix(w, h) {
const matrix = [];
while (h--) {
matrix.push(new Array(w).fill(0));
}
return matrix;
}

Then we create our game’s field by using the createMatrix() function and passing in a width of 12 and height of 20.

To view the field we console.log() field like this:

const field = createMatrix(12, 20);
console.log(field)

Open your developer tools inside the browser and inspect your console. You will see the field we created.

In this 2D array, we use an array of numbers to represent a row with 12 elements and an array of 20 rows to represent the field.

We can also use the console.table() method to view the field as a table in our console.

Line 52–55 Adding a Player

const player = {
pos: { x: 5, y: -2 },
piece: piece,
};

We declare a player variable and define its position at x = 5 and y = -2 because we want the first Tetris piece to drop from the center above our field. We also give our player a Tetris piece.

Line 57–66 drawing the Tetris piece

function drawPiece(piece, offset) {
piece.forEach((row, y) => {
row.forEach((value, x) => {
if (value !== 0) {
ctx.fillStyle = "blue";
ctx.fillRect(x + offset.x, y + offset.y, 1, 1);
}
});
});
}

Here we create a drawPiece() function to draw the first Tetris piece.

  • We use the forEach() method to grab each row and the y index.
  • Then we iterate over the row and use forEach() to get the value and the x index.
  • We add a conditional if statement to check if the value is not equal to 0.
    If it is not equal to 0 then we paint the cell blue. If it is 0 we will skip it.
  • We add offset so that we can move our pieces later when we program our keys.

Line 68–73 drawing the pieces repeatedly

function draw() {
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawPiece(field, { x: 0, y: 0 });
drawPiece(player.piece, player.pos);
}

We create a draw() function to draw our pieces and pass in the player’s piece and position into the drawPiece() function.

  • This draw() function will also be called within theupdate() function to draw our game repeatedly.
  • We must clear our field; otherwise, our piece will be drawn every time the function is called. To do this, we set the ctx.fillstyle to black starting at x coordinate 0 and y coordinate 0 and encompass the entire canvas width and height.
  • In this same draw() function, we use drawPiece() again to paint the cells in the field starting from x coordinate 0 and y coordinate 0.

Line 76–96 Using requestAnimationFrame() and dropping our pieces

let dCounter = 0;
let dropInterval = 500;
let lastTime = 0;
function update(time = 0) {
const deltaTime = time - lastTime;
lastTime = time;
dCounter += deltaTime;
if (dCounter > dropInterval) {
player.pos.y++;
if (collide(field, player)) {
player.pos.y--;
join(field, player);
player.pos.y = 0;
}
dCounter = 0;
}
draw();
requestAnimationFrame(update);
}

Here we create the update() function that will be responsible for starting our game. It makes a call the draw() function and requestAnimationFrame() method.

First, we defined a variable dCounterthat we default to 0 to help us move our piece back to the top. We added a dropInterval variable set to 500 milliseconds so that our pieces drop every 1/2 of a second. To get the difference of time (deltaTime) between our frames, we set a variable lastTime equal to 0. DeltaTime is a useful variable for animation that contains the time difference between the beginning of the previous frame and the beginning of the current frame in milliseconds.

  • We drop the pieces using the requestAnimationFrame() by grabbing time and defaulting to 0.
  • Then we add drop count to the difference between the previous frame and the current frame.
  • We add an if statement to check if the dCounter is greater than the dropInterval. If it is, then we move our player piece position down the y axis (y++).
  • If the piece is dropped and collides, it means that the piece touches the bottom of the screen or another piece, so we move the player piece one-up the y axis.
  • We then set the player’s piece to the top to start over

Line 98–106 join() function

function join(field, player) {
player.piece.forEach((row, y) => {
row.forEach((value, x) => {
if (value !== 0) {
field[y + player.pos.y][x + player.pos.x] = value;
}
});
});
}

Here we create the join() function to print the player’s piece position to the field. It copies the player's piece position onto the field.

Line 108–119 collision detection

function collide(field, player) {
const b = player.piece;
const o = player.pos;
for (let y = 0; y < b.length; y++) {
for (let x = 0; x < b[y].length; x++) {
if (b[y][x] !== 0 && (field[y + o.y] && field[y + o.y][x + o.x]) !== 0) {
return true;
}
}
}
return false;
}

We create a collide() function to check where the squares on our field are not zero.

  • We assign the piece and position to variable b & o, saving the position of our piece
  • We iterate over the player piece position using a for loop to loop over the rows (y)
  • We use a for loop to loop over x
  • We add a conditional statement to check if the piece position of the player is not zero and if the fields row (y) exists.
  • If the fields row (y) doesn’t exist, it will count as a collision
  • If it exists, grab the child (x), and if they are not zero (they collide), so return true
  • Return false if the conditions are not 0

Line 121–132 Rotating pieces

function rotate(piece, control) {
for (let y = 0; y < piece.length; y++) {
for (let x = 0; x < y; x++) {
[piece[x][y], piece[y][x]] = [piece[y][x], piece[x][y]];
}
}
if (control > 0) {
piece.forEach((row) => row.reverse());
} else {
piece.reverse();
}
}

Here we create a rotate() function that takes in a piece and a control. This function converts a piece’s rows (y) into columns using the reverse() method.

Line 134–195 Adding key controls

This part was very challenging for me. Before you attempt it, please check out this tutorial that explains how to program keys using the addEventListener() method to listen to thekeydown event.

We set an event listener using the method addEventListener() with an if..else statement to detect when key 37 (left arrow), key 39 (right arrow), Key 40 (down arrow), key 65 (A), and key 68 (D) are pressed. You can use the following site to find the codes for any key: https://keycode.info/ quickly.

Challenge Yourself 😎

  • Create 6 more blocks with if statements
  • Create a function to randomly select a block
  • Add colors to blocks
  • Add a game over screen
  • Count filled rows using a for loop and clear rows once full
  • Add a function that updates the score
  • Add SMS functionality with SMS API from Autocode’s Standard Library to challenge friends to beat your high scores

Thank you for building with me! 🥳

We’ve done it! I hope this was a fun way to review our CSS, HTML, DOM manipulation, JavaScript, and logic creation. I’d encourage you to continue building out the game. Feel free to reach out if I can help in any way. My email is ledezmajane@berkeley.edu. You can also tweet @ms_ledezma.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

No responses yet

What are your thoughts?