Created
July 12, 2023 13:35
-
-
Save grabcode/8b8bf98d3ae9939901952e8e3911262e to your computer and use it in GitHub Desktop.
Snake game
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<head> | |
<meta charset="UTF-8"> | |
<title>Text-based snake game</title> | |
<style> | |
#board { | |
border: 3px solid; | |
float: left; | |
font-size: 24px; | |
line-height: 1.5ex; | |
margin: 10px; | |
} | |
</style> | |
</head> | |
<body> | |
<pre id="board"></pre> | |
<p id="score"></p> | |
<button id="restart" style="display:none;">Restart</button> | |
<script src="widget.js"></script> | |
</body> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Subtasks; | |
// done: snake navigating edge to edge | |
// done: generate randomly located fruit | |
// todo: exclude any occupied snake location from fruit's candidate locations | |
// done: increasing snake's size when eating a fruit | |
// done: snake's tail following its head | |
// done: game over when snake eat itself | |
// done: preventing snake suddenly going opposite of current direction | |
// todo: diamonds | |
// todo (stretch): second snake | |
let startGameLoop = true; | |
const boardWidth = 16; | |
const boardHeight = 10; | |
const board = document.getElementById('board'); | |
const score = document.getElementById('score'); | |
const restart = document.getElementById('restart'); | |
const keyToDirection = { | |
'ArrowUp': { x: 0, y: -1, opposite: 'ArrowDown' }, | |
'ArrowRight': { x: 1, y: 0, opposite: 'ArrowLeft' }, | |
'ArrowDown': { x: 0, y: 1, opposite: 'ArrowUp' }, | |
'ArrowLeft': { x: -1, y: 0, opposite: 'ArrowRight' }, | |
}; | |
function initSnake() { | |
return { | |
x: boardWidth / 2, | |
y: boardHeight / 2, | |
tail: [ | |
{ x: boardWidth / 2 - 1, y: boardHeight / 2}, | |
{ x: boardWidth / 2 - 2, y: boardHeight / 2}, | |
], | |
direction: keyToDirection['ArrowRight'], | |
}; | |
} | |
const snake = { | |
...initSnake(), | |
}; | |
function gameOver() { | |
computeAndDisplayScore(); | |
restart.style.display = 'block'; | |
startGameLoop = !startGameLoop; | |
} | |
function gameOn() { | |
score.innerHTML = '' | |
restart.style.display = 'none'; | |
startGameLoop = !startGameLoop; | |
Object.assign(snake, initSnake()); | |
} | |
function getRandomInt(max) { | |
return Math.floor(Math.random() * max); | |
} | |
function generateRandomFruitPosition() { | |
const x = getRandomInt(boardWidth); | |
const y = getRandomInt(boardHeight); | |
return {x , y}; | |
} | |
const fruit = generateRandomFruitPosition(); | |
function renderBoard() { | |
let newBoard = ''; | |
for (let y = 0; y < boardHeight; y++) { | |
for (let x = 0; x < boardWidth; x++) { | |
let char = ' '; | |
if (x === snake.x && y === snake.y) { | |
char = '*'; | |
} else if (x === fruit.x && y === fruit.y) { | |
char = '+'; | |
} | |
snake.tail.forEach(pieceOfTail => { | |
if (pieceOfTail.x === x && pieceOfTail.y === y) { | |
char = '*'; | |
} | |
}); | |
newBoard += char; | |
} | |
newBoard += '\n'; | |
} | |
board.textContent = newBoard; | |
} | |
function computeAndDisplayScore() { | |
score.innerHTML = `Score: ${snake.tail.length * 10}`; | |
} | |
function gameLoop() { | |
snake.tail.splice(0, 0, { | |
x: snake.x, | |
y: snake.y | |
}); | |
snake.x += snake.direction.x; | |
snake.y += snake.direction.y; | |
snake.x = (boardWidth + snake.x) % boardWidth; | |
snake.y = (boardHeight + snake.y) % boardHeight; | |
snake.tail.forEach((pieceOfTail) => { | |
if (pieceOfTail.x === snake.x && pieceOfTail.y === snake.y) { | |
gameOver(); | |
} | |
}); | |
if (fruit.x === snake.x && fruit.y === snake.y) { | |
const newFruit = generateRandomFruitPosition(); | |
fruit.x = newFruit.x; | |
fruit.y = newFruit.y; | |
} else { | |
snake.tail.pop(); | |
} | |
renderBoard(); | |
} | |
renderBoard(); | |
setInterval(() => { | |
startGameLoop && gameLoop(); | |
}, 200); | |
document.addEventListener('keydown', e => { | |
if (keyToDirection[e.key] && e.key !== snake.direction.opposite) { | |
snake.direction = keyToDirection[e.key]; | |
} | |
}); | |
restart.addEventListener('click', () => gameOn()); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Demo video of the game-over, score and restart:
https://www.loom.com/share/01a655782a5a440aba73660ea131a627