Make Snake game using react.js (javascript)

In this tutorial, we are going to create a snake game using React.js, and I assure you that it will help you improve your logic.

This is how our game is going to perform :

The first step would be setting up our React application and for that you need choose a development environment. For this project, we’ll use Vite, but feel free to choose another environment that suits your preferences.

If you don’t know how to set up React app using Vite Click here.

Now we are gonna create a jsx file called Snake-Game.jsx in our src folder, and import it in App.jsx.

In our project, all of the code will be contained within the Snake-Game.jsx file and
We are going to use Inline CSS here so you don’t need to install any other CSS Frameworks.

We are ready to code :

1. Creating UI

To create the design of a user interface, we will make a div called “great-grandfather” and put another div called “grandfather” inside it.

The div “grandfather” has two parts called “father” and “uncle”. In our project, “father” is the game’s title and “uncle” is the game’s outline.
The “uncle” will also contain a child div called “cousin” which is our snake.

Here we also have two variables (x and y) that determine the position of our snake in the game. We are using these variables to set the “top” and “left” properties of the “cousin” element’s style attribute. where “top” defines the Vertical position and “left” defines the horizontal position our snake.

This is How our game is gonna look :

We have set the initial values of both variables (x and y) to 100, so the snake will start at that position in the game.

import React from "react";

const [x, setX] = useState(100);
  const [y, setY] = useState(100);

const SnakeGame = () => {
  return (
    <div className="great-grandfather" style={{ margin: "20px" }}>
      <div
      className="grandfather"
        style={{
          display: "flex",
          flexDirection: "column",
          fontSize: "38px",
          width: "500px",
          height: "500px",
        }}
      >
        <div className="father" >snake game</div>

        <div
        className="uncle"
          style={{
            position: "relative",
            width: "400px",
            height: "400px",
            border: "2px solid blue",
            borderRadius: "10px",
          }}
        >
          <div
          className="cousin"
            style={{
              width: "8px",
              height: "8px",
              border: "1px solid blue",
              backgroundColor: "blue",
              position: "absolute",
              left: x,
              top: y,
              borderRadius: "10px",
            }}
          ></div>
        </div>
      </div>
    </div>
  );
};

export default SnakeGame;

2. Moving the Snake

To move our snake, first, we will focus on having control over the buttons that will change the direction of our snake.

To change the direction of our snake, we will create a variable called “keyPressed”. This variable will hold the value of the key that is pressed. Initially, the value of the variable will be set to “none”.

Note: We will use Arrow keys to control our snake.

To detect which button is pressed on the keyboard, we will use the addEventListener() method. Inside this method, we will create a variable called “name”, which will hold the name of the button that is pressed.

"keyup" is the first parameter passed to the “addEventListener()” method and it is a string that specifies the type of event to listen for. In this case, the event type is "keyup", which means the event will be triggered when a keyboard key is released after being pressed down,

The second parameter is a callback function that will be executed when the event is triggered. In this code, the callback function extracts the name of the key that was released and calls the “setKeyPressed” function, passing in the name of the key as an argument.

And "false is the third parameter passed to the “addEventListener()” method and it indicates whether to use event bubbling or event capturing.

After detecting which button is pressed, we will pass its name to the "setKeyPressed“. This will update the state of the “keyPressed" with the name of the pressed button.

Now we have value of the button which is pressed.

const [keyPressed, setKeyPressed] = useState("none");

document.addEventListener(
    "keyup",
    (event) => {
      const name = event.key;
      setKeyPressed(name);
    },
    false
  );
  
  console.log(keyPressed) // Output: The Button you pressed.

After getting the value of pressed button, Now we will move to the main part,
which is moving the snake ,

We will use the useEffect() hook and include x, y, and keyPressed variables as dependencies. This means whenever there is a change in any of these three values, useEffect() will automatically re-render the component.

Inside the useEffect() hook, we will create a variable called “interval”. We will use the setInterval() method to repeatedly execute a function every 200 milliseconds. This function will continue to run until the clearInterval() method is called.

Inside the setInterval() method, we will use a switch statement that will depend on the value of the keyPressed variable. We will add different cases for different button presses. For example, if the keyPressed variable is “ArrowUp”, we will make the snake move upward, and similarly for other cases.

But here is a problem, when we click on a button to move the snake in a particular direction, it starts moving but does not leave its previous position. This means that the snake appears to be in two places at once, which is not desirable.

To fix the problem, we need to stop the setInterval() method every time the snake changes direction. We can achieve this by returning clearInterval() from our useEffect() hook and passing the interval variable, as an argument, which contains the setInterval() method. This will clear the previous interval and create a new one in the new direction.

This is how our snake is going to move :

Now, the snake will start moving in the direction we want with a delay of 200 milliseconds between each movement. The movement will be smooth and the snake will not appear in two places at once.

