HomeCSS2048 Game

2048 Game

Introduction:

In this tutorial, we will learn how to build a popular puzzle game called 2048 using HTML, CSS, and JavaScript. 2048 is a grid-based game where the player combines numbers to reach the ultimate goal of creating a tile with the number 2048. We will cover the entire process of creating the game, from setting up the project folder structure to implementing the game logic and user interactions.

Things You Will Learn:

  1. Setting up the project folder structure
  2. Creating the HTML layout for the game board
  3. Styling the game using CSS
  4. Implementing the game logic in JavaScript
  5. Handling user interactions for swiping and keyboard controls

Video Tutorial:

To accompany this blog post, you can find a detailed video tutorial on my YouTube channel

 

Project Folder Structure:

To organize our project, we will follow a simple folder structure:

  • index.html: Contains the HTML structure of the game.
  • style.css: Includes the CSS styles for the game layout and appearance.
  • script.js: Contains the JavaScript code for implementing the game logic and handling user interactions.

HTML:

We will start by creating the game board using HTML. The board consists of a grid with multiple boxes, representing the tiles in the game. We will also include a start button, score display, and a game over screen. The HTML structure will be well-commented for better understanding.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>2048 Game</title>
    <!-- Google Fonts -->
    <link
      href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap"
      rel="stylesheet"
    />
    <!-- Stylesheet-->
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <div class="container hide">
      <div class="heading">
        <h1>2048</h1>
        <div class="score-container">
          Score:
          <span id="score">0</span>
        </div>
      </div>
      <div class="grid"></div>
    </div>
    <div class="cover-screen">
      <h2 id="over-text" class="hide">Game Over</h2>
      <p id="result"></p>
      <button id="start-button">Start Game</button>
    </div>
    <!-- Script -->
    <script src="script.js"></script>
  </body>
</html>

CSS:

Next, we will style the game using CSS. We will define the grid layout, box styles, animations, and transitions to make the game visually appealing. Additionally, we will design the game-over screen and the start button. CSS classes will be used to apply styles to different elements, keeping the code organized.

* {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
  font-family: "Poppins", sans-serif;
}
body {
  background-color: #faf8ef;
}
.heading {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
h1 {
  color: #776e65;
}
.container {
  position: absolute;
  transform: translate(-50%, -50%);
  left: 50%;
  top: 50%;
}
h1 {
  font-size: 4em;
}
.score-container {
  color: #776e65;
}
#score {
  font-weight: 600;
}
.grid {
  width: 25em;
  height: 25em;
  background-color: #cdc1b5;
  border: 5px solid #bbada0;
  margin: 0 auto;
  display: flex;
  flex-wrap: wrap;
  border-radius: 0.5em;
}
.box {
  width: 97.5px;
  height: 97.5px;
  border: 5px solid #bbada0;
  font-size: 40px;
  font-weight: bold;
  display: flex;
  align-items: center;
  justify-content: center;
}
.cover-screen {
  position: absolute;
  top: 0;
  left: 0;
  background-color: #faf8ef;
  height: 100%;
  width: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}
#start-button {
  background-color: #8f7a66;
  padding: 1em 2em;
  border: none;
  border-radius: 0.5em;
  font-size: 1em;
  letter-spacing: 0.2px;
  color: #ffffff;
}
.hide {
  display: none;
}
.box-2 {
  background-color: #eee4da;
  color: #727371;
}
.box-4 {
  background-color: #eee1c9;
  color: #727371;
}

.box-8 {
  background-color: #f3b27a;
  color: white;
}

.box-16 {
  background-color: #f69664;
  color: white;
}

.box-32 {
  background-color: #f77c5f;
  color: white;
}

.box-64 {
  background-color: #f75f3b;
  color: white;
}

.box-128 {
  background-color: #edd073;
  color: white;
}

.box-256 {
  background-color: #edcc63;
  color: white;
}

.box-512 {
  background-color: #edc651;
  color: white;
}

.box-1024 {
  background-color: #eec744;
  color: white;
}

.box-2048 {
  background-color: #ecc230;
  color: white;
}

JS:

The game logic will be implemented using JavaScript. We will handle user interactions, including swiping and keyboard controls. The code will generate random tiles, update the game matrix, and check for game-over conditions. We will also keep track of the score and update it accordingly. Proper comments will be provided to explain the code’s functionality.

let grid = document.querySelector(".grid");
const startButton = document.getElementById("start-button");
const container = document.querySelector(".container");
const coverScreen = document.querySelector(".cover-screen");
const result = document.getElementById("result");
const overText = document.getElementById("over-text");

