diff --git a/README.md b/README.md index 563e595..ea9ef0c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,98 @@ -# snake-game +# 🐍 贪吃蛇游戏 -网页版贪吃蛇游戏 - 完整开发流程演示项目 \ No newline at end of file +一个经典的网页版贪吃蛇游戏,使用纯 HTML/CSS/JavaScript 实现。 + +## ✨ 特性 + +- 🎮 经典贪吃蛇玩法 +- 🎯 三种难度级别(简单/中等/困难) +- ⏸️ 支持暂停和重置 +- 📊 实时分数统计 +- 🎨 精美的渐变 UI 设计 +- ⌨️ 键盘方向键控制 +- 📱 响应式设计 + +## 🎮 游戏玩法 + +1. 点击"开始游戏"按钮开始 +2. 使用方向键 ↑ ↓ ← → 控制蛇的移动 +3. 吃到食物(红色圆点)获得 10 分 +4. 避免撞墙或撞到自己的身体 +5. 随时可以暂停或重置游戏 + +## 🚀 快速开始 + +### 方式 1:直接打开 + +直接用浏览器打开 `index.html` 文件即可开始游戏。 + +### 方式 2:本地服务器 + +```bash +# 使用 Python +python3 -m http.server 8000 + +# 使用 Node.js (需要安装 http-server) +npx http-server + +# 然后访问 http://localhost:8000 +``` + +### 方式 3:部署到 Web 服务器 + +将所有文件部署到任何 Web 服务器(Nginx、Apache 等)的根目录即可。 + +## 📦 项目结构 + +``` +snake-game/ +├── index.html # 主页面 +├── style.css # 样式文件 +├── game.js # 游戏逻辑 +├── README.md # 项目说明 +└── LICENSE # MIT 许可证 +``` + +## 🛠️ 技术栈 + +- **HTML5** - 页面结构 +- **CSS3** - 样式和动画 +- **JavaScript (ES6+)** - 游戏逻辑 +- **Canvas API** - 游戏渲染 + +## 🎯 游戏配置 + +可以在 `game.js` 中修改 `CONFIG` 对象来自定义游戏参数: + +```javascript +const CONFIG = { + gridSize: 20, // 网格大小 + canvasSize: 400, // 画布大小 + easySpeed: 150, // 简单模式速度 + mediumSpeed: 100, // 中等模式速度 + hardSpeed: 50 // 困难模式速度 +}; +``` + +## 📝 开发路线图 + +本项目通过分阶段开发完成,展示完整的软件开发流程: + +- ✅ [阶段1:项目初始化](https://github.com/user/repo/issues/1) +- ✅ [阶段2:游戏核心逻辑](https://github.com/user/repo/issues/2) +- ✅ [阶段3:游戏 UI 界面](https://github.com/user/repo/issues/3) +- ✅ [阶段4:交互和优化](https://github.com/user/repo/issues/4) +- 🚧 [阶段5:CI/CD 配置](https://github.com/user/repo/issues/5) +- 🚧 [阶段6:部署上线](https://github.com/user/repo/issues/6) + +## 📄 许可证 + +本项目采用 MIT 许可证。详见 [LICENSE](LICENSE) 文件。 + +## 🤝 贡献 + +欢迎提交 Issue 和 Pull Request! + +--- + +**游戏愉快!🎮** diff --git a/game.js b/game.js new file mode 100644 index 0000000..dc15500 --- /dev/null +++ b/game.js @@ -0,0 +1,278 @@ +// 游戏配置 +const CONFIG = { + gridSize: 20, // 网格大小 + canvasSize: 400, // 画布大小 + easySpeed: 150, // 简单模式速度 + mediumSpeed: 100, // 中等模式速度 + hardSpeed: 50 // 困难模式速度 +}; + +// 游戏状态 +let snake = []; +let food = {}; +let direction = 'right'; +let nextDirection = 'right'; +let score = 0; +let gameLoop = null; +let isPaused = false; +let isGameRunning = false; + +// DOM 元素 +const canvas = document.getElementById('gameCanvas'); +const ctx = canvas.getContext('2d'); +const scoreElement = document.getElementById('score'); +const startBtn = document.getElementById('startBtn'); +const pauseBtn = document.getElementById('pauseBtn'); +const resetBtn = document.getElementById('resetBtn'); +const difficultySelect = document.getElementById('difficulty'); + +// 初始化游戏 +function initGame() { + snake = [ + { x: 5, y: 10 }, + { x: 4, y: 10 }, + { x: 3, y: 10 } + ]; + direction = 'right'; + nextDirection = 'right'; + score = 0; + isPaused = false; + isGameRunning = false; + updateScore(); + spawnFood(); + draw(); +} + +// 生成食物 +function spawnFood() { + const maxPos = CONFIG.canvasSize / CONFIG.gridSize; + let newFood; + + do { + newFood = { + x: Math.floor(Math.random() * maxPos), + y: Math.floor(Math.random() * maxPos) + }; + } while (snake.some(segment => segment.x === newFood.x && segment.y === newFood.y)); + + food = newFood; +} + +// 更新游戏状态 +function update() { + if (isPaused) return; + + direction = nextDirection; + + // 计算蛇头新位置 + const head = { ...snake[0] }; + + switch (direction) { + case 'up': + head.y -= 1; + break; + case 'down': + head.y += 1; + break; + case 'left': + head.x -= 1; + break; + case 'right': + head.x += 1; + break; + } + + // 检查碰撞 + if (checkCollision(head)) { + gameOver(); + return; + } + + // 添加蛇头 + snake.unshift(head); + + // 检查是否吃到食物 + if (head.x === food.x && head.y === food.y) { + score += 10; + updateScore(); + spawnFood(); + } else { + snake.pop(); + } + + draw(); +} + +// 检查碰撞 +function checkCollision(head) { + const maxPos = CONFIG.canvasSize / CONFIG.gridSize; + + // 撞墙检测 + if (head.x < 0 || head.x >= maxPos || head.y < 0 || head.y >= maxPos) { + return true; + } + + // 撞自己检测 + return snake.some(segment => segment.x === head.x && segment.y === head.y); +} + +// 绘制游戏 +function draw() { + // 清空画布 + ctx.fillStyle = '#2c3e50'; + ctx.fillRect(0, 0, CONFIG.canvasSize, CONFIG.canvasSize); + + // 绘制蛇 + snake.forEach((segment, index) => { + const gradient = ctx.createRadialGradient( + segment.x * CONFIG.gridSize + CONFIG.gridSize / 2, + segment.y * CONFIG.gridSize + CONFIG.gridSize / 2, + 0, + segment.x * CONFIG.gridSize + CONFIG.gridSize / 2, + segment.y * CONFIG.gridSize + CONFIG.gridSize / 2, + CONFIG.gridSize + ); + + if (index === 0) { + gradient.addColorStop(0, '#2ecc71'); + gradient.addColorStop(1, '#27ae60'); + } else { + gradient.addColorStop(0, '#82e0aa'); + gradient.addColorStop(1, '#58d68d'); + } + + ctx.fillStyle = gradient; + ctx.fillRect( + segment.x * CONFIG.gridSize + 1, + segment.y * CONFIG.gridSize + 1, + CONFIG.gridSize - 2, + CONFIG.gridSize - 2 + ); + }); + + // 绘制食物 + const foodGradient = ctx.createRadialGradient( + food.x * CONFIG.gridSize + CONFIG.gridSize / 2, + food.y * CONFIG.gridSize + CONFIG.gridSize / 2, + 0, + food.x * CONFIG.gridSize + CONFIG.gridSize / 2, + food.y * CONFIG.gridSize + CONFIG.gridSize / 2, + CONFIG.gridSize + ); + foodGradient.addColorStop(0, '#e74c3c'); + foodGradient.addColorStop(1, '#c0392b'); + + ctx.fillStyle = foodGradient; + ctx.beginPath(); + ctx.arc( + food.x * CONFIG.gridSize + CONFIG.gridSize / 2, + food.y * CONFIG.gridSize + CONFIG.gridSize / 2, + CONFIG.gridSize / 2 - 2, + 0, + Math.PI * 2 + ); + ctx.fill(); +} + +// 更新分数 +function updateScore() { + scoreElement.textContent = score; +} + +// 游戏结束 +function gameOver() { + stopGame(); + alert(`游戏结束!得分:${score}\n点击"重置"重新开始`); +} + +// 开始游戏 +function startGame() { + if (isGameRunning) return; + + initGame(); + isGameRunning = true; + startBtn.disabled = true; + pauseBtn.disabled = false; + difficultySelect.disabled = true; + + const speed = getGameSpeed(); + gameLoop = setInterval(update, speed); +} + +// 停止游戏 +function stopGame() { + if (gameLoop) { + clearInterval(gameLoop); + gameLoop = null; + } + isGameRunning = false; + startBtn.disabled = false; + pauseBtn.disabled = true; + pauseBtn.textContent = '暂停'; + difficultySelect.disabled = false; +} + +// 暂停/继续游戏 +function togglePause() { + if (!isGameRunning) return; + + isPaused = !isPaused; + + if (isPaused) { + pauseBtn.textContent = '继续'; + } else { + pauseBtn.textContent = '暂停'; + } +} + +// 重置游戏 +function resetGame() { + stopGame(); + initGame(); +} + +// 获取游戏速度 +function getGameSpeed() { + const difficulty = difficultySelect.value; + + switch (difficulty) { + case 'easy': + return CONFIG.easySpeed; + case 'medium': + return CONFIG.mediumSpeed; + case 'hard': + return CONFIG.hardSpeed; + default: + return CONFIG.mediumSpeed; + } +} + +// 键盘控制 +document.addEventListener('keydown', (e) => { + const key = e.key; + + if (key === 'ArrowUp' && direction !== 'down') { + nextDirection = 'up'; + e.preventDefault(); + } else if (key === 'ArrowDown' && direction !== 'up') { + nextDirection = 'down'; + e.preventDefault(); + } else if (key === 'ArrowLeft' && direction !== 'right') { + nextDirection = 'left'; + e.preventDefault(); + } else if (key === 'ArrowRight' && direction !== 'left') { + nextDirection = 'right'; + e.preventDefault(); + } else if (key === ' ' && isGameRunning) { + togglePause(); + e.preventDefault(); + } +}); + +// 按钮事件 +startBtn.addEventListener('click', startGame); +pauseBtn.addEventListener('click', togglePause); +resetBtn.addEventListener('click', resetGame); + +// 初始化 +initGame(); diff --git a/index.html b/index.html new file mode 100644 index 0000000..bedc912 --- /dev/null +++ b/index.html @@ -0,0 +1,50 @@ + + + + + + 贪吃蛇游戏 + + + +
+
+