useEffect(() => {

    const interval = setInterval(() => {
      switch (keyPressed) {
        case "ArrowUp":
          setY(y - 10);
          break;
        case "ArrowDown":
          setY(y + 10);
          break;
        case "ArrowLeft":
          setX(x - 10);
          break;
        case "ArrowRight":
          setX(x + 10);
          break;
      }
    }, 200);
    return () => clearInterval(interval);
    
  }, [x, y, keyPressed]);

3. Food for snake

To make food for the snake, we will make three state variables called “foodX”, “foodY”, and “score”. The initial value of “foodX” and “foodY” will be 150, and the initial value of “score” will be 0. “foodX” represents the horizontal position of the food and “foodY” represents the vertical position of the food and “score” represents the food eaten by snake.

To show the food on the screen, we will create a new “div” element called “cousin-3” inside the “uncle” div, after the “cousin” div.

After that, we will make a new function named “food_collide”. The function “food_collide” will check for a collision between the snake and the food. It will do this by comparing the position of the snake’s head, represented by the variables “x” and “y”, with the position of the food, represented by the variables “foodX” and “foodY”.

If the head of the snake is within 10 units of distance from the food in both the horizontal and vertical directions, then the function will return “true”. Otherwise, it will return “false”.

And after the completion of collision we will set an if else statement in our useEffcet() hook that if “food_collide” is true so generate a new position for the food with the help of Math.random method and also increase our score to +1.
Now, the food is ready to eat.

This is how our game will appear after the changes we made :

Note: I have commented out the old code.

const [foodX, setFoodX] = useState(50);
  const [foodY, setFoodY] = useState(50);
  const [score, setScore] = useState(0);
  
  
  const food_collide = () => {
    if (
      x >= foodX - 10 &&
      x <= foodX + 10 &&
      y >= foodY - 10 &&
      y <= foodY + 10
    ) {
      return true;
    } else {
      return false;
    }
  };
  
 // useEffect(() => {
    
    if (food_collide()) {
      setFoodX(Math.round(Math.random() * 140));
      setFoodY(Math.round(Math.random() * 140));
      setScore(score + 1);
    }

  //  const interval = setInterval(() => {
  // setPreviousPos((pre) => [...pre, [x, y]]);
  //    switch (keyPressed) {
  //     case "ArrowUp":
  //        setY(y - 10);
  //        break;
  //      case "ArrowDown":
  //        setY(y + 10);
  //        break;
  //      case "ArrowLeft":
  //        setX(x - 10);
  //        break;
  //      case "ArrowRight":
  //        setX(x + 10);
  //        break;
  //    }
  //  }, 200);
  //  return () => clearInterval(interval);
   }, [x, y, keyPressed, foodX, foodY, score]);
  
  return(
// <div
  //           className="cousin"
   //          style={{
   //             width: "8px",
   //            height: "8px",
   //           border: "1px solid blue",
   //          backgroundColor: "blue",
   //         position: "absolute",
   //          left: x,
   //           top: y,
   //          borderRadius: "10px",
   //       }}
   //    ></div> 
   <div
   className="cousine-3"
            style={{
              width: "8px",
              height: "8px",
              border: "1px solid green",
              backgroundColor: "green",
              position: "absolute",
              left: foodX,
              top: foodY,
              borderRadius: "10px",
            }}
          ></div>
  )
}

After the snake collides with the food, the food changes its position, which is what we wanted to happen. Now, we can move on to the next part.

4. Making snake tail

Based on what we’ve made so far, the blue dot represents the head of the snake, while the green dot represents the food. Whenever the snake eats the food, its length should increase depending on the amount of food it eats.

To make the snake longer, we need to store its previous positions in an array called “previousPos”. This array will contain an array of the snake’s previous positions. Then, we will use the “map” method to iterate through the “previousPos” array. Inside the “map” method, we will add a “div” element with the class name “cousin-2”.

The “map” method goes through all previous positions of the snake, even those we don’t want. Therefore, we will use the “splice()” method to remove some previous positions we don’t need.

We will use the “splice()” method inside an “if” statement, which checks if the length of the “previousPos” array is greater than the score. If it is, we will remove some of the previous positions we don’t need.

If you want to know more about splice() method you can check out this article :

 const [previousePos, setPreviousPos] = useState([]);

// useEffect(() => {

