Build a Tetris game with HTML Canvas, CSS, and JavaScript on Autocode
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-font
property 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";
});
- We add an event listener for when the
<close>
element is clicked and set the modal display style tonone
.
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 eachrow
and they
index. - Then we iterate over the row and use
forEach()
to get the value and thex
index. - We add a conditional
if
statement to check if the value is not equal to0.
If it is not equal to0
then we paint the cellblue
. If it is0
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
toblack
starting at x coordinate 0 and y coordinate 0 and encompass the entire canvas width and height. - In this same
draw()
function, we usedrawPiece()
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 dCounter
that 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 thedropInterval.
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.