DEV Community

Rabi Roshan
Rabi Roshan

Posted on • Edited on

Simple Snake Game

Develop a simple snake game using vanilla javascript, html and css. Show Demo

Alt Text

NOTE: READ COMMENTS TO UNDERSTAND THE CODE ✌🏻

NOTE: YOU CAN SKIP TO STEP 4 TO READ THE ACTUAL CODE IN JS

LET US START:

  1. Create the the base files:
    • index.html
    • main.css
    • jscript.js
  2. Add the following content to index.html:

    
    <!DOCTYPE  html>
    <html>
    <head  lang="en">
        <meta  charset="UTF-8"  />
        <meta  name="viewport"  content="width=device width,initial-scale=1"  />
    
        <title>Snake v8</title>
    
        <!-- Link our styles from main.css -->
        <link  rel="stylesheet"  type="text/css" href="main.css"  />
    </head>
    
    <body>
        <!-- #gameContainer is the main game board-->
        <div  class="gameContainer"  id="gameContainer"></div>
    
        <!-- #scoreContainer contains the scores -->
        <div  id="scoreContainer">
            <div  class="scoreBoard">Food: <span id="pointsEarned">0</span></div>
            <div  class="scoreBoard">Blocks: <span  id="blocksTravelled">0</span></div>
        </div>
    
        <!-- #onScreenControllers contains the navigation buttons for mobile screens -->
        <div  id="mobileControllers">
            <button  id="leftButton">◀️</button>
            <div>
                <button  id="upButton">🔼</button>
                <button  id="downButton">🔽</button>
            </div>
            <button  id="rightButton">▶️</button>
        </div>
    </body>
    
    <!-- #Load our jscript.js containing the game logic -->
    <script  src="jscript.js"></script>
    
    </html>
    
  3. Add the following content to main.css:

    body {
        background-color: darkslategrey;
        text-align: center;
    }
    
    /* GAME BOARD STYLES */
    #gameContainer {
        /*
        width and height of .gameBoardPixel should 1/40 of the width and height of #gameContainer,
        because it is used in calculation in the jscript.js file
        */
        width: 40vw;
        height: 40vw;
        margin: 2vw  auto;
        background-color: #0c1021;
        border: solid  10px  slategrey;
        border-radius: 10px;
    
        -webkit-box-shadow: 0px  0px  20px  3px  rgba(0, 0, 0, 0.6);
        -moz-box-shadow: 0px  0px  20px  3px  rgba(0, 0, 0, 0.6);
        box-shadow: 0px  0px  20px  3px  rgba(0, 0, 0, 0.6);
    }
    
    .gameBoardPixel {
        /*
        width and height of .gameBoardPixel should 1/40 of the width and height of #gameContainer,
        because it is used in calculation in the jscript.js file
        */
        width: 1vw;
        height: 1vw;
        border-radius: 10px;
        float: left;
    }
    /* GAME BOARD STYLES END*/
    
    /* SNAKE STYLES */
    .snakeBodyPixel {
        background-color: #fd6401;
        box-shadow: 0  0  5px  #fd6401;
    }
    /* SNAKE STYLES END*/
    
    /* FOOD STYLES */
    .food {
        background-color: #68e768;
    }
    /* FOOD STYLES END*/
    
    /* SCORE STYLES */
    #scoreContainer {
        width: 40vw;
        display: flex;
        margin: auto;
        justify-content: space-around;
    }
    
    .scoreBoard {
        border-radius: 10px;
        border: solid  5px  slategrey;
        color: dimgray;
        background-color: #0c1021;
        display: inline-block;
        padding: 1vw;
        width: 40%;
    
        -webkit-box-shadow: 0px  0px  20px  3px  rgba(0, 0, 0, 0.6);
        -moz-box-shadow: 0px  0px  20px  3px  rgba(0, 0, 0, 0.6);
        box-shadow: 0px  0px  20px  3px  rgba(0, 0, 0, 0.6);
    }
    /* SCORE STYLES END */
    
    /* Hide #onScreenControllers on desktop */
    #onScreenControllers {
        display: none;
    }
    
    @media  only  screen  and (max-width: 768px) {
        /* MOBILE STYLES*/
    
        #gameContainer {
            width: 80vw;
            height: 80vw;
        }
    
        .gameBoardPixel {
            width: 2vw;
            height: 2vw;
        }
    
        #scoreContainer {
            width: 80vw;
        }
    
        #onScreenControllers {
            width: 80vw;
            margin: 5vw  auto;
            display: flex;
            justify-content: space-evenly;
            align-items: center;
        }
    
        #onScreenControllers  >  div {
            display: flex;
            flex-direction: column;
        }
    
        #onScreenControllers  button {
            background-color: transparent;
            height: 20vw;
            width: 20vw;
            font-size: 10vw;
            border: none;
        }
    
        #onScreenControllers  button:focus {
            outline: none;
        }
    
        #onScreenControllers  button:active {
            background-color: slategray;
        }
    }
    
  4. Finally, we will write the JavaScript code in jscript.js where the actual logic is written.

    1. Initialize the scores:

      
      let  totalFoodAte  =  0;
      let  totalDistanceTravelled  =  0;
      
    2. Code for the Game Board pixels:

      
      const  gameContainer  =  document.getElementById("gameContainer");
      const  createGameBoardPixels  = () => {
          // Populate the [#gameContainer] div with small div's representing game pixels
          for (let  i  =  1; i  <=  1600; ++i) {
              gameContainer.innerHTML  =  `${gameContainer.innerHTML} <div class="gameBoardPixel" id="pixel${i}"></div>`;
          }
      };
      // This variable always holds the updated array of game pixels created by createGameBoardPixels() :
      const  gameBoardPixels  =  document.getElementsByClassName("gameBoardPixel");
      
    3. Code for the Food:

      
      let  currentFoodPostion  =  0; // Initially set to 0
      const  createFood  = () => {
          // Remove previous food;
          gameBoardPixels[currentFoodPostion].classList.remove("food");
      
          // Create new food
          currentFoodPostion  =  Math.random();
          currentFoodPostion  =  Math.floor(currentFoodPostion  *  1600);
          gameBoardPixels[currentFoodPostion].classList.add("food");
      };
      
    4. Code for the Snake 🐍:

      1. Code related to the Direction of the snake:

        
        // Direction codes (Keyboard key codes for arrow keys):
        const  LEFT_DIR  =  37;
        const  UP_DIR  =  38;
        const  RIGHT_DIR  =  39;
        const  DOWN_DIR  =  40;
        
        // Set snake direction initially to right
        let  snakeCurrentDirection  =  RIGHT_DIR;
        
        const  changeDirection  =  newDirectionCode  => {
            // Change the direction of the snake
            if (newDirectionCode  ==  snakeCurrentDirection) return;
            if (newDirectionCode  ==  LEFT_DIR  &&  snakeCurrentDirection  != RIGHT_DIR) {
                snakeCurrentDirection  =  newDirectionCode;
            } else  if (newDirectionCode  ==  UP_DIR  &&  snakeCurrentDirection  !=  DOWN_DIR) {
                snakeCurrentDirection  =  newDirectionCode;
            } else  if (newDirectionCode  ==  RIGHT_DIR  && snakeCurrentDirection  !=  LEFT_DIR) {
                snakeCurrentDirection  =  newDirectionCode;
            } else  if (newDirectionCode  ==  DOWN_DIR  &&  snakeCurrentDirection  !=  UP_DIR) {
                snakeCurrentDirection  =  newDirectionCode;
            }
        };
        
      2. Code related to the Movement of the snake:

        
        // Let the starting position of the snake be at the middle of game board
        let  currentSnakeHeadPosition  =  799;
        let  snakeLength  =  1000; // Initial length of the snake = 1000
        
        // Move snake continously by calling this function repeatedly :
        const  moveSnake  = () => {
            switch (snakeCurrentDirection) {
                case  LEFT_DIR:
                    --currentSnakeHeadPosition;
                    const  isSnakeHeadAtLastGameBoardPixelTowardsLeft  = currentSnakeHeadPosition  %  40  ==  39  ||  currentSnakeHeadPosition  <  0;
                    if (isSnakeHeadAtLastGameBoardPixelTowardsLeft) {
                        currentSnakeHeadPosition  =  currentSnakeHeadPosition  +  40;
                    }
                    break;
        
                case  UP_DIR:
                    currentSnakeHeadPosition  =  currentSnakeHeadPosition  -  40;
                    const  isSnakeHeadAtLastGameBoardPixelTowardsUp  = currentSnakeHeadPosition  <  0;
                    if (isSnakeHeadAtLastGameBoardPixelTowardsUp) {
                        currentSnakeHeadPosition  =  currentSnakeHeadPosition  +  1600;
                    }
                    break;
        
                case  RIGHT_DIR:
                    ++currentSnakeHeadPosition;
                    const  isSnakeHeadAtLastGameBoardPixelTowardsRight  = currentSnakeHeadPosition  %  40  ==  0;
                    if (isSnakeHeadAtLastGameBoardPixelTowardsRight) {
                        currentSnakeHeadPosition  =  currentSnakeHeadPosition  -  40;
                    }
                    break;
        
                case  DOWN_DIR:
                    currentSnakeHeadPosition  =  currentSnakeHeadPosition  +  40;
                    const  isSnakeHeadAtLastGameBoardPixelTowardsDown  = currentSnakeHeadPosition  >  1599;
                    if (isSnakeHeadAtLastGameBoardPixelTowardsDown) {
                        currentSnakeHeadPosition  =  currentSnakeHeadPosition  -  1600;
                    }
                    break;
        
                default:
                    break;
            }
            let  nextSnakeHeadPixel  = gameBoardPixels[currentSnakeHeadPosition];
        
            // Kill snake if it bites itself:
            if (nextSnakeHeadPixel.classList.contains("snakeBodyPixel")) {
                // Stop moving the snake
                clearInterval(moveSnakeInterval);
                if (!alert(`You have ate ${totalFoodAte} food by travelling ${totalDistanceTravelled} blocks.`))
                window.location.reload();
            }
        
            // If not killed add the snake body:
            nextSnakeHeadPixel.classList.add("snakeBodyPixel");
        
            // This fuction removes the snake body from the end of the snake as it moves.
            // Also note that snakeLength is used as the timeout interval
            setTimeout(() => {
                nextSnakeHeadPixel.classList.remove("snakeBodyPixel");
            }, snakeLength);
        
            // Update total distance travelled
            totalDistanceTravelled++;
            // Update in UI:
            document.getElementById("blocksTravelled").innerHTML  = totalDistanceTravelled;
        
            // If snike bites the food:
            if (currentSnakeHeadPosition  ==  currentFoodPostion) {
                // Update total food ate
                totalFoodAte++;
                // Update in UI:
                document.getElementById("pointsEarned").innerHTML  =  totalFoodAte;
                // Increase Snake length:
                snakeLength  =  snakeLength  +  100;
                // Create new food:
                createFood();
            }
        };
        
    5. Code to start the game using the above logic:

      
      // Create game board pixels:
      createGameBoardPixels();
      
      // Create initial food:
      createFood();
      
      // Move snake:
      // The variable, "moveSnakeInterval" is used to stop the snake on snake killed.
      var  moveSnakeInterval  =  setInterval(moveSnake, 80);
      
      // Call change direction function on keyboard key-down event:
      addEventListener("keydown", e  =>  changeDirection(e.keyCode));
      
      // CONFIGURE THE ON SCREEN CONTROLLERS:
      const  leftButton  =  document.getElementById("leftButton");
      const  rightButton  =  document.getElementById("rightButton");
      const  upButton  =  document.getElementById("upButton");
      const  downButton  =  document.getElementById("downButton");
      
      leftButton.onclick  = () =>  changeDirection(LEFT_DIR);
      rightButton.onclick  = () =>  changeDirection(RIGHT_DIR);
      upButton.onclick  = () =>  changeDirection(UP_DIR);
      downButton.onclick  = () =>  changeDirection(DOWN_DIR);
      

Completed project available at Github. Feel free to create PR's 😊.

Top comments (0)