Simple Snake Game
Tags: javascript, html, css, beginners
Published: 2020-05-20 10:58:32

# Develop a simple snake game using vanilla javascript, html and css. [Show Demo](https://rabiroshan.github.io/snake_game/)
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/o82coecrz2mzqpjqqhhw.png)

#### 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`**: 
    ```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`**:
    ```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:
        ```javascript

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

	    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**: 
        ```javascript

        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:
			```javascript

			// 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:
			```javascript

			// 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:
        ```javascript

        // 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](https://github.com/RabiRoshan/snake_game). Feel free to create PR's 😊.