Building the Classic 2048 Game with HTML, CSS, and JavaScript: A Step-by-Step Guide

Atik Bin Mustafij (Sobuj)
5 min readOct 20, 2024

--

If you’ve ever played the classic 2048 game, you know how addictive it can be. But have you ever wondered how it’s built? In this article, we take a deep dive into creating your very own version of the 2048 game from scratch using only HTML, CSS, and vanilla JavaScript. We’ll go step-by-step, explaining the logic and showcasing the entire development process, so you can both learn and build an interactive project that you can proudly showcase.

What is 2048?

2048 is a sliding tile puzzle game, where players combine numbered tiles by moving them in one of four directions (up, down, left, right) on a grid. When two tiles of the same value collide, they merge into one with double the value. The goal is to reach the tile with a value of 2048. It’s simple, elegant, and highly educational for those interested in learning the fundamentals of web development and game logic.

Why Build 2048?

The 2048 game is perfect for learners wanting to understand core concepts of web development, like DOM manipulation, CSS transitions, and JavaScript algorithms. By building this game, you’ll develop a solid foundation in the following:

  • DOM Manipulation: You’ll learn how to dynamically add, update, and remove elements from the DOM.
  • JavaScript Logic: From handling user input to combining tiles logically, you’ll get plenty of practice writing JavaScript functions.
  • Game State Management: You’ll learn how to manage game states like win, loss, and score tracking.

You can access the full project code and try it out on GitHub here.

The Building Blocks: File Structure

Before jumping into the code, let’s organize the project into different files for a more professional setup. Here’s how we structure our 2048 game:

vanilla-2048-game/
├── index.html
├── css/
│ └── styles.css
├── js/
│ └── main.js
├── assets/
│ └── (optional images/icons if needed)
├── README.md
└── LICENSE
  • index.html: Main HTML file, providing the basic structure of the game.
  • css/styles.css: Contains the styling that makes the game visually appealing.
  • js/main.js: Implements the entire game logic using JavaScript.
  • README.md: A detailed file explaining the project and how it works.
  • LICENSE: Contains the license for the project, which in this case is an MIT Licens

Step-by-Step Guide to Building the Game

Let’s break down the key aspects of the 2048 game implementation. We’ll discuss how each major JavaScript function works and how it fits into the bigger picture.

1. Initializing the Board

The first step is to create a 4x4 grid representing our game board, and then add two initial tiles:

function initializeBoard() {
board = Array(boardSize).fill().map(() => Array(boardSize).fill(0));
score = 0;
addNewTile();
addNewTile();
}
  • This function sets up the board with all tiles initialized to zero.
  • We use Array.fill() to create a matrix of size 4x4.
  • We then call addNewTile() twice to start the game with two tiles.

2. Adding a New Tile

To add excitement to the game, we randomly add a new tile after each valid move:

function addNewTile() {
let emptyTiles = [];

for (let i = 0; i < boardSize; i++) {
for (let j = 0; j < boardSize; j++) {
if (board[i][j] === 0) {
emptyTiles.push({ x: i, y: j });
}
}
}

if (emptyTiles.length > 0) {
let { x, y } = emptyTiles[Math.floor(Math.random() * emptyTiles.length)];
board[x][y] = Math.random() < 0.9 ? 2 : 4;
}
}
  • We gather all empty spots into emptyTiles.
  • We then pick a random empty spot and place either a 2 or 4 tile, with a 90% chance for 2 and 10% for 4.

3. Rendering the Board

Updating the user interface with the current board status is essential for user interaction:

function drawBoard() {
const gameBoard = document.getElementById('game-board');
gameBoard.innerHTML = '';

for (let i = 0; i < boardSize; i++) {
for (let j = 0; j < boardSize; j++) {
const tileValue = board[i][j];

if (tileValue > 0) {
const tile = document.createElement('div');
tile.className = 'tile';
tile.textContent = tileValue;
tile.classList.add(`tile-${tileValue}`);
tile.style.transform = `translate(${j * 100}px, ${i * 100}px)`;
gameBoard.appendChild(tile);
}
}
}

document.getElementById('score').textContent = `Score: ${score}`;
}
  • This function updates the DOM to match the current game state.
  • It uses CSS transforms for smooth animations and transitions.

4. Handling User Input

One of the most crucial aspects of any game is handling player interactions. Here we handle arrow key inputs:

function handleInput(event) {
if (isGameOver()) return;

switch (event.key) {
case 'ArrowUp':
moveUp();
break;
case 'ArrowDown':
moveDown();
break;
case 'ArrowLeft':
moveLeft();
break;
case 'ArrowRight':
moveRight();
break;
default:
return;
}

addNewTile();
drawBoard();
}
  • Depending on the arrow key pressed, the corresponding movement function (moveUp(), moveDown(), etc.) is called.
  • After the movement, a new tile is added, and the board is redrawn.

5. Sliding and Combining Tiles

The real action happens in sliding and combining tiles:

function slide(row) {
let arr = row.filter(val => val);
let missing = boardSize - arr.length;
let zeros = Array(missing).fill(0);
return arr.concat(zeros);
}

function combine(row) {
for (let i = 0; i < row.length - 1; i++) {
if (row[i] !== 0 && row[i] === row[i + 1]) {
row[i] *= 2;
score += row[i];
row[i + 1] = 0;
}
}
return row;
}
  • slide(row) moves all non-zero tiles to one end and fills the rest with zeros.
  • combine(row) merges adjacent tiles of the same value, updating the score accordingly.

6. Game Over Condition

The game ends when there are no possible moves left:

function isGameOver() {
for (let i = 0; i < boardSize; i++) {
for (let j = 0; j < boardSize; j++) {
if (board[i][j] === 0) return false;
if (j < boardSize - 1 && board[i][j] === board[i][j + 1]) return false;
if (i < boardSize - 1 && board[i][j] === board[i + 1][j]) return false;
}
}
return true;
}
  • This function checks for any empty tiles or adjacent tiles with the same value. If neither is found, the game is over.

Conclusion

By building the 2048 game from scratch, you’ve gained insights into web development fundamentals — how to manipulate the DOM, handle events, manage game state, and add animations to make the experience engaging. Whether you’re just starting your programming journey or brushing up on JavaScript concepts, building a project like this helps solidify the learning process.

The complete code is available on GitHub: Vanilla 2048 Game. Feel free to clone it, tinker with it, and even add new features like an undo button or high score tracker.

What’s Next?

You can take this game to the next level by adding additional features like:

  • High Score Tracking: Save the player’s highest score locally.
  • Advanced Animations: Add more visual flair when tiles merge.
  • Improving User Interface: Add more styling to make the game even more attractive.

I hope this tutorial inspired you to build your own version of 2048 or any similar game. Happy coding!

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

--

--

Atik Bin Mustafij (Sobuj)
Atik Bin Mustafij (Sobuj)

Written by Atik Bin Mustafij (Sobuj)

Expert Software Developer with a knack for mobile/web applications, database design, and architecture. Proficient in project management and business analysis.

No responses yet

Write a response