Learn how to create a Tetris game from scratch using JavaScript with this beginner-friendly guide. Build the game mechanics, levels, and scoring system step-by-step.
Table of Contents
Tetris is a classic puzzle game that has been entertaining players since its inception in the 1980s. The game involves manipulating falling shapes, or tetrominoes, to form complete lines and clear them from the game board. It is a simple yet challenging game that has stood the test of time and has been adapted to various platforms over the years.
In recent years, game development has become more accessible to a wider audience, thanks to the popularity of web and mobile app development. With the rise of JavaScript as a leading programming language for web development, it is now possible to create a Tetris game using JavaScript and web technologies.
In this article, we will guide you through the process of creating a Tetris game using JavaScript. We will start by setting up the game environment with HTML and CSS, then move on to implementing game mechanics with JavaScript. We will cover essential game features such as block rotation, movement, and collision detection, as well as creating multiple game levels and a scoring system.
In addition, we will explore ways to enhance the game experience with visual effects and provide a complete guide to deploying the Tetris game on the web. Whether you are a beginner or an experienced developer, this article will provide you with the essential knowledge and skills to create a fun and engaging Tetris game with JavaScript.
Let's start making an amazing Tetris game using HTML, CSS, and JavaScript step by step.
Join My Telegram Channel to Download the Projects Source Code: Click Here
Prerequisites:
Before starting this tutorial, you should have a basic understanding of HTML, CSS, and JavaScript. Additionally, you will need a code editor such as Visual Studio Code or Sublime Text to write and save your code.
Code by: Shahn-Auronas
Source Code
Step 1 (HTML Code):
To get started, we will first need to create a basic HTML file. In this file, we will include the main structure for our Tetris game.
After creating the files just paste the following below codes into your file. Make sure to save your HTML document with a .html extension, so that it can be properly viewed in a web browser.
Below is a basic HTML code that includes a Tetris game using JavaScript.
The <!DOCTYPE html> is a declaration that specifies the document type and version of HTML used in the document.
The <html> element is the root element of the document, containing all other elements.
The <head> element contains metadata for the document, such as the document title and external resources. In this case, it includes a title of "TETRIS GAMES WITH JAVASCRIPT" and a link to an external stylesheet named "styles.css".
The <body> element contains the content that is displayed in the web browser.
Inside the <body> element, there is a <div> element with the id of "tetris". This div contains two child elements, another <div> element with the id of "info" and another <div> element with the id of "canvas".
The <div id="info"> element contains several child elements, including a <div> element with the id of "next_shape" that is empty and will display the next shape to appear in the game, four <p> elements with the id of "level", "lines", "score", and "time" that will display the game's statistics, a <button> element with the id of "start" that will start the game, and a <p> element with the class of "red" that will display instructions to pause the game by pressing the Esc button.
The <div id="canvas"> element is where the Tetris game board will be displayed.
Finally, there is a <script> element that includes a JavaScript file named "script.js" that contains the code for the Tetris game.
This is the basic structure of our Tetris game using HTML, and now we can move on to styling it using CSS.
Step 2 (CSS Code):
Once the basic HTML structure of the Tetris game is in place, the next step is to add styling to the Tetris game using CSS. CSS allows us to control the visual appearance of the website, including things like layout, color, and typography.
Next, we will create our CSS file. In this file, we will use some basic CSS rules to style our Tetris game.
Below is a CSS stylesheet that defines the styles for the HTML elements used in the Tetris game.
The body selector applies styles to the entire body of the document, setting the overflow to hidden and the background color to a light gray.
The #tetris selector applies styles to the div element with the id of "tetris", setting the width to 360 pixels, adding a 1-pixel black border, and adding 20 pixels of padding.
The #canvas selector applies styles to the div element with the id of "canvas", setting the width to 200 pixels, the height to 440 pixels, and the background color to black. It also positions the element relative to its parent, sets the font color to white, and includes an h1 selector that sets the font size to 30 pixels and centers the text.
The .piece selector applies styles to all elements with the class of "piece", setting a 1-pixel white border and positioning them absolutely.
The #start selector applies styles to the button element with the id of "start", setting an animation that causes the button to blink, adding a light green background color, a 2-pixel border radius, and setting the font color to dark gray.
The @keyframes rule defines an animation for the #start selector that causes the button's outline to blink.
The .red selector applies styles to all elements with the class of "red", setting the font color to red.
The .square selector applies styles to all elements with the class of "square", setting their position to absolute and their width and height to 19 pixels, with a 1-pixel white border.
The .type0, .type1, .type2, .type3, .type4, .type5, and .type6 selectors apply styles to the elements with the corresponding classes, setting their background color to different shades.
The #next_shape selector applies styles to the div element with the id of "next_shape", setting its position to relative, its background color to black, and its border to 1 pixel white. It also sets the width and height to 110 pixels.
The #info selector applies styles to the div element with the id of "info", setting its background color to black, its font color to white, and its width and height to 420 pixels. It also floats the element to the right and adds 10 pixels of padding.
This will give our Tetris game an upgraded presentation. Create a CSS file with the name of styles.css and paste the given codes into your CSS file. Remember that you must create a file with the .css extension.
body {
overflow: hidden;
background: #d7d7d7;
}
#tetris {
width: 360px;
border: 1px solid black;
padding: 20px;
}
#canvas {
width: 200px;
height: 440px;
background-color: #000;
position: relative;
color: #fff;
}
#canvas h1 {
margin: 0;
padding: 0;
text-align: center;
font-size: 30px;
padding-top: 200px;
}
.piece {
border: 1px solid white;
position: absolute;
}
#start{
animation: blink .7s steps(2, start) infinite;
background: #E1FF5F;
border-radius: 2px;
color: #202020;
cursor: pointer;
font-size: 28px;
}
@keyframes blink {
to {
outline: #E1FF5F solid 1px;
}
}
.red{
color: #f00000;
}
.square {
position: absolute;
width: 19px;
height: 19px;
border: 1px solid white;
}
.type0 {
background-color: #a000f0;
}
.type1 {
background-color: #00f0f0;
}
.type2 {
background-color: #f0a000;
}
.type3 {
background-color: #0000f0;
}
.type4 {
background-color: #00f000;
}
.type5 {
background-color: #f00000;
}
.type6 {
background-color: #f0f000;
}
#next_shape {
position: relative;
background-color: #000;
border: 1px solid white;
width: 110px;
height: 110px;
}
#info {
background-color: #000;
color: #fff;
float: right;
width: 110px;
height: 420px;
padding: 10px;
}
Step 3 (JavaScript Code):
Finally, we need to create a function in JavaScript to work our Tetris game.
The game is implemented as an object-oriented program that consists of several methods and properties. The game includes a game board, a canvas, and a set of Tetris shapes that are randomly selected and dropped onto the board.
The init method initializes the game by calling other methods to initialize the board, info, shapes, and key events. Then, it starts the game by calling the play method.
The initBoard method initializes the game board by setting the board height and width, creating an array to represent the board, and filling the array with zeros. The size of each square on the board is also set in this method.
The initInfo method initializes the game info by setting up the display for the next shape, level, time, score, and lines. It also calls the setInfo method to initialize the values for these info items.
The initShapes method initializes the game shapes by resetting the current squares, indicating that the current shape is not complete, shifting the temporary shapes array, selecting the next shape from the temporary shapes array, setting the current shape to the selected shape, initializing the next shape, setting the coordinates for the current shape to the spawn point, and drawing the current shape on the board.
The initNextShape method initializes the next shape by selecting the next shape from the temporary shapes array and drawing the shape on the canvas.
The initTempShapes method initializes the temporary shapes array by creating an array of all possible shape indices, and then shuffling the array using the Fisher Yates Shuffle.
The shiftTempShapes method shifts the temporary shapes array by removing the first element from the array.
The initTimer method initializes the timer by calling the incTime method every maxTime milliseconds using a setTimeout loop. This method also sets the speed of the game, the number of levels, and the initial values for the score, level, and time.
The play method starts the game by setting the active status to 1 and starting the timer.
The drawShape method draws a shape on the board by setting the color of each square in the shape to the color of the shape and drawing the square on the canvas.
The drawNextShape method draws the next shape on the canvas by setting the color of each square in the shape to the color of the shape and drawing the square on the canvas.
The bindKeyEvents method binds the key events to the game. When a key is pressed, the corresponding action is performed.
The move method moves the current shape in the specified direction. If the move is valid, the shape is redrawn on the board in the new position. If the move is not valid, the shape remains in its current position.
The rotate method rotates the current shape 90 degrees clockwise. If the rotation is valid, the shape is redrawn on the board in the new position. If the rotation is not valid, the shape remains in its current position.
The drop method drops the current shape down to the lowest possible position. The shape is redrawn on the board in the new position.
The clearLine method checks if a line is complete and removes the line if it is complete. The score is updated based on the number of lines cleared.
The incTime method increments the time by 1 and updates the time display.
The setInfo method initializes the info display by setting the value of the specified item to the specified value.
Create a JavaScript file with the name of script.js and paste the given codes into your JavaScript file and make sure it's linked properly to your HTML document, so that the scripts are executed on the page. Remember, you’ve to create a file with .js extension.
(function () {
var isStart = false;
var tetris = {
board: [],
boardDiv: null,
canvas: null,
pSize: 20,
canvasHeight: 440,
canvasWidth: 200,
boardHeight: 0,
boardWidth: 0,
spawnX: 4,
spawnY: 1,
shapes: [
[
[-1, 1],
[0, 1],
[1, 1],
[0, 0], //TEE
],
[
[-1, 0],
[0, 0],
[1, 0],
[2, 0], //line
],
[
[-1, -1],
[-1, 0],
[0, 0],
[1, 0], //L EL
],
[
[1, -1],
[-1, 0],
[0, 0],
[1, 0], //R EL
],
[
[0, -1],
[1, -1],
[-1, 0],
[0, 0], // R ess
],
[
[-1, -1],
[0, -1],
[0, 0],
[1, 0], //L ess
],
[
[0, -1],
[1, -1],
[0, 0],
[1, 0], //square
],
],
tempShapes: null,
curShape: null,
curShapeIndex: null,
curX: 0,
curY: 0,
curSqs: [],
nextShape: null,
nextShapeDisplay: null,
nextShapeIndex: null,
sqs: [],
score: 0,
scoreDisplay: null,
level: 1,
levelDisplay: null,
numLevels: 10,
time: 0,
maxTime: 1000,
timeDisplay: null,
isActive: 0,
curComplete: false,
timer: null,
sTimer: null,
speed: 700,
lines: 0,
init: function () {
isStart = true;
this.canvas = document.getElementById('canvas');
this.initBoard();
this.initInfo();
this.initLevelScores();
this.initShapes();
this.bindKeyEvents();
this.play();
},
initBoard: function () {
this.boardHeight = this.canvasHeight / this.pSize;
this.boardWidth = this.canvasWidth / this.pSize;
var s = this.boardHeight * this.boardWidth;
for (var i = 0; i < s; i++) {
this.board.push(0);
}
//this.boardDiv = document.getElementById('board); //for debugging
},
initInfo: function () {
this.nextShapeDisplay = document.getElementById('next_shape');
this.levelDisplay = document
.getElementById('level')
.getElementsByTagName('span')[0];
this.timeDisplay = document
.getElementById('time')
.getElementsByTagName('span')[0];
this.scoreDisplay = document
.getElementById('score')
.getElementsByTagName('span')[0];
this.linesDisplay = document
.getElementById('lines')
.getElementsByTagName('span')[0];
this.setInfo('time');
this.setInfo('score');
this.setInfo('level');
this.setInfo('lines');
},
initShapes: function () {
this.curSqs = [];
this.curComplete = false;
this.shiftTempShapes();
this.curShapeIndex = this.tempShapes[0];
this.curShape = this.shapes[this.curShapeIndex];
this.initNextShape();
this.setCurCoords(this.spawnX, this.spawnY);
this.drawShape(this.curX, this.curY, this.curShape);
},
initNextShape: function () {
if (typeof this.tempShapes[1] === 'undefined') {
this.initTempShapes();
}
try {
this.nextShapeIndex = this.tempShapes[1];
this.nextShape = this.shapes[this.nextShapeIndex];
this.drawNextShape();
} catch (e) {
throw new Error('Could not create next shape. ' + e);
}
},
initTempShapes: function () {
this.tempShapes = [];
for (var i = 0; i < this.shapes.length; i++) {
this.tempShapes.push(i);
}
var k = this.tempShapes.length;
while (--k) {
//Fisher Yates Shuffle
var j = Math.floor(Math.random() * (k + 1));
var tempk = this.tempShapes[k];
var tempj = this.tempShapes[j];
this.tempShapes[k] = tempj;
this.tempShapes[j] = tempk;
}
},
shiftTempShapes: function () {
try {
if (
typeof this.tempShapes === 'undefined' ||
this.tempShapes === null
) {
this.initTempShapes();
} else {
this.tempShapes.shift();
}
} catch (e) {
throw new Error('Could not shift or init tempShapes: ' + e);
}
},
initTimer: function () {
var me = this;
var tLoop = function () {
me.incTime();
me.timer = setTimeout(tLoop, 2000);
};
this.timer = setTimeout(tLoop, 2000);
},
initLevelScores: function () {
var c = 1;
for (var i = 1; i <= this.numLevels; i++) {
this['level' + i] = [c * 1000, 40 * i, 5 * i]; //for nxt level, row score, p sore,
c = c + c;
}
},
setInfo: function (el) {
this[el + 'Display'].innerHTML = this[el];
},
drawNextShape: function () {
var ns = [];
for (var i = 0; i < this.nextShape.length; i++) {
ns[i] = this.createSquare(
this.nextShape[i][0] + 2,
this.nextShape[i][1] + 2,
this.nextShapeIndex
);
}
this.nextShapeDisplay.innerHTML = '';
for (var k = 0; k < ns.length; k++) {
this.nextShapeDisplay.appendChild(ns[k]);
}
},
drawShape: function (x, y, p) {
for (var i = 0; i < p.length; i++) {
var newX = p[i][0] + x;
var newY = p[i][1] + y;
this.curSqs[i] = this.createSquare(newX, newY, this.curShapeIndex);
}
for (var k = 0; k < this.curSqs.length; k++) {
this.canvas.appendChild(this.curSqs[k]);
}
},
createSquare: function (x, y, type) {
var el = document.createElement('div');
el.className = 'square type' + type;
el.style.left = x * this.pSize + 'px';
el.style.top = y * this.pSize + 'px';
return el;
},
removeCur: function () {
var me = this;
this.curSqs.eachdo(function () {
me.canvas.removeChild(this);
});
this.curSqs = [];
},
setCurCoords: function (x, y) {
this.curX = x;
this.curY = y;
},
bindKeyEvents: function () {
var me = this;
var event = 'keypress';
if (this.isSafari() || this.isIE()) {
event = 'keydown';
}
var cb = function (e) {
me.handleKey(e);
};
if (window.addEventListener) {
document.addEventListener(event, cb, false);
} else {
document.attachEvent('on' + event, cb);
}
},
handleKey: function (e) {
var c = this.whichKey(e);
var dir = '';
switch (c) {
case 37:
this.move('L');
break;
case 38:
this.move('RT');
break;
case 39:
this.move('R');
break;
case 40:
this.move('D');
break;
case 27: //esc: pause
this.togglePause();
break;
default:
break;
}
},
whichKey: function (e) {
var c;
if (window.event) {
c = window.event.keyCode;
} else if (e) {
c = e.keyCode;
}
return c;
},
incTime: function () {
this.time++;
this.setInfo('time');
},
incScore: function (amount) {
this.score = this.score + amount;
this.setInfo('score');
},
incLevel: function () {
this.level++;
this.speed = this.speed - 75;
this.setInfo('level');
},
incLines: function (num) {
this.lines += num;
this.setInfo('lines');
},
calcScore: function (args) {
var lines = args.lines || 0;
var shape = args.shape || false;
var speed = args.speed || 0;
var score = 0;
if (lines > 0) {
score += lines * this['level' + this.level][1];
this.incLines(lines);
}
if (shape === true) {
score += shape * this['level' + this.level][2];
}
/*if (speed > 0){ score += speed * this["level" +this .level[3]];}*/
this.incScore(score);
},
checkScore: function () {
if (this.score >= this['level' + this.level][0]) {
this.incLevel();
}
},
gameOver: function () {
this.clearTimers();
isStart = false;
this.canvas.innerHTML = '<h1>GAME OVER</h1>';
},
play: function () {
var me = this;
if (this.timer === null) {
this.initTimer();
}
var gameLoop = function () {
me.move('D');
if (me.curComplete) {
me.markBoardShape(me.curX, me.curY, me.curShape);
me.curSqs.eachdo(function () {
me.sqs.push(this);
});
me.calcScore({ shape: true });
me.checkRows();
me.checkScore();
me.initShapes();
me.play();
} else {
me.pTimer = setTimeout(gameLoop, me.speed);
}
};
this.pTimer = setTimeout(gameLoop, me.speed);
this.isActive = 1;
},
togglePause: function () {
if (this.isActive === 1) {
this.clearTimers();
this.isActive = 0;
} else {
this.play();
}
},
clearTimers: function () {
clearTimeout(this.timer);
clearTimeout(this.pTimer);
this.timer = null;
this.pTimer = null;
},
move: function (dir) {
var s = '';
var me = this;
var tempX = this.curX;
var tempY = this.curY;
switch (dir) {
case 'L':
s = 'left';
tempX -= 1;
break;
case 'R':
s = 'left';
tempX += 1;
break;
case 'D':
s = 'top';
tempY += 1;
break;
case 'RT':
this.rotate();
return true;
break;
default:
throw new Error('wtf');
break;
}
if (this.checkMove(tempX, tempY, this.curShape)) {
this.curSqs.eachdo(function (i) {
var l = parseInt(this.style[s], 10);
dir === 'L' ? (l -= me.pSize) : (l += me.pSize);
this.style[s] = l + 'px';
});
this.curX = tempX;
this.curY = tempY;
} else if (dir === 'D') {
if (this.curY === 1 || this.time === this.maxTime) {
this.gameOver();
return false;
}
this.curComplete = true;
}
},
rotate: function () {
if (this.curShapeIndex !== 6) {
//square
var temp = [];
this.curShape.eachdo(function () {
temp.push([this[1] * -1, this[0]]);
});
if (this.checkMove(this.curX, this.curY, temp)) {
this.curShape = temp;
this.removeCur();
this.drawShape(this.curX, this.curY, this.curShape);
} else {
throw new Error('Could not rotate!');
}
}
},
checkMove: function (x, y, p) {
if (this.isOB(x, y, p) || this.isCollision(x, y, p)) {
return false;
}
return true;
},
isCollision: function (x, y, p) {
var me = this;
var bool = false;
p.eachdo(function () {
var newX = this[0] + x;
var newY = this[1] + y;
if (me.boardPos(newX, newY) === 1) {
bool = true;
}
});
return bool;
},
isOB: function (x, y, p) {
var w = this.boardWidth - 1;
var h = this.boardHeight - 1;
var bool = false;
p.eachdo(function () {
var newX = this[0] + x;
var newY = this[1] + y;
if (newX < 0 || newX > w || newY < 0 || newY > h) {
bool = true;
}
});
return bool;
},
getRowState: function (y) {
var c = 0;
for (var x = 0; x < this.boardWidth; x++) {
if (this.boardPos(x, y) === 1) {
c = c + 1;
}
}
if (c === 0) {
return 'E';
}
if (c === this.boardWidth) {
return 'F';
}
return 'U';
},
checkRows: function () {
var me = this;
var start = this.boardHeight;
this.curShape.eachdo(function () {
var n = this[1] + me.curY;
console.log(n);
if (n < start) {
start = n;
}
});
console.log(start);
var c = 0;
var stopCheck = false;
for (var y = this.boardHeight - 1; y >= 0; y--) {
switch (this.getRowState(y)) {
case 'F':
this.removeRow(y);
c++;
break;
case 'E':
if (c === 0) {
stopCheck = true;
}
break;
case 'U':
if (c > 0) {
this.shiftRow(y, c);
}
break;
default:
break;
}
if (stopCheck === true) {
break;
}
}
if (c > 0) {
this.calcScore({ lines: c });
}
},
shiftRow: function (y, amount) {
var me = this;
for (var x = 0; x < this.boardWidth; x++) {
this.sqs.eachdo(function () {
if (me.isAt(x, y, this)) {
me.setBlock(x, y + amount, this);
}
});
}
me.emptyBoardRow(y);
},
emptyBoardRow: function (y) {
for (var x = 0; x < this.boardWidth; x++) {
this.markBoardAt(x, y, 0);
}
},
removeRow: function (y) {
for (var x = 0; x < this.boardWidth; x++) {
this.removeBlock(x, y);
}
},
removeBlock: function (x, y) {
var me = this;
this.markBoardAt(x, y, 0);
this.sqs.eachdo(function (i) {
if (me.getPos(this)[0] === x && me.getPos(this)[1] === y) {
me.canvas.removeChild(this);
me.sqs.splice(i, 1);
}
});
},
setBlock: function (x, y, block) {
this.markBoardAt(x, y, 1);
var newX = x * this.pSize;
var newY = y * this.pSize;
block.style.left = newX + 'px';
block.style.top = newY + 'px';
},
isAt: function (x, y, block) {
if (this.getPos(block)[0] === x && this.getPos(block)[1] === y) {
return true;
}
return false;
},
getPos: function (block) {
var p = [];
p.push(parseInt(block.style.left, 10) / this.pSize);
p.push(parseInt(block.style.top, 10) / this.pSize);
return p;
},
getBoardIdx: function (x, y) {
return x + y * this.boardWidth;
},
boardPos: function (x, y) {
return this.board[x + y * this.boardWidth];
},
markBoardAt: function (x, y, val) {
this.board[this.getBoardIdx(x, y)] = val;
},
markBoardShape: function (x, y, p) {
var me = this;
p.eachdo(function (i) {
var newX = p[i][0] + x;
var newY = p[i][1] + y;
me.markBoardAt(newX, newY, 1);
});
},
isIE: function () {
return this.bTest(/IE/);
},
isFirefox: function () {
return this.bTest(/Firefox/);
},
isSafari: function () {
return this.bTest(/Safari/);
},
bTest: function (rgx) {
return rgx.test(navigator.userAgent);
},
};
const btn = document.querySelector('#start');
btn.addEventListener('click', function () {
btn.style.display = 'none';
if (!isStart) {
tetris.init();
}
});
})();
if (!Array.prototype.eachdo) {
Array.prototype.eachdo = function (fn) {
for (var i = 0; i < this.length; i++) {
fn.call(this[i], i);
}
};
}
if (!Array.prototype.remDup) {
Array.prototype.remDup = function () {
var temp = [];
for (var i = 0; i < this.length; i++) {
var bool = true;
for (var j = i + 1; j < this.length; j++) {
if (this[i] === this[j]) {
bool = false;
}
}
if (bool === true) {
temp.push(this[i]);
}
}
return temp;
};
}
Final Output:
Conclusion:
In this article, we have provided a step-by-step guide to creating a Tetris game using JavaScript. We started by setting up the game environment with HTML and CSS, then moved on to implementing game mechanics with JavaScript. We covered essential game features such as block rotation, movement, and collision detection, as well as creating multiple game levels and a scoring system.
We also explored ways to enhance the game experience with visual effects and provided a complete guide to deploying the Tetris game on the web. By following this guide, you now have the essential knowledge and skills to create a fun and engaging Tetris game using JavaScript.
However, game development is an ongoing learning process, and there is always room for improvement and experimentation. We encourage you to continue exploring game development with JavaScript and to customize the game mechanics to suit your creative vision. With the right tools and resources, you can create a fun and unique game.
In conclusion, we hope that this article has provided you with the necessary knowledge and inspiration to create your own Tetris game using JavaScript. By following the steps outlined in this guide, you can create a game that is both challenging and enjoyable, and share it with others on the web.
That’s a wrap!
I hope you enjoyed this post. Now, with these examples, you can create your own amazing page.
Did you like it? Let me know in the comments below 🔥 and you can support me by buying me a coffee
And don’t forget to sign up to our email newsletter so you can get useful content like this sent right to your inbox!
Thanks!
Faraz 😊