//if (food_collide()) {
//      setFoodX(Math.round(Math.random() * 140));
//      setFoodY(Math.round(Math.random() * 140));
//      setScore(score + 1);
//   }

 if (previousePos.length > score) {
      arr.splice(0, 1);
    }
   // const interval = setInterval(() => {
      setPreviousPos((pre) => [...pre, [x, y]]);
    //  switch (keyPressed) {
      //  case "ArrowUp":
        //  setY(y - 10);
          //break;
        //case "ArrowDown":
          //setY(y + 10);
          //break;
        //case "ArrowLeft":
          //setX(x - 10);
          //break;
        //case "ArrowRight":
          //setX(x + 10);
          //break;
      //}
    //}, 200);
    //return () => clearInterval(interval);
  //}, [x, y, keyPressed]);
 
 return (
 
 // ----> Your code
 
 
// <div
         //   className="cousin"
           // style={{
           //   width: "8px",
           // height: "8px",
           // border: "1px solid blue",
           //   backgroundColor: "blue",
           //   position: "absolute",
           //   left: x,
           //   top: y,
           //   borderRadius: "10px",
           // }}
           // ></div>
          {previousePos.map((e, index) => (
            <div
              key={index}
              className="cousin-2"
              style={{
                width: "8px",
                height: "8px",
                border: "1px solid blue",
                backgroundColor: "skyblue",
                position: "absolute",
                left: e[0],
                top: e[1],
                borderRadius: "10px",
              }}
            ></div>
          ))}
          
          
          // ----> Your code
 )
 }

5. Creating outline

To prevent the snake from moving outside the game box, we need to use “if” and “else” statements in our code. These statements will check the position of the snake’s head and make sure that it stays within the boundaries of the game box.

// useEffect(() => {

   //    ---->
      
   // const interval = setInterval(() => {
   //   setArr((prevArr) => [...prevArr, [x, y]]);

    //  switch (keyPressed) {
    //    case "ArrowUp":
    //      setY(y - 10);
    //      break;
    //    case "ArrowDown":
    //      setY(y + 10);
    //      break;
    //    case "ArrowLeft":
    //      setX(x - 10);
    //     break;
    //    case "ArrowRight":
    //      setX(x + 10);
    //      break;
    //  }

      if (x > 390) {
        setX(0);
      }
      if (x < 0) {
        setX(390);
      }
      if (y > 370) {
        setY(0);
      }
      if (y < 0) {
        setY(370);
      }
   // }, 200);

  //  return () => clearInterval(interval);
  // }, [x, y, keyPressed, foodX, foodY, score]);

In this code there are four statements,

1. If the value of x is more than 390, it means that if our snake moves horizontally to the right and goes beyond the screen boundary, it will reappear on the left-hand side of the screen.

2. If the value of x is less than 0, it means that if our snake moves horizontally to the left and goes beyond the screen boundary, it will reappear on the Right-hand side of the screen.

3. If the value of y is more than 370, it means that if our snake moves verticaly to upward and goes beyond the screen boundary, it will reappear on the bottom of the screen.

4. If the value of y is less than 0, it means that if our snake moves vertically to the bottom and goes beyond the screen boundary, it will reappear from the top of the screen.

6. Collision of snake

To get collision of the snake we will create a function called “snakecollide”.

The function uses the forEach() method to loop through each element e in the “previousePos” array. “previousePos” is an array of arrays that contains the previous positions of the snake’s body.

For each element e, the function checks if the first element of e (stored at index 0) is equal to the current value of x and if the second element of e (stored at index 1) is equal to the current value of y.

If the conditions are true, it means that the snake’s head has collided with its body, and the function calls setScore(0) and setArr([]) to reset the score and the array of the snake’s body to an empty array, indicating that the game is over and this function will be called every time the snake moves.

//function food_Colied() {
  //  if (
  //    x >= foodX - 10 &&
  //    x <= foodX + 10 &&
  //    y >= foodY - 10 &&
  //    y <= foodY + 10
  //  ) {
  //    return true;
  //  } else {
  //    return false;
  //  }
  //}
  const snakecollide = () => {
    previousePos.forEach((e) => {
      if (e[0] == x && e[1] == y) {
        setScore(0);
        setArr([]);
      }
    });
  };
  
  
 // useEffect(() => {
    
 //   const interval = setInterval(() => {
  //    setArr((prevArr) => [...prevArr, [x, y]]);

  //    switch (keyPressed) {
  //      case "ArrowUp":
  //        setY(y - 10);
  //        break;
  //      case "ArrowDown":
  //        setY(y + 10);
  //        break;
  //      case "ArrowLeft":
  //        setX(x - 10);
  //        break;
  //      case "ArrowRight":
  //        setX(x + 10);
  //        break;
  //    }

 //     if (x > 390) {
 //       setX(0);
 //     }
 //     if (x < 0) {
 //       setX(390);
 //     }
 //     if (y > 370) {
 //       setY(0);
 //     }
 //     if (y < 0) {
 //       setY(370);
 //     }
 //   }, 200);
    snakecollide();

 //   return () => clearInterval(interval);
 // }, [x, y, keyPressed, foodX, foodY, score]);

I hope this tutorial may have helped you create a snake game and improve your logical thinking skills.

Leave a Comment