Free 2048 Game Source Code in HTML, CSS & JavaScript

Here is free 2048 game source code built with HTML, CSS and JavaScript. Learn how to create, customize and run a responsive number puzzle game.

Have you ever started playing 2048 and told yourself, “Just one more round”? That is what makes this simple puzzle game so much enjoyable. You slide numbered tiles around the board, combine matching numbers, and try to create the 2048 tile.

In this tutorial, you will learn how to build your own 2048-style game using HTML, CSS, and JavaScript. The game runs directly in a web browser, so you do not need to install any software or use a game engine.

You can use the source code to practise web development, learn basic game logic, or create a small browser game for your website.

What Is the 2048 Game?

2048 is a number puzzle game played on a grid of 16 squares. Each square can contain a numbered tile. When you move the tiles in one direction, tiles with the same value combine into one tile with their total value.

For example, two tiles with the number 2 can combine to make 4. Two tiles with the number 4 can combine to make 8. The goal is to create a tile with the number 2048.

The challenge is to plan your moves carefully because the board gradually fills up. If there are no empty squares and no matching tiles that can combine, the game ends.

Features of This 2048 Game

This version includes the main features needed for a playable browser puzzle game:

  • A responsive 4 × 4 game board.
  • Randomly generated starting tiles.
  • Smooth tile movement and merging logic.
  • Score tracking during gameplay.
  • Best score saved in the browser.
  • Keyboard controls for desktop users.
  • Swipe controls for mobile devices.
  • A new game button to restart the puzzle.
  • Win and game-over messages.
  • A clean layout that adapts to smaller screens.

The project uses plain HTML, CSS, and JavaScript. You do not need an external library.

Free 2048 Game Source Code

The following code contains the complete game in a single HTML file. You can copy it into a file named index.html and open it in your browser.