let matrix,
  score,
  isSwiped,
  touchY,
  initialY = 0,
  touchX,
  initialX = 0,
  rows = 4,
  columns = 4,
  swipeDirection;

let rectLeft = grid.getBoundingClientRect().left;
let rectTop = grid.getBoundingClientRect().top;

const getXY = (e) => {
  touchX = e.touches[0].pageX - rectLeft;
  touchY = e.touches[0].pageY - rectTop;
};

const createGrid = () => {
  for (let i = 0; i < rows; i++) {
    for (let j = 0; j < columns; j++) {
      const boxDiv = document.createElement("div");
      boxDiv.classList.add("box");
      boxDiv.setAttribute("data-position", `${i}_${j}`);
      grid.appendChild(boxDiv);
    }
  }
};

const adjacentCheck = (arr) => {
  for (let i = 0; i < arr.length - 1; i++) {
    if (arr[i] == arr[i + 1]) {
      return true;
    }
  }
  return false;
};

const possibleMovesCheck = () => {
  for (let i in matrix) {
    if (adjacentCheck(matrix[i])) {
      return true;
    }
    let colarr = [];
    for (let j = 0; j < columns; j++) {
      colarr.push(matrix[i][j]);
    }
    if (adjacentCheck(colarr)) {
      return true;
    }
  }
  return false;
};

const randomPosition = (arr) => {
  return Math.floor(Math.random() * arr.length);
};

const hasEmptyBox = () => {
  for (let r in matrix) {
    for (let c in matrix[r]) {
      if (matrix[r][c] == 0) {
        return true;
      }
    }
  }
  return false;
};

const gameOverCheck = () => {
  if (!possibleMovesCheck()) {
    coverScreen.classList.remove("hide");
    container.classList.add("hide");
    overText.classList.remove("hide");
    result.innerText = `Final score: ${score}`;
    startButton.innerText = "Restart Game";
  }
};

const generateTwo = () => {
  if (hasEmptyBox()) {
    let randomRow = randomPosition(matrix);
    let randomCol = randomPosition(matrix[randomPosition(matrix)]);
    if (matrix[randomRow][randomCol] == 0) {
      matrix[randomRow][randomCol] = 2;
      let element = document.querySelector(
        `[data-position = '${randomRow}_${randomCol}']`
      );
      element.innerHTML = 2;
      element.classList.add("box-2");
    } else {
      generateTwo();
    }
  } else {
    gameOverCheck();
  }
};

const generateFour = () => {
  if (hasEmptyBox()) {
    let randomRow = randomPosition(matrix);
    let randomCol = randomPosition(matrix[randomPosition(matrix)]);
    if (matrix[randomRow][randomCol] == 0) {
      matrix[randomRow][randomCol] = 4;
      let element = document.querySelector(
        `[data-position= '${randomRow}_${randomCol}']`
      );
      element.innerHTML = 4;
      element.classList.add("box-4");
    } else {
      generateFour();
    }
  } else {
    gameOverCheck();
  }
};

const removeZero = (arr) => arr.filter((num) => num);
const checker = (arr, reverseArr = false) => {
  arr = reverseArr ? removeZero(arr).reverse() : removeZero(arr);

  for (let i = 0; i < arr.length - 1; i++) {
    if (arr[i] == arr[i + 1]) {
      arr[i] += arr[i + 1];
      arr[i + 1] = 0;
      score += arr[i];
    }
  }

  arr = reverseArr ? removeZero(arr).reverse() : removeZero(arr);

  let missingCount = 4 - arr.length;
  while (missingCount > 0) {
    if (reverseArr) {
      arr.unshift(0);
    } else {
      arr.push(0);
    }
    missingCount -= 1;
  }
  return arr;
};

const slideDown = () => {
  for (let i = 0; i < columns; i++) {
    let num = [];
    for (let j = 0; j < rows; j++) {
      num.push(matrix[j][i]);
    }
    num = checker(num, true);
    for (let j = 0; j < rows; j++) {
      matrix[j][i] = num[j];
      let element = document.querySelector(`[data-position='${j}_${i}']`);
      element.innerHTML = matrix[j][i] ? matrix[j][i] : "";
      element.classList.value = "";
      element.classList.add("box", `box-${matrix[j][i]}`);
    }
  }

  let decision = Math.random() > 0.5 ? 1 : 0;
  if (decision) {
    setTimeout(generateFour, 200);
  } else {
    setTimeout(generateTwo, 200);
  }
};

