<div id="app"></div>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #0093E9;
background-image: linear-gradient(160deg, #0093E9 0%, #80D0C7 100%);
}
#app {
font-family: Arial, sans-serif;
}
.game-title {
color: #D9AFD9;
}
.restart-button {
background-image: linear-gradient(to right, #314755 0%, #26a0da 51%, #314755 100%);
background-color: transparent;
color: #fff;
cursor: pointer;
outline: none;
border: none;
box-shadow: 0 0 20px #eee;
border-radius: 10px;
display: block;
margin-bottom: 10px;
padding: 5px 20px;
text-transform: uppercase;
-webkit-appearance: button;
}
/*
* https://frontendeval.com/questions/snake
*
* Create an HTML/CSS/JS version of Snake
*/
const { useState, useEffect, useRef } = React;
const ROWS = 15;
const COLS = 15;
const Direction = {
UP: 'UP',
DOWN: 'DOWN',
LEFT: 'LEFT',
RIGHT: 'RIGHT'
};
const getRandomPosition = () => {
return {
x: Math.floor(Math.random() * COLS),
y: Math.floor(Math.random() * ROWS)
}
};
const initialSnake = [
{ x : 7, y : 7 },
{ x : 7, y : 8 },
{ x : 7, y : 9 },
];
const SnakeGame = () => {
const [snake, setSnake] = useState(initialSnake);
const [apple, setApple] = useState(getRandomPosition());
const [direction, setDirection] = useState(Direction.RIGHT);
const [gameStarted, setGameStarted] = useState(false);
const [gameOver, setGameOver] = useState(false);
const [score, setScore] = useState(0);
const gameRef = useRef(null);
const restartGame = () => {
setSnake(initialSnake);
setDirection(Direction.RIGHT);
setGameOver(false);
setScore(0);
setApple(getRandomPosition());
}
useEffect(() => {
const handleKeyPress = (event) => {
if (!gameStarted) setGameStarted(true);
switch (event.key) {
case 'ArrowUp':
if (direction !== Direction.DOWN) setDirection(Direction.UP);
break;
case 'ArrowDown':
if (direction !== Direction.UP) setDirection(Direction.DOWN);
break;
case 'ArrowLeft':
if (direction !== Direction.RIGHT) setDirection(Direction.LEFT);
break;
case 'ArrowRight':
if (direction !== Direction.LEFT) setDirection(Direction.RIGHT);
break;
default:
break;
}
};
window.addEventListener('keydown', handleKeyPress);
return () => window.removeEventListener('keydown', handleKeyPress);
}, [direction]);
useEffect(() => {
const moveSnake = () => {
if (gameOver) return;
if (!gameStarted) return;
const newSnake = [...snake];
const head = { ...newSnake[0] };
switch (direction) {
case Direction.UP:
head.y -= 1;
break;
case Direction.DOWN:
head.y += 1;
break;
case Direction.LEFT:
head.x -= 1;
break;
case Direction.RIGHT:
head.x += 1;
break;
default:
break;
}
newSnake.unshift(head);
if (head.x === apple.x && head.y === apple.y) {
setApple(getRandomPosition());
setScore(score + 1);
} else {
newSnake.pop();
}
if (
head.x < 0 ||
head.x >= COLS ||
head.y < 0 ||
head.y >= ROWS ||
newSnake.slice(1).some((segment) => segment.x === head.x && segment.y === head.y)
) {
setGameOver(true);
}
setSnake(newSnake);
};
const gameInterval = setInterval(moveSnake, 150);
gameRef.current = gameInterval;
return () => clearInterval(gameInterval);
}, [snake, direction, apple, gameOver, score, gameStarted]);
return (
<div>
<h1 className="game-title">Snake Game</h1>
<button className="restart-button" onClick={restartGame}>Restart</button>
<div>
{gameOver ? (
<div>Game Over! Your Score: {score}</div>
) : (
<div>Score: {score}</div>
)}
<div style={{ display: 'grid', gridTemplateColumns: `repeat(${COLS}, 20px)` }}>
{[...Array(ROWS)].map((_, rowIndex) =>
[...Array(COLS)].map((_, colIndex) => (
<div
key={`${rowIndex}-${colIndex}`}
style={{
width: '20px',
height: '20px',
border: '1px solid #ccc',
backgroundColor: snake.some((seg) => seg.x === colIndex && seg.y === rowIndex)
? '#333'
: apple.x === colIndex && apple.y === rowIndex
? 'red'
: 'transparent',
}}
></div>
))
)}
</div>
</div>
</div>
);
}
ReactDOM.render(<SnakeGame />, document.getElementById('app'));
View Compiled
This Pen doesn't use any external CSS resources.