🐍 贪吃蛇游戏

+
+ 得分: 0 +
+
+ +
+
+ + + +
+ +
+ + +
+ + + +
+

游戏说明

+
    +
  • 使用方向键 ↑ ↓ ← → 控制蛇的移动
  • +
  • 吃到食物得分,蛇身变长
  • +
  • 撞墙或撞到自己,游戏结束
  • +
  • 随时可以暂停或重置游戏
  • +
+
+
+
+ + + + diff --git a/style.css b/style.css new file mode 100644 index 0000000..d1d1095 --- /dev/null +++ b/style.css @@ -0,0 +1,177 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + min-height: 100vh; + display: flex; + justify-content: center; + align-items: center; + padding: 20px; +} + +.container { + background: rgba(255, 255, 255, 0.95); + border-radius: 20px; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2); + padding: 30px; + max-width: 600px; + width: 100%; +} + +header { + text-align: center; + margin-bottom: 20px; +} + +header h1 { + color: #333; + font-size: 2.5em; + margin-bottom: 10px; +} + +.score-board { + font-size: 1.5em; + color: #764ba2; + font-weight: bold; +} + +#score { + color: #e74c3c; +} + +main { + text-align: center; +} + +.game-controls { + margin-bottom: 20px; + display: flex; + justify-content: center; + gap: 10px; +} + +.btn { + padding: 10px 20px; + font-size: 16px; + border: none; + border-radius: 8px; + cursor: pointer; + transition: all 0.3s ease; + font-weight: bold; +} + +#startBtn { + background: #27ae60; + color: white; +} + +#startBtn:hover { + background: #229954; +} + +#pauseBtn { + background: #f39c12; + color: white; +} + +#pauseBtn:hover { + background: #e67e22; +} + +#pauseBtn:disabled { + background: #ccc; + cursor: not-allowed; +} + +#resetBtn { + background: #e74c3c; + color: white; +} + +#resetBtn:hover { + background: #c0392b; +} + +.difficulty-selector { + margin-bottom: 20px; + font-size: 1.1em; + color: #333; +} + +#difficulty { + padding: 5px 10px; + font-size: 16px; + border: 2px solid #764ba2; + border-radius: 5px; + background: white; + cursor: pointer; +} + +#gameCanvas { + background: #2c3e50; + border: 4px solid #34495e; + border-radius: 10px; + display: block; + margin: 0 auto 20px; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3); +} + +.game-info { + text-align: left; + background: #f8f9fa; + padding: 15px; + border-radius: 8px; + border-left: 4px solid #764ba2; +} + +.game-info h3 { + color: #333; + margin-bottom: 10px; +} + +.game-info ul { + list-style: none; + padding-left: 0; +} + +.game-info li { + padding: 5px 0; + color: #555; + position: relative; + padding-left: 20px; +} + +.game-info li::before { + content: "🎮"; + position: absolute; + left: 0; +} + +@media (max-width: 600px) { + .container { + padding: 20px; + } + + header h1 { + font-size: 1.8em; + } + + #gameCanvas { + width: 100%; + height: auto; + } + + .game-controls { + flex-wrap: wrap; + } + + .btn { + flex: 1; + min-width: 80px; + } +}