Source: https://frontendeval.com/questions/snake
Code Pen Example: https://codepen.io/alfinity/pen/MWOMeXe?editors=1111
/*High-level approach1. Scope out how to manage the game state2. Figure out how to draw the board and initial score3. Figure out how to move one square with the arrow keys4. Figure out how to place apples randomly on the board5. Figure out how to add the tail at the end after eating apple, randomizing new apple position, bumping score6. Figure out collision detection with itself to end the game or hitting the borders to end the gameBonus: Starting game after pressing spacebar key/showing initial help information, keeping track of high score in localstorage, restarting game by pressing a key, adding obstacles, etc.*/const snakeCanvas = document.getElementById("snake-canvas");const snakeCtx = snakeCanvas.getContext("2d");// Initialize the game state// Keep track of player position playerX, playerYlet playerX = 8;let playerY = 8;// Keep track of apple position appleX, appleYlet appleX = 12;let appleY = 12;// Keep track of snake trail positions in an array [{x,y}, {x,y},...]let trail = [];const initialTailLength = 2;let tail = initialTailLength;// 15 x 15 boardconst totalCells = 15;// 400 width/height gridconst cellLength = 400 / totalCells;// Set the velocity as snake keeps on moving with momentum, velocityX and velocityYlet velocityX = -1;let velocityY = 0;// Keep track of the score aka number of apples eatenlet score = 0;// Handle moving snake in response to arrow key downdocument.addEventListener("keydown", moveSnake);const framesPerSecond = 1000 / 10;let gameIntervalId = null;gameStep();function gameStep() {// Keep moving the player in the same directionplayerX += velocityX;playerY += velocityY;// Color the boardsnakeCtx.fillStyle = "black";snakeCtx.fillRect(0, 0, snakeCanvas.width, snakeCanvas.height);// Color the player/snake trailsnakeCtx.fillStyle = "lime";snakeCtx.fillRect(playerX * cellLength,playerY * cellLength,cellLength - 2,cellLength - 2);// Color the player snake trailfor (let i = 0; i < trail.length; i++) {snakeCtx.fillRect(trail[i].x * cellLength,trail[i].y * cellLength,cellLength - 2,cellLength - 2);if (trail[i].x === playerX && trail[i].y === playerY) {return gameOver();}}// Add onto trail with player's current positiontrail.push({ x: playerX, y: playerY });// Remove the ends of trail to stay within length of tail as we move aroundwhile (trail.length > tail) {trail.shift();}// Color the applesnakeCtx.fillStyle = "red";snakeCtx.fillRect(appleX * cellLength,appleY * cellLength,cellLength - 2,cellLength - 2);// If player position hits the borders, we should stop the gameconst playerHitBorders =playerX < 0 ||playerX >= totalCells ||playerY < 0 ||playerY >= totalCells;if (playerHitBorders) {console.log("Player hit borders, game over");return gameOver();}// If the player eats an apple, we should bump the score, randomize the next location of the appleif (playerX === appleX && playerY === appleY) {score++;tail++;appleX = Math.floor(Math.random() * totalCells);appleY = Math.floor(Math.random() * totalCells);}// Update the scoreupdateScore();}function gameOver() {updateScore();playerX = 8;playerY = 8;tail = initialTailLength;score = 0;trail = [];clearInterval(gameIntervalId);gameIntervalId = null;}function updateScore() {snakeCtx.font = "30px Georgia";snakeCtx.fillStyle = "white";snakeCtx.fillText(score, 200, 50);}// Move the snake in response to pressing arrow keys// by changing the velocity directionconst leftKey = 37;const upKey = 38;const rightKey = 39;const downKey = 40;const spaceBarKey = 32;const xKey = 88;function moveSnake(event) {switch (event.keyCode) {case leftKey:velocityX = -1;velocityY = 0;break;case upKey:velocityX = 0;velocityY = -1;break;case rightKey:velocityX = 1;velocityY = 0;break;case downKey:velocityX = 0;velocityY = 1;break;case spaceBarKey:if (gameIntervalId === null) {gameIntervalId = setInterval(gameStep, framesPerSecond);}break;case xKey:clearInterval(gameIntervalId);gameIntervalId = null;break;default:// Ignore any other keys}}