```html Snake Game
Score: 0
Game Over!
``` ```typescript // Game constants const GRID_SIZE = 20; const CELL_SIZE = 20; const INITIAL_SPEED = 150; // milliseconds // Game state let canvas: HTMLCanvasElement; let ctx: CanvasRenderingContext2D; let snake: { x: number; y: number }[] = []; let food: { x: number; y: number }; let direction: 'up' | 'down' | 'left' | 'right' = 'right'; let nextDirection: 'up' | 'down' | 'left' | 'right' = 'right'; let score = 0; let gameRunning = true; let gameLoop: number; function init(): void { canvas = document.getElementById('gameCanvas') as HTMLCanvasElement; ctx = canvas.getContext('2d')!; // Initialize snake in the middle const startX = Math.floor(canvas.width / (CELL_SIZE * 2)) * CELL_SIZE; const startY = Math.floor(canvas.height / (CELL_SIZE * 2)) * CELL_SIZE; snake = [ { x: startX, y: startY }, { x: startX - CELL_SIZE, y: startY }, { x: startX - CELL_SIZE * 2, y: startY } ]; generateFood(); document.addEventListener('keydown', handleKeyPress); gameLoop = window.setInterval(update, INITIAL_SPEED); } function generateFood(): void { const maxX = Math.floor(canvas.width / CELL_SIZE) - 1; const maxY = Math.floor(canvas.height / CELL_SIZE) - 1; let newFood: { x: number; y: number }; let overlapping: boolean; do { overlapping = false; newFood = { x: Math.floor(Math.random() * maxX) * CELL_SIZE, y: Math.floor(Math.random() * maxY) * CELL_SIZE }; // Check if food overlaps with snake for (const segment of snake) { if (segment.x === newFood.x && segment.y === newFood.y) { overlapping = true; break; } } } while (overlapping); food = newFood; } function handleKeyPress(event: KeyboardEvent): void { switch (event.key) { case 'ArrowUp': if (direction !== 'down') nextDirection = 'up'; break; case 'ArrowDown': if (direction !== 'up') nextDirection = 'down'; break; case 'ArrowLeft': if (direction !== 'right') nextDirection = 'left'; break; case 'ArrowRight': if (direction !== 'left') nextDirection = 'right'; break; } } function update(): void { if (!gameRunning) return; // Update direction direction = nextDirection; // Calculate new head position const head = { ...snake[0] }; switch (direction) { case 'up': head.y -= CELL_SIZE; break; case 'down': head.y += CELL_SIZE; break; case 'left': head.x -= CELL_SIZE; break; case 'right': head.x += CELL_SIZE; break; } // Check collision with walls if ( head.x < 0 || head.y < 0 || head.x >= canvas.width || head.y >= canvas.height ) { gameOver(); return; } // Check collision with self for (const segment of snake) { if (head.x === segment.x && head.y === segment.y) { gameOver(); return; } } // Add new head snake.unshift(head); // Check if food is eaten if (head.x === food.x && head.y === food.y) { score += 10; document.getElementById('score')!.textContent = `Score: ${score}`; generateFood(); } else { // Remove tail if no food eaten snake.pop(); } draw(); } function draw(): void { // Clear canvas ctx.fillStyle = '#fff'; ctx.fillRect(0, 0, canvas.width, canvas.height); // Draw snake ctx.fillStyle = '#00ff00'; for (const segment of snake) { ctx.fillRect(segment.x, segment.y, CELL_SIZE, CELL_SIZE); } // Draw food ctx.fillStyle = '#ff0000'; ctx.fillRect(food.x, food.y, CELL_SIZE, CELL_SIZE); // Draw grid ctx.strokeStyle = '#eee'; ctx.lineWidth = 0.5; for (let i = 0; i <= canvas.width; i += CELL_SIZE) { ctx.beginPath(); ctx.moveTo(i, 0); ctx.lineTo(i, canvas.height); ctx.stroke(); } for (let j = 0; j <= canvas.height; j += CELL_SIZE) { ctx.beginPath(); ctx.moveTo(0, j); ctx.lineTo(canvas.width, j); ctx.stroke(); } } function gameOver(): void { gameRunning = false; clearInterval(gameLoop); document.getElementById('game-over')!.style.display = 'block'; } // Start the game when the page loads window.addEventListener('load', init); ```