用原生JS+CSS3实现扫雷游戏:从零搭建完整项目(附源码下载)
用原生JSCSS3实现扫雷游戏从零搭建完整项目附源码下载1. 项目概述与核心设计思路扫雷作为Windows系统自带的经典游戏其背后的算法逻辑和交互设计对前端开发者而言是绝佳的练手项目。不同于直接使用现成框架我们从零开始用原生技术栈实现能深入理解以下核心概念游戏状态管理如何用二维数组表示雷区递归算法实现空白区域的自动展开响应式设计适配不同屏幕尺寸性能优化减少DOM操作频率先看最终实现的游戏界面效果div classgame-container div idminefield/div div classcontrols select iddifficulty option valueeasy初级9×9/option option valuemedium中级16×16/option option valuehard高级30×16/option /select button idreset重新开始/button div idtimer00:00/div /div /div2. 游戏数据结构设计2.1 单元格建模每个格子需要存储以下状态class Cell { constructor(x, y) { this.x x; // 横坐标 this.y y; // 纵坐标 this.isMine false; // 是否是雷 this.isRevealed false; // 是否已揭开 this.isFlagged false; // 是否被标记 this.neighborMines 0; // 周围雷数 } }2.2 雷区初始化生成指定尺寸的二维数组function createBoard(width, height, mineCount) { // 初始化空白棋盘 const board Array(height).fill() .map((_, y) Array(width).fill() .map((_, x) new Cell(x, y))); // 随机布置地雷 let minesPlaced 0; while (minesPlaced mineCount) { const x Math.floor(Math.random() * width); const y Math.floor(Math.random() * height); if (!board[y][x].isMine) { board[y][x].isMine true; minesPlaced; } } // 计算每个格子周围雷数 for (let y 0; y height; y) { for (let x 0; x width; x) { if (!board[y][x].isMine) { board[y][x].neighborMines countAdjacentMines(board, x, y); } } } return board; }3. 核心算法实现3.1 递归展开空白区域当点击到周围无雷的格子时自动展开相邻区域function revealCell(board, x, y) { // 边界检查 if (x 0 || x board[0].length || y 0 || y board.length) { return; } const cell board[y][x]; // 已揭开或已标记则跳过 if (cell.isRevealed || cell.isFlagged) { return; } cell.isRevealed true; // 如果是空白格递归展开周围 if (cell.neighborMines 0 !cell.isMine) { const directions [ [-1,-1], [-1,0], [-1,1], [0,-1], [0,1], [1,-1], [1,0], [1,1] ]; for (const [dx, dy] of directions) { revealCell(board, x dx, y dy); } } }3.2 胜负判定逻辑function checkGameStatus(board) { let allNonMinesRevealed true; let mineTriggered false; for (const row of board) { for (const cell of row) { if (cell.isMine cell.isRevealed) { mineTriggered true; } if (!cell.isMine !cell.isRevealed) { allNonMinesRevealed false; } } } if (mineTriggered) return lose; if (allNonMinesRevealed) return win; return playing; }4. 界面渲染优化4.1 CSS网格布局使用CSS Grid实现自适应雷区#minefield { display: grid; grid-template-columns: repeat(var(--cols), 30px); grid-template-rows: repeat(var(--rows), 30px); gap: 2px; background: #999; padding: 10px; border-radius: 5px; } .cell { width: 100%; height: 100%; background: #ccc; display: flex; justify-content: center; align-items: center; font-weight: bold; cursor: pointer; user-select: none; border-radius: 3px; transition: all 0.2s; } .cell.revealed { background: #fff; } .cell.mine { background: #f44336; }4.2 移动端适配技巧通过媒体查询调整格子大小media (max-width: 600px) { #minefield { grid-template-columns: repeat(var(--cols), 25px); grid-template-rows: repeat(var(--rows), 25px); } }添加触摸事件支持cellElement.addEventListener(touchstart, (e) { e.preventDefault(); // 区分左右键点击 if (e.touches.length 1) { handleLeftClick(x, y); } }, { passive: false });5. 完整项目结构与源码项目目录结构/minesweeper ├── index.html ├── css/ │ └── style.css ├── js/ │ ├── game.js # 核心逻辑 │ └── render.js # 界面渲染 └── assets/ # 图片音效关键实现代码片段// 游戏主循环 function gameLoop() { renderBoard(); const status checkGameStatus(); if (status ! playing) { clearInterval(timer); showGameOver(status); } } // 初始化游戏 function initGame(difficulty) { const settings { easy: { width: 9, height: 9, mines: 10 }, medium: { width: 16, height: 16, mines: 40 }, hard: { width: 30, height: 16, mines: 99 } }; const { width, height, mines } settings[difficulty]; board createBoard(width, height, mines); startTimer(); }完整项目源码可通过GitHub获取链接见文末。建议按照以下顺序阅读代码game.js- 核心游戏逻辑render.js- DOM操作与事件处理style.css- 视觉呈现6. 常见问题与调试技巧6.1 性能优化点优化方向具体措施效果提升DOM操作使用文档片段批量更新减少重绘次数事件处理采用事件委托机制降低内存占用计算优化预生成相邻格子索引加快递归速度6.2 调试技巧开发时可以先禁用随机布雷固定雷的位置方便测试// 测试用固定雷位 board[0][0].isMine true; board[5][5].isMine true;添加调试视图显示格子状态function debugRender() { console.log(board.map(row row.map(cell cell.isMine ? X : cell.neighborMines ).join( ) ).join(\n)); }7. 项目扩展方向难度自定义允许玩家输入任意棋盘尺寸和雷数主题切换实现暗黑模式/怀旧风格等皮肤排行榜功能使用localStorage存储最佳成绩动画效果添加格子翻转、爆炸特效多语言支持国际化界面文本实现主题切换的CSS变量方案:root { --cell-color: #ccc; --mine-color: #f44336; } [data-themedark] { --cell-color: #444; --mine-color: #d32f2f; } .cell { background: var(--cell-color); }8. 学习资源推荐MDN Canvas教程 - 进阶图形渲染JavaScript设计模式 - 优化代码结构CSS Grid布局指南 - 深入掌握布局技术在实现过程中最容易出错的是递归展开算法中的边界条件判断。建议在编写这部分代码时先用小棋盘如5×5进行测试逐步验证各种点击情况下的行为是否符合预期。