Skip to content

Instantly share code, notes, and snippets.

@grabcode
Created July 12, 2023 13:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save grabcode/8b8bf98d3ae9939901952e8e3911262e to your computer and use it in GitHub Desktop.
Save grabcode/8b8bf98d3ae9939901952e8e3911262e to your computer and use it in GitHub Desktop.
Snake game
<!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>
// 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());
@grabcode
Copy link
Author

Demo video of the game-over, score and restart:

https://www.loom.com/share/01a655782a5a440aba73660ea131a627

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment