class SnakeGame { constructor(canvasId, scoreId) { this.canvas = document.getElementById(canvasId); this.ctx = this.canvas.getContext('2d'); this.scoreElement = document.getElementById(scoreId); this.gridSize = 20; this.tileCount = this.canvas.width / this.gridSize; this.reset(); window.addEventListener('keydown', this.handleKeyDown.bind(this)); } reset() { this.snake = [ { x: 10, y: 10 }, { x: 10, y: 11 }, { x: 10, y: 12 } ]; this.food = this.getRandomFood(); this.dx = 0; this.dy = -1; this.score = 0; this.updateScore(); this.gameOver = false; } getRandomFood() { let newFood; while (true) { newFood = { x: Math.floor(Math.random() * this.tileCount), y: Math.floor(Math.random() * this.tileCount) }; // 确保食物不生成在蛇身上 let collision = this.snake.some(segment => segment.x === newFood.x && segment.y === newFood.y); if (!collision) break; } return newFood; } handleKeyDown(e) { switch (e.key) { case 'ArrowUp': if (this.dy === 0) { this.dx = 0; this.dy = -1; } break; case 'ArrowDown': if (this.dy === 0) { this.dx = 0; this.dy = 1; } break; case 'ArrowLeft': if (this.dx === 0) { this.dx = -1; this.dy = 0; } break; case 'ArrowRight': if (this.dx === 0) { this.dx = 1; this.dy = 0; } break; } } update() { if (this.gameOver) return; const head = { x: this.snake[0].x + this.dx, y: this.snake[0].y + this.dy }; // 碰撞墙壁检测 if (head.x < 0 || head.x >= this.tileCount || head.y < 0 || head.y >= this.tileCount) { this.endGame(); return; } // 碰撞自身检测 if (this.snake.some(segment => segment.x === head.x && segment.y === head.y)) { this.endGame(); return; } this.snake.unshift(head); // 进食检测 if (head.x === this.food.x && head.y === this.food.y) { this.score += 10; this.updateScore(); this.food = this.getRandomFood(); } else { this.snake.pop(); } } draw() { // 清屏 this.ctx.fillStyle = '#1a1a1a'; this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); // 画网格背景 (可选) this.ctx.strokeStyle = 'rgba(255, 255, 255, 0.03)'; this.ctx.lineWidth = 1; for (let i = 0; i <= this.tileCount; i++) { this.ctx.beginPath(); this.ctx.moveTo(i * this.gridSize, 0); this.ctx.lineTo(i * this.gridSize, this.canvas.height); this.ctx.stroke(); this.ctx.beginPath(); this.ctx.moveTo(0, i * this.gridSize); this.ctx.lineTo(this.canvas.width, i * this.gridSize); this.ctx.stroke(); } // 画食物 const foodX = this.food.x * this.gridSize + this.gridSize / 2; const foodY = this.food.y * this.gridSize + this.gridSize / 2; const foodRadius = this.gridSize / 2 - 2; const gradient = this.ctx.createRadialGradient(foodX, foodY, 2, foodX, foodY, foodRadius); gradient.addColorStop(0, '#ff7675'); gradient.addColorStop(1, '#d63031'); this.ctx.fillStyle = gradient; this.ctx.shadowBlur = 15; this.ctx.shadowColor = '#d63031'; this.ctx.beginPath(); this.ctx.arc(foodX, foodY, foodRadius, 0, Math.PI * 2); this.ctx.fill(); this.ctx.shadowBlur = 0; // 画蛇 this.snake.forEach((segment, index) => { const isHead = index === 0; const x = segment.x * this.gridSize; const y = segment.y * this.gridSize; const size = this.gridSize - 2; const radius = isHead ? 8 : 4; this.ctx.fillStyle = isHead ? '#55efc4' : '#00b894'; // 蛇头阴影 if (isHead) { this.ctx.shadowBlur = 10; this.ctx.shadowColor = '#55efc4'; } this.ctx.beginPath(); this.ctx.roundRect(x + 1, y + 1, size, size, radius); this.ctx.fill(); this.ctx.shadowBlur = 0; // 眼睛 if (isHead) { this.ctx.fillStyle = '#2d3436'; const eyeSize = 3; const offset = 5; // 根据方向画眼睛 if (this.dy === -1) { // Up this.ctx.fillRect(x + offset, y + offset, eyeSize, eyeSize); this.ctx.fillRect(x + size - offset - eyeSize, y + offset, eyeSize, eyeSize); } else if (this.dy === 1) { // Down this.ctx.fillRect(x + offset, y + size - offset - eyeSize, eyeSize, eyeSize); this.ctx.fillRect(x + size - offset - eyeSize, y + size - offset - eyeSize, eyeSize, eyeSize); } else if (this.dx === -1) { // Left this.ctx.fillRect(x + offset, y + offset, eyeSize, eyeSize); this.ctx.fillRect(x + offset, y + size - offset - eyeSize, eyeSize, eyeSize); } else { // Right this.ctx.fillRect(x + size - offset - eyeSize, y + offset, eyeSize, eyeSize); this.ctx.fillRect(x + size - offset - eyeSize, y + size - offset - eyeSize, eyeSize, eyeSize); } } }); if (this.gameOver) { this.ctx.fillStyle = 'rgba(0, 0, 0, 0.75)'; this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); this.ctx.shadowBlur = 20; this.ctx.shadowColor = 'rgba(255, 255, 255, 0.5)'; this.ctx.fillStyle = 'white'; this.ctx.textAlign = 'center'; this.ctx.font = 'bold 40px Segoe UI'; this.ctx.fillText('GAME OVER', this.canvas.width / 2, this.canvas.height / 2); this.ctx.font = '20px Segoe UI'; this.ctx.fillStyle = '#bdc3c7'; this.ctx.fillText('按空格重新开始', this.canvas.width / 2, this.canvas.height / 2 + 50); this.ctx.shadowBlur = 0; this.ctx.textAlign = 'start'; } } updateScore() { this.scoreElement.innerText = this.score; } endGame() { this.gameOver = true; } run() { this.update(); this.draw(); } } const game = new SnakeGame('snakeGame', 'score'); setInterval(() => game.run(), 100); window.addEventListener('keydown', (e) => { if (e.code === 'Space' && game.gameOver) { game.reset(); } });