const slideUp = () => {
  for (let i = 0; i < columns; i++) {
    let num = [];
    for (let j = 0; j < rows; j++) {
      num.push(matrix[j][i]);
    }
    num = checker(num);
    for (let j = 0; j < rows; j++) {
      matrix[j][i] = num[j];
      let element = document.querySelector(`[data-position = '${j}_${i}']`);
      element.innerHTML = matrix[j][i] ? matrix[j][i] : "";
      element.classList.value = "";
      element.classList.add("box", `box-${matrix[j][i]}`);
    }
  }
  let decision = Math.random() > 0.5 ? 1 : 0;
  if (decision) {
    setTimeout(generateFour, 200);
  } else {
    setTimeout(generateTwo, 200);
  }
};

const slideRight = () => {
  for (let i = 0; i < rows; i++) {
    let num = [];
    for (let j = 0; j < columns; j++) {
      num.push(matrix[i][j]);
    }
    num = checker(num, true);
    for (let j = 0; j < columns; j++) {
      matrix[i][j] = num[j];
      let element = document.querySelector(`[data-position = '${i}_${j}']`);
      element.innerHTML = matrix[i][j] ? matrix[i][j] : "";
      element.classList.value = "";
      element.classList.add("box", `box-${matrix[i][j]}`);
    }
  }
  let decision = Math.random() > 0.5 ? 1 : 0;
  if (decision) {
    setTimeout(generateFour, 200);
  } else {
    setTimeout(generateTwo, 200);
  }
};

const slideLeft = () => {
  for (let i = 0; i < rows; i++) {
    let num = [];
    for (let j = 0; j < columns; j++) {
      num.push(matrix[i][j]);
    }

    num = checker(num);
    for (let j = 0; j < columns; j++) {
      matrix[i][j] = num[j];
      let element = document.querySelector(`[data-position = '${i}_${j}']`);
      element.innerHTML = matrix[i][j] ? matrix[i][j] : "";
      element.classList.value = "";
      element.classList.add("box", `box-${matrix[i][j]}`);
    }
  }
  let decision = Math.random() > 0.5 ? 1 : 0;
  if (decision) {
    setTimeout(generateFour, 200);
  } else {
    setTimeout(generateTwo, 200);
  }
};

document.addEventListener("keyup", (e) => {
  if (e.code == "ArrowLeft") {
    slideLeft();
  } else if (e.code == "ArrowRight") {
    slideRight();
  } else if (e.code == "ArrowUp") {
    slideUp();
  } else if (e.code == "ArrowDown") {
    slideDown();
  }
  document.getElementById("score").innerText = score;
});

grid.addEventListener("touchstart", (event) => {
  isSwiped = true;
  getXY(event);
  initialX = touchX;
  initialY = touchY;
});

grid.addEventListener("touchmove", (event) => {
  if (isSwiped) {
    getXY(event);
    let diffX = touchX - initialX;
    let diffY = touchY - initialY;
    if (Math.abs(diffY) > Math.abs(diffX)) {
      swipeDirection = diffX > 0 ? "down" : "up";
    } else {
      swipeDirection = diffX > 0 ? "right" : "left";
    }
  }
});

grid.addEventListener("touchend", () => {
  isSwiped = false;
  let swipeCalls = {
    up: slideUp,
    down: slideDown,
    left: slideLeft,
    right: slideRight,
  };
  swipeCalls[swipeDirection]();
  document.getElementById("score").innerText = score;
});

const startGame = () => {
  score = 0;
  document.getElementById("score").innerText = score;
  grid.innerHTML = "";
  matrix = [
    [0, 0, 0, 0],
    [0, 0, 0, 0],
    [0, 0, 0, 0],
    [0, 0, 0, 0],
  ];
  container.classList.remove("hide");
  coverScreen.classList.add("hide");
  createGrid();
  generateTwo();
  generateTwo();
};

startButton.addEventListener("click", () => {
  startGame();
  swipeDirection = "";
});

Conclusion:

In this tutorial, we have explored how to build a 2048 game using HTML, CSS, and JavaScript. By following the step-by-step instructions and watching the accompanying video tutorial, you can create your own version of the game. The project folder structure ensures organization and separation of concerns, while the HTML, CSS, and JavaScript codes work together to create an interactive and enjoyable gaming experience. Have fun building your 2048 game and feel free to customize it further to make it your own!

RELATED ARTICLES

1 COMMENT

  1. hello, maybe you have the code of this game, but with a choice of difficulty level 2*2 – easy, medium 4*4, and difficult 6*6. very necessary

LEAVE A REPLY

Please enter your comment!
Please enter your name here

twelve − 9 =

Most Popular