Complete HTML, CSS and JavaScript Code

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>2048 Puzzle Game</title>

  <style>
    * {
      box-sizing: border-box;
    }

    body {
      margin: 0;
      padding: 24px 14px;
      background: #faf8ef;
      color: #776e65;
      font-family: Arial, sans-serif;
      display: flex;
      justify-content: center;
      min-height: 100vh;
    }

    .game {
      width: 100%;
      max-width: 430px;
    }

    .top-bar {
      display: flex;
      justify-content: space-between;
      align-items: center;
      gap: 12px;
      margin-bottom: 18px;
    }

    h1 {
      margin: 0;
      font-size: clamp(38px, 10vw, 58px);
      color: #776e65;
    }

    .scores {
      display: flex;
      gap: 6px;
    }

    .score-box {
      min-width: 68px;
      padding: 8px;
      border-radius: 5px;
      background: #bbada0;
      color: white;
      text-align: center;
    }

    .score-box span {
      display: block;
      font-size: 11px;
      font-weight: bold;
      text-transform: uppercase;
    }

    .score-box strong {
      display: block;
      margin-top: 3px;
      font-size: 20px;
    }

    .intro {
      line-height: 1.6;
      margin: 0 0 15px;
    }

    .controls {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 10px;
      margin-bottom: 14px;
    }

    .controls p {
      margin: 0;
      font-size: 14px;
    }

    button {
      border: none;
      border-radius: 5px;
      padding: 11px 14px;
      background: #8f7a66;
      color: white;
      font-size: 14px;
      font-weight: bold;
      cursor: pointer;
    }

    button:hover {
      background: #776452;
    }

    .board {
      position: relative;
      display: grid;
      grid-template-columns: repeat(4, minmax(0, 1fr));
      gap: 10px;
      padding: 10px;
      background: #bbada0;
      border-radius: 7px;
      touch-action: none;
      user-select: none;
    }

    .cell {
      aspect-ratio: 1;
      display: flex;
      justify-content: center;
      align-items: center;
      min-width: 0;
      border-radius: 5px;
      background: rgba(238, 228, 218, 0.35);
      font-size: clamp(20px, 6vw, 34px);
      font-weight: bold;
    }

    .tile-2    { background: #eee4da; color: #776e65; }
    .tile-4    { background: #ede0c8; color: #776e65; }
    .tile-8    { background: #f2b179; color: white; }
    .tile-16   { background: #f59563; color: white; }
    .tile-32   { background: #f67c5f; color: white; }
    .tile-64   { background: #f65e3b; color: white; }
    .tile-128  { background: #edcf72; color: white; font-size: clamp(17px, 5vw, 27px); }
    .tile-256  { background: #edcc61; color: white; font-size: clamp(17px, 5vw, 27px); }
    .tile-512  { background: #edc850; color: white; font-size: clamp(17px, 5vw, 27px); }
    .tile-1024 { background: #edc53f; color: white; font-size: clamp(14px, 4vw, 23px); }
    .tile-2048 { background: #edc22e; color: white; font-size: clamp(14px, 4vw, 23px); }

    .tile-large {
      background: #3c3a32;
      color: white;
      font-size: clamp(13px, 3.7vw, 21px);
    }

    .message {
      min-height: 26px;
      margin: 15px 0;
      font-weight: bold;
      text-align: center;
      line-height: 1.5;
    }

    .help {
      margin-top: 22px;
      padding: 15px;
      border-radius: 6px;
      background: #eee4da;
      line-height: 1.6;
      font-size: 14px;
    }

    @media (max-width: 360px) {
      .board {
        gap: 6px;
        padding: 7px;
      }

      .top-bar {
        align-items: flex-start;
        flex-direction: column;
      }

      .scores {
        width: 100%;
      }

      .score-box {
        flex: 1;
      }
    }
  </style>
</head>

<body>
  <main class="game">
    <div class="top-bar">
      <h1>2048</h1>

      <div class="scores">
        <div class="score-box">
          <span>Score</span>
          <strong id="score">0</strong>
        </div>

        <div class="score-box">
          <span>Best</span>
          <strong id="best">0</strong>
        </div>
      </div>
    </div>

    <p class="intro">
      Join the numbers and reach the <strong>2048 tile!</strong>
      Use the arrow keys or swipe on your screen.
    </p>

    <div class="controls">
      <p>Combine matching numbers.</p>
      <button id="restart">New Game</button>
    </div>

    <div
      class="board"
      id="board"
      role="grid"
      aria-label="2048 puzzle board"
    ></div>

    <p class="message" id="message" aria-live="polite"></p>

    <div class="help">
      <strong>How to play:</strong>
      Move the tiles up, down, left, or right. Matching numbers merge
      into one tile. Each successful move adds a new tile to the board.
      Try to reach 2048 without running out of possible moves.
    </div>
  </main>

  <script>
    const SIZE = 4;

    const boardElement = document.getElementById("board");
    const scoreElement = document.getElementById("score");
    const bestElement = document.getElementById("best");
    const messageElement = document.getElementById("message");
    const restartButton = document.getElementById("restart");

    let board = [];
    let score = 0;
    let best = loadBest();
    let won = false;
    let keepPlaying = false;
    let gameOver = false;

    function loadBest() {
      try {
        return Number(localStorage.getItem("2048-best")) || 0;
      } catch (error) {
        return 0;
      }
    }

    function saveBest() {
      try {
        localStorage.setItem("2048-best", String(best));
      } catch (error) {
        // The game can still run if browser storage is unavailable.
      }
    }

    function newGame() {
      board = Array.from(
        { length: SIZE },
        () => Array(SIZE).fill(0)
      );

      score = 0;
      won = false;
      keepPlaying = false;
      gameOver = false;
      messageElement.textContent = "";

      addRandomTile();
      addRandomTile();
      render();
    }

    function addRandomTile() {
      const empty = [];

      for (let row = 0; row < SIZE; row++) {
        for (let col = 0; col < SIZE; col++) {
          if (board[row][col] === 0) {
            empty.push({ row, col });
          }
        }
      }

      if (empty.length === 0) return;

      const position = empty[
        Math.floor(Math.random() * empty.length)
      ];

      board[position.row][position.col] =
        Math.random() < 0.9 ? 2 : 4;
    }

    function render() {
      boardElement.innerHTML = "";

      for (let row = 0; row < SIZE; row++) {
        for (let col = 0; col < SIZE; col++) {
          const value = board[row][col];
          const cell = document.createElement("div");

          cell.className = "cell";
          cell.setAttribute("role", "gridcell");

          if (value !== 0) {
            cell.textContent = value;

            if (value <= 2048) {
              cell.classList.add("tile-" + value);
            } else {
              cell.classList.add("tile-large");
            }

            cell.setAttribute("aria-label", String(value));
          } else {
            cell.setAttribute("aria-label", "Empty");
          }

          boardElement.appendChild(cell);
        }
      }

      scoreElement.textContent = score;
      bestElement.textContent = best;
    }

    function getLine(direction, index) {
      const coordinates = [];

      for (let step = 0; step < SIZE; step++) {
        let row;
        let col;

        if (direction === "left") {
          row = index;
          col = step;
        } else if (direction === "right") {
          row = index;
          col = SIZE - 1 - step;
        } else if (direction === "up") {
          row = step;
          col = index;
        } else {
          row = SIZE - 1 - step;
          col = index;
        }

        coordinates.push({ row, col });
      }

      return coordinates;
    }

    function slideLine(values) {
      const numbers = values.filter(value => value !== 0);
      const result = [];
      let gained = 0;

      for (let i = 0; i < numbers.length; i++) {
        if (numbers[i] === numbers[i + 1]) {
          const merged = numbers[i] * 2;

          result.push(merged);
          gained += merged;
          i++;
        } else {
          result.push(numbers[i]);
        }
      }

      while (result.length < SIZE) {
        result.push(0);
      }

      return { result, gained };
    }

    function move(direction) {
      if (gameOver) return;

      const previous = JSON.stringify(board);
      let gainedThisMove = 0;

      for (let index = 0; index < SIZE; index++) {
        const coordinates = getLine(direction, index);

        const values = coordinates.map(
          position => board[position.row][position.col]
        );

        const outcome = slideLine(values);
        gainedThisMove += outcome.gained;

        coordinates.forEach((position, step) => {
          board[position.row][position.col] =
            outcome.result[step];
        });
      }

      if (JSON.stringify(board) === previous) {
        return;
      }

      score += gainedThisMove;

      if (score > best) {
        best = score;
        saveBest();
      }

      addRandomTile();

      if (!won && !keepPlaying && has2048()) {
        won = true;
        messageElement.textContent =
          "You reached 2048! Great job! Keep playing to improve your score.";
      }

      if (!hasMoves()) {
        gameOver = true;
        messageElement.textContent =
          "Game over! No more moves are available. Start a new game to try again.";
      }

      render();
    }

    function has2048() {
      return board.some(row => row.includes(2048));
    }

    function hasMoves() {
      for (let row = 0; row < SIZE; row++) {
        for (let col = 0; col < SIZE; col++) {
          if (board[row][col] === 0) return true;

          if (
            col < SIZE - 1 &&
            board[row][col] === board[row][col + 1]
          ) {
            return true;
          }

          if (
            row < SIZE - 1 &&
            board[row][col] === board[row + 1][col]
          ) {
            return true;
          }
        }
      }

      return false;
    }

    document.addEventListener("keydown", function(event) {
      const directions = {
        ArrowUp: "up",
        ArrowDown: "down",
        ArrowLeft: "left",
        ArrowRight: "right"
      };

      if (directions[event.key]) {
        event.preventDefault();
        move(directions[event.key]);
      }
    });

    let startX = 0;
    let startY = 0;

    boardElement.addEventListener("pointerdown", function(event) {
      startX = event.clientX;
      startY = event.clientY;
    });

    boardElement.addEventListener("pointerup", function(event) {
      const deltaX = event.clientX - startX;
      const deltaY = event.clientY - startY;

      if (Math.max(Math.abs(deltaX), Math.abs(deltaY)) < 25) {
        return;
      }

      if (Math.abs(deltaX) > Math.abs(deltaY)) {
        move(deltaX > 0 ? "right" : "left");
      } else {
        move(deltaY > 0 ? "down" : "up");
      }
    });

    restartButton.addEventListener("click", newGame);

    newGame();
  </script>
</body>
</html>

How to Run the Game Coding

You do not need a complicated development setup to test this project. Follow these steps:

  1. Copy the complete source code above.
  2. Open a text editor such as Notepad or Visual Studio Code.
  3. Create a new file named index.html.
  4. Paste the code into the file and save it.
  5. Open index.html in a modern web browser.
  6. Start combining the tiles using your keyboard or mobile screen.

If you want to publish the game on your website, you can upload the file to your hosting account. You can also place the code inside an HTML page on your WordPress site, depending on how your theme and editor handle custom JavaScript.

Important: If you use the WordPress block editor, do not paste the entire document into a normal Paragraph block. For a standalone game, use a suitable HTML page or template. Some WordPress setups restrict JavaScript in the Custom HTML block.

How Does the 2048 Game Works

The game may look simple, but a few important JavaScript functions work together to make it playable.

1. Creating the Board

The game begins with a 4 × 4 array containing 16 positions. Empty positions hold the value 0. Two starting tiles are then placed randomly on the board.

2. Moving the Tiles

When a player presses an arrow key or swipes across the board, JavaScript identifies the requested direction. It reads each row or column in the correct order and moves its numbers toward that side.

3. Combining Matching Numbers

When two neighbouring tiles have the same value, they combine into a single tile with twice the value. For example, 8 and 8 become 16.

Each pair can merge only once during a single move. This prevents a newly created tile from merging repeatedly in the same turn.

4. Adding New Tiles

After a successful move, the game chooses an empty position and adds a new tile. Most new tiles contain 2, while some contain 4.

5. Updating the Score

Whenever tiles merge, their new values are added to the score. The game also saves the best score in the browser using local storage, where available.

6. Detecting the End of the Game

The game checks whether the board contains empty cells or neighbouring tiles that can merge. If neither is possible, it displays a game-over message.

When the 2048 tile appears, the game displays a congratulatory message and allows the player to continue.

How to Customize the Game

One advantage of building a game with HTML, CSS, and JavaScript is that you can change its appearance and behaviour without needing a game engine.

Change the Background Colour

Find the following CSS rule:

body {
  background: #faf8ef;
}

Replace the colour value with your preferred background colour. You can also adjust the board and tile colours to create a different visual style.

Change the Game Width

Find this rule:

.game {
  width: 100%;
  max-width: 430px;
}

Increase max-width if you want a wider game layout. Keep mobile screen sizes in mind when making changes.

Change the Starting Tile Values

Inside the addRandomTile() function, find:

Math.random() < 0.9 ? 2 : 4;

This controls the type of tile that appears. You can experiment with the probability, but keep the game balanced so it remains enjoyable.

Add Sound Effects

You could add sound effects when tiles merge, when the player reaches 2048, or when the game ends. Browser audio normally needs to be triggered by a user interaction, so test sound behaviour on both desktop and mobile devices.

Improve the Design

You can add a short animation when tiles move, improve the win message, or introduce a dark theme. For more advanced development, you could also add a pause option, a reset confirmation, or selectable board sizes.

Can You Use This Game on a WordPress Website?

Yes. You can publish a browser game on a WordPress website, but the best method depends on your hosting and editor setup.

For a clean implementation, you can:

  • Create a dedicated page for the game.
  • Keep the CSS and JavaScript in separate files.
  • Use a custom page template or a suitable game-embedding method.
  • Add instructions and a short explanation above or below the playable game.
  • Test the page on mobile phones, tablets, and desktop computers.

Separating the HTML, CSS, and JavaScript into different files can make future updates easier. A single HTML file is useful for beginners because everything is in one place.

Is This Game Suitable for Beginners?

Yes. A 2048-style game is a useful project for anyone learning front-end web development.

HTML provides the page structure, CSS controls the board and tile appearance, and JavaScript handles the game rules. Working on this project can help you understand arrays, loops, event listeners, conditional logic, and browser storage.

Once the basic version works, try changing the colours or adding a feature of your own. Making small improvements is a practical way to learn.

Frequently Asked Questions Here

1. Is the 2048 game source code free?

Yes. The code provided in this tutorial is available for you to copy, test, and modify for your own learning and projects. If you redistribute it, review any third-party assets or additions you include and make sure you have the necessary rights.

2. Does the game require a server?

No. The basic game runs locally in a modern browser. You only need web hosting if you want to make it available online to other visitors.

3. Can I play this game on a mobile phone?

Yes. The layout adjusts to smaller screens, and swipe gestures are supported. You can also test the game on a desktop using the arrow keys.

4. Can I change the game’s design?

Yes. You can modify the CSS to change the colours, spacing, typography, board size, and other visual details.

5. Why is my best score not saved?

The best score uses browser local storage. It may not persist if the browser blocks storage, the user clears site data, or the game is opened in a restricted browsing environment.

6. Can I add this game to my own website?

Yes. You can host the HTML file directly or integrate the game into a suitable web page. For WordPress, make sure your chosen method supports the JavaScript required by the game.

Building a 2048-style puzzle game is a great way to practise HTML, CSS, and JavaScript together. The rules are easy to understand, but implementing tile movement, merging, scoring, and game-over detection gives you useful programming experience.

Explore more free HTML5 game source code and tutorials on our website to discover other projects you can learn from and customize.