```tsx import React, { useState, useEffect, useCallback, useRef } from 'react'; // 游戏常量 const GRID_SIZE = 20; const CELL_SIZE = 20; const INITIAL_SPEED = 150; const MIN_SPEED = 50; const SPEED_STEP = 10; const INITIAL_SNAKE: [number, number][] = [[10, 10]]; const INITIAL_FOOD: [number, number] = [5, 5]; // 类型定义 type Direction = 'UP' | 'DOWN' | 'LEFT' | 'RIGHT'; type Position = [number, number]; type GameStatus = 'IDLE' | 'RUNNING' | 'PAUSED' | 'GAME_OVER'; interface GameState { snake: Position[]; food: Position; direction: Direction; nextDirection: Direction; score: number; highScore: number; speed: number; status: GameStatus; } const SnakeGame: React.FC = () => { // 游戏状态 const [gameState, setGameState] = useState({ snake: INITIAL_SNAKE, food: INITIAL_FOOD, direction: 'RIGHT', nextDirection: 'RIGHT', score: 0, highScore: parseInt(localStorage.getItem('snakeHighScore') || '0', 10), speed: INITIAL_SPEED, status: 'IDLE', }); const [showInstructions, setShowInstructions] = useState(true); const gameLoopRef = useRef(null); const directionRef = useRef(gameState.direction); const nextDirectionRef = useRef(gameState.nextDirection); // 生成随机食物位置(避开蛇身) const generateFood = useCallback((): Position => { const newFood: Position = [ Math.floor(Math.random() * GRID_SIZE), Math.floor(Math.random() * GRID_SIZE), ]; // 检查是否与蛇重叠 const isOnSnake = gameState.snake.some( ([x, y]) => x === newFood[0] && y === newFood[1] ); return isOnSnake ? generateFood() : newFood; }, [gameState.snake]); // 重置游戏 const resetGame = useCallback(() => { setGameState(prev => ({ ...prev, snake: INITIAL_SNAKE, food: INITIAL_FOOD, direction: 'RIGHT', nextDirection: 'RIGHT', score: 0, status: 'IDLE', })); directionRef.current = 'RIGHT'; nextDirectionRef.current = 'RIGHT'; }, []); // 开始游戏 const startGame = useCallback(() => { if (gameState.status === 'GAME_OVER') { resetGame(); } setGameState(prev => ({ ...prev, status: 'RUNNING', direction: 'RIGHT', nextDirection: 'RIGHT', })); directionRef.current = 'RIGHT'; nextDirectionRef.current = 'RIGHT'; }, [gameState.status, resetGame]); // 切换暂停 const togglePause = useCallback(() => { if (gameState.status === 'RUNNING') { setGameState(prev => ({ ...prev, status: 'PAUSED' })); } else if (gameState.status === 'PAUSED') { setGameState(prev => ({ ...prev, status: 'RUNNING' })); } }, [gameState.status]); // 键盘控制 useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (gameState.status !== 'RUNNING') return; switch (e.key) { case 'ArrowUp': if (directionRef.current !== 'DOWN') { nextDirectionRef.current = 'UP'; } break; case 'ArrowDown': if (directionRef.current !== 'UP') { nextDirectionRef.current = 'DOWN'; } break; case 'ArrowLeft': if (directionRef.current !== 'RIGHT') { nextDirectionRef.current = 'LEFT'; } break; case 'ArrowRight': if (directionRef.current !== 'LEFT') { nextDirectionRef.current = 'RIGHT'; } break; case ' ': e.preventDefault(); togglePause(); break; } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [gameState.status, togglePause]); // 游戏主循环 useEffect(() => { if (gameState.status !== 'RUNNING') return; const moveSnake = () => { setGameState(prev => { const { snake, food, score, highScore, speed } = prev; const head = [...snake[0]] as Position; directionRef.current = nextDirectionRef.current; // 移动头部 switch (nextDirectionRef.current) { case 'UP': head[1] -= 1; break; case 'DOWN': head[1] += 1; break; case 'LEFT': head[0] -= 1; break; case 'RIGHT': head[0] += 1; break; } // 检查碰撞边界 const hitWall = head[0] < 0 || head[0] >= GRID_SIZE || head[1] < 0 || head[1] >= GRID_SIZE; // 检查碰撞自身 const hitSelf = snake.slice(1).some(([x, y]) => x === head[0] && y === head[1]); if (hitWall || hitSelf) { // 更新最高分 const newHighScore = Math.max(score, highScore); localStorage.setItem('snakeHighScore', newHighScore.toString()); return { ...prev, status: 'GAME_OVER', highScore: newHighScore, }; } // 检查吃食物 const ateFood = head[0] === food[0] && head[1] === food[1]; const newSnake = [head, ...snake]; const newFood = ateFood ? generateFood() : food; const newScore = ateFood ? score + 10 : score; const newSpeed = ateFood && speed > MIN_SPEED ? Math.max(speed - SPEED_STEP, MIN_SPEED) : speed; return { ...prev, snake: ateFood ? newSnake : newSnake.slice(0, -1), food: newFood, score: newScore, speed: newSpeed, }; }); }; gameLoopRef.current = window.setTimeout(moveSnake, gameState.speed); return () => { if (gameLoopRef.current) { clearTimeout(gameLoopRef.current); } }; }, [gameState.status, gameState.speed, generateFood]); // 更新速度滑块 const handleSpeedChange = (e: React.ChangeEvent) => { const newSpeed = parseInt(e.target.value, 10); setGameState(prev => ({ ...prev, speed: newSpeed, })); }; // 渲染游戏网格 const renderGrid = () => { const cells = []; for (let y = 0; y < GRID_SIZE; y++) { for (let x = 0; x < GRID_SIZE; x++) { const isSnakeHead = gameState.snake[0][0] === x && gameState.snake[0][1] === y; const isSnakeBody = gameState.snake.slice(1).some(([sx, sy]) => sx === x && sy === y); const isFood = gameState.food[0] === x && gameState.food[1] === y; let cellClass = 'cell'; if (isSnakeHead) { cellClass += ' snake-head'; } else if (isSnakeBody) { cellClass += ' snake-body'; } else if (isFood) { cellClass += ' food'; } cells.push(
); } } return cells; }; // 渲染分数图表(模拟简单柱状图) const renderScoreChart = () => { const maxScore = Math.max(gameState.score, gameState.highScore, 50); const currentHeight = Math.max(20, (gameState.score / (maxScore || 1)) * 100); const highHeight = Math.max(20, (gameState.highScore / (maxScore || 1)) * 100); return (
当前分 最高分
{gameState.score} {gameState.highScore}
); }; return (

🐍 贪吃蛇游戏

七年级数学 · 逻辑与算法思维

🎮 游戏说明

  • 使用 方向键控制蛇的移动方向
  • 空格键 暂停/继续游戏
  • 吃到红色食物得 10 分,蛇会变长
  • 撞到墙壁或自己的身体游戏结束
  • 调整滑块改变游戏速度(数值越小越快)
当前得分
{gameState.score}
最高得分
{gameState.highScore}
{gameState.status === 'IDLE' && 准备就绪,点击“开始游戏”启动} {gameState.status === 'RUNNING' && 🎮 游戏进行中...} {gameState.status === 'PAUSED' && ⏸️ 游戏已暂停} {gameState.status === 'GAME_OVER' && 💥 游戏结束!点击“重新开始”继续}
游戏速度控制 (数值越小越快)
{gameState.speed}ms

📊 得分对比图

{renderScoreChart()}

🎮 游戏区域

{renderGrid()}

💡 数学小知识

  • 贪吃蛇体现了坐标系思想:每个位置用(x, y)表示
  • 蛇的移动是向量运算:方向+位置=新位置
  • 碰撞检测运用了逻辑判断集合关系
  • 游戏速度调节展示了变量与函数关系
); }; export default SnakeGame; ```