import { useState, useEffect, useRef, useCallback } from 'react' const BOARD_SIZE = 20 const INITIAL_SNAKE = [{ x: 10, y: 10 }] const INITIAL_FOOD = { x: 15, y: 15 } const INITIAL_DIRECTION = { x: 0, y: -1 } const GAME_SPEED = 150 const DIRECTIONS = { UP: { x: 0, y: -1 }, DOWN: { x: 0, y: 1 }, LEFT: { x: -1, y: 0 }, RIGHT: { x: 1, y: 0 } } export default function SnakeGame() { const canvasRef = useRef(null) const gameLoopRef = useRef(null) const [snake, setSnake] = useState(INITIAL_SNAKE) const [food, setFood] = useState(INITIAL_FOOD) const [direction, setDirection] = useState(INITIAL_DIRECTION) const [gameOver, setGameOver] = useState(false) const [score, setScore] = useState(0) const [isPlaying, setIsPlaying] = useState(false) const [isPaused, setIsPaused] = useState(false) // Generate random food position const generateFood = useCallback((currentSnake) => { let newFood do { newFood = { x: Math.floor(Math.random() * BOARD_SIZE), y: Math.floor(Math.random() * BOARD_SIZE) } } while (currentSnake.some(segment => segment.x === newFood.x && segment.y === newFood.y)) return newFood }, []) // Check collision with walls or self const checkCollision = useCallback((head, body) => { // Wall collision if (head.x < 0 || head.x >= BOARD_SIZE || head.y < 0 || head.y >= BOARD_SIZE) { return true } // Self collision return body.some(segment => segment.x === head.x && segment.y === head.y) }, []) // Move snake const moveSnake = useCallback(() => { if (gameOver || isPaused || !isPlaying) return setSnake(currentSnake => { const newSnake = [...currentSnake] const head = { ...newSnake[0] } head.x += direction.x head.y += direction.y // Check collision if (checkCollision(head, newSnake)) { setGameOver(true) setIsPlaying(false) return currentSnake } newSnake.unshift(head) // Check if food is eaten if (head.x === food.x && head.y === food.y) { setScore(prev => prev + 10) setFood(generateFood(newSnake)) } else { newSnake.pop() } return newSnake }) }, [direction, food, gameOver, isPaused, isPlaying, checkCollision, generateFood]) // Game loop useEffect(() => { if (isPlaying && !gameOver && !isPaused) { gameLoopRef.current = setInterval(moveSnake, GAME_SPEED) } else { clearInterval(gameLoopRef.current) } return () => clearInterval(gameLoopRef.current) }, [isPlaying, gameOver, isPaused, moveSnake]) // Draw game const draw = useCallback(() => { const canvas = canvasRef.current if (!canvas) return const ctx = canvas.getContext('2d') const cellSize = canvas.width / BOARD_SIZE // Clear canvas ctx.fillStyle = '#000' ctx.fillRect(0, 0, canvas.width, canvas.height) // Draw snake ctx.fillStyle = '#4ade80' snake.forEach((segment, index) => { if (index === 0) { // Head ctx.fillStyle = '#22c55e' } else { ctx.fillStyle = '#4ade80' } ctx.fillRect( segment.x * cellSize, segment.y * cellSize, cellSize - 2, cellSize - 2 ) }) // Draw food ctx.fillStyle = '#ef4444' ctx.fillRect( food.x * cellSize, food.y * cellSize, cellSize - 2, cellSize - 2 ) // Draw grid ctx.strokeStyle = '#333' ctx.lineWidth = 1 for (let i = 0; i <= BOARD_SIZE; i++) { ctx.beginPath() ctx.moveTo(i * cellSize, 0) ctx.lineTo(i * cellSize, canvas.height) ctx.stroke() ctx.beginPath() ctx.moveTo(0, i * cellSize) ctx.lineTo(canvas.width, i * cellSize) ctx.stroke() } }, [snake, food]) // Redraw when snake or food changes useEffect(() => { draw() }, [draw]) // Handle keyboard input const handleKeyPress = useCallback((e) => { if (!isPlaying) return const { key } = e const currentDirection = direction switch (key) { case 'ArrowUp': case 'w': case 'W': if (currentDirection.y !== 1) { setDirection(DIRECTIONS.UP) } break case 'ArrowDown': case 's': case 'S': if (currentDirection.y !== -1) { setDirection(DIRECTIONS.DOWN) } break case 'ArrowLeft': case 'a': case 'A': if (currentDirection.x !== 1) { setDirection(DIRECTIONS.LEFT) } break case 'ArrowRight': case 'd': case 'D': if (currentDirection.x !== -1) { setDirection(DIRECTIONS.RIGHT) } break case ' ': e.preventDefault() setIsPaused(prev => !prev) break } }, [isPlaying, direction]) useEffect(() => { window.addEventListener('keydown', handleKeyPress) return () => window.removeEventListener('keydown', handleKeyPress) }, [handleKeyPress]) // Touch controls const handleDirection = useCallback((newDirection) => { if (!isPlaying || isPaused) return const currentDirection = direction // Prevent reverse direction if ( (newDirection === DIRECTIONS.UP && currentDirection.y === 1) || (newDirection === DIRECTIONS.DOWN && currentDirection.y === -1) || (newDirection === DIRECTIONS.LEFT && currentDirection.x === 1) || (newDirection === DIRECTIONS.RIGHT && currentDirection.x === -1) ) { return } setDirection(newDirection) }, [isPlaying, isPaused, direction]) // Start new game const startNewGame = () => { setSnake(INITIAL_SNAKE) setFood(generateFood(INITIAL_SNAKE)) setDirection(INITIAL_DIRECTION) setGameOver(false) setScore(0) setIsPlaying(true) setIsPaused(false) } return (
{/* Score */}
Score: {score}
Length: {snake.length}
{/* Game Canvas */}
{/* Game Over Overlay */} {gameOver && (

Game Over!

Final Score: {score}

)} {/* Pause Overlay */} {isPaused && !gameOver && (

Paused

Press SPACE or tap Resume to continue

)}
{/* Controls */}
{!isPlaying || gameOver ? ( ) : (
)}
{/* Mobile Touch Controls */}
Touch Controls
{/* Instructions */}

Keyboard: Arrow keys or WASD to move, SPACE to pause

Mobile: Use touch controls below

) }