当前位置: 首页 > news >正文

经典扫雷游戏实现:从零构建HTML5扫雷游戏

一、引言

        扫雷是一款经典的单人益智游戏,起源于20世纪60年代,并在90年代随着Windows操作系统的普及而风靡全球。本文将详细介绍如何使用现代网页技术(HTML、CSS和JavaScript)从零开始构建一个功能完整的扫雷游戏。我们将涵盖游戏逻辑设计、用户界面实现以及性能优化等方面。


二、游戏概述

扫雷游戏的核心规则很简单:

  1. 游戏在一个方格棋盘上进行,某些随机方格中隐藏着"地雷"
  2. 玩家需要揭开所有不含地雷的方格
  3. 揭开方格后会显示周围8个方格中的地雷数量
  4. 玩家可以标记他们认为有地雷的方格
  5. 如果揭开一个地雷,游戏立即结束


三、技术实现

HTML结构

游戏的基本HTML结构包括:

  • 游戏标题和难度选择按钮
  • 游戏信息显示区域(剩余地雷数、计时器、重置按钮)
  • 游戏棋盘
  • 游戏状态和统计信息显示
<div class="game-container"><header><h1><i class="fas fa-bomb"></i> 经典扫雷</h1><div class="game-controls"><div class="difficulty-selector"><button class="active" data-level="easy">简单</button><button data-level="medium">中等</button><button data-level="hard">困难</button></div><div class="game-info"><span class="flags"><i class="fas fa-flag"></i> <span id="flag-count">10</span></span><button id="reset-btn"><i class="fas fa-redo"></i></button><span class="timer"><i class="fas fa-clock"></i> <span id="time">0</span></span></div></div></header><div class="game-board" id="game-board"></div><div class="game-status"><div id="message"></div><div class="game-stats"><div>最佳时间: <span id="best-time">-</span>秒</div><div>当前胜率: <span id="win-rate">0%</span></div></div></div>
</div>

JavaScript游戏逻辑

游戏的核心逻辑包括:

  1. 游戏初始化
    • 创建棋盘数据结构
    • 随机放置地雷
    • 计算每个方格周围的地雷数量
function initGame() {// 初始化游戏状态boardData = [];// 创建游戏板数据for (let i = 0; i < boardSize * boardSize; i++) {boardData.push({isMine: false,isRevealed: false,isFlagged: false,neighborMines: 0});}// 放置地雷let minesPlaced = 0;while (minesPlaced < mineCount) {const randomIndex = Math.floor(Math.random() * boardSize * boardSize);if (!boardData[randomIndex].isMine) {boardData[randomIndex].isMine = true;minesPlaced++;}}// 计算每个格子周围的地雷数for (let i = 0; i < boardSize; i++) {for (let j = 0; j < boardSize; j++) {const index = i * boardSize + j;if (!boardData[index].isMine) {boardData[index].neighborMines = countAdjacentMines(i, j);}}}
}

  1. 游戏交互处理
    • 左键点击揭开方格
    • 右键点击标记/取消标记方格
    • 递归揭开空白区域
function handleCellClick(row, col) {if (gameOver) return;const index = row * boardSize + col;const cell = board.children[index];const cellData = boardData[index];if (cellData.isRevealed || cellData.isFlagged) return;if (cellData.isMine) {// 点到地雷,游戏结束gameOver = true;revealAllMines();messageElement.textContent = '游戏结束!';return;}revealCell(row, col);// 检查是否获胜if (revealedCount === boardSize * boardSize - mineCount) {gameOver = true;messageElement.textContent = '恭喜你赢了!';}
}
  1. 游戏状态管理
    • 计时器
    • 胜负判断
    • 游戏统计信息
function startTimer() {clearInterval(timerInterval);timerInterval = setInterval(() => {timer++;timerElement.textContent = timer;}, 1000);
}function updateStats() {bestTimeElement.textContent = bestTime === Infinity ? '-' : bestTime;winRateElement.textContent = gamesPlayed > 0 ? `${Math.round((gamesWon / gamesPlayed) * 100)}%` : '0%';
}

CSS样式设计

游戏的视觉设计采用现代、简洁的风格,使用CSS Grid布局实现响应式棋盘:

.game-board {display: grid;grid-template-columns: repeat(10, 1fr);gap: 3px;background: #bdc3c7;padding: 5px;border-radius: 5px;
}.cell {aspect-ratio: 1/1;display: flex;justify-content: center;align-items: center;background: #ecf0f1;border-radius: 3px;cursor: pointer;
}.cell.revealed {background: #d5dbdb;
}.cell.flagged {background: #f9e79f;
}.cell.mine {background: #e74c3c;color: white;
}

四、功能亮点

  1. 多种难度级别‌:提供简单、中等和困难三种难度选择
  2. 游戏统计‌:记录最佳时间和胜率
  3. 响应式设计‌:适配不同屏幕尺寸
  4. 本地存储‌:使用localStorage保存最佳成绩
  5. 视觉反馈‌:不同的数字使用不同颜色,提高可读性


五、技术细节解析

地雷生成算法

游戏使用Fisher-Yates洗牌算法的简化版本来随机放置地雷:

let minesPlaced = 0;
while (minesPlaced < mineCount) {const randomIndex = Math.floor(Math.random() * boardSize * boardSize);if (!boardData[randomIndex].isMine) {boardData[randomIndex].isMine = true;minesPlaced++;}
}

递归揭开空白区域

当玩家点击一个周围没有地雷的方格时,游戏会自动递归揭开所有相邻的空白方格:

function revealCell(row, col) {// ...if (cellData.neighborMines > 0) {cell.textContent = cellData.neighborMines;cell.style.color = getNumberColor(cellData.neighborMines);} else {// 递归揭示周围的格子for (let i = Math.max(0, row - 1); i <= Math.min(boardSize - 1, row + 1); i++) {for (let j = Math.max(0, col - 1); j <= Math.min(boardSize - 1, col + 1); j++) {if (i === row && j === col) continue;revealCell(i, j);}}}
}

数字颜色编码

为了提升游戏体验,不同数字使用不同颜色表示:

function getNumberColor(num) {const colors = ['',       // 0'#1976D2', // 1'#388E3C', // 2'#D32F2F', // 3'#7B1FA2', // 4'#FF8F00', // 5'#0097A7', // 6'#5D4037', // 7'#616161'  // 8];return colors[num];
}

六、性能优化

  1. 事件委托‌:使用事件委托减少事件监听器数量
  2. CSS硬件加速‌:使用transform属性实现平滑动画
  3. 最小化重绘‌:只在必要时更新DOM
  4. 内存管理‌:合理使用数据结构减少内存占用

七、扩展功能建议

  1. 添加音效和动画效果
  2. 实现多人对战模式
  3. 添加成就系统
  4. 支持自定义棋盘大小和地雷数量
  5. 添加教程和新手引导

八、总结

通过本文的介绍,我们完整实现了一个功能丰富的扫雷游戏。这个项目涵盖了现代Web开发的多个重要方面,包括:

  • DOM操作和事件处理
  • 游戏状态管理
  • 递归算法应用
  • 响应式设计
  • 本地存储使用

这个扫雷游戏不仅具有娱乐性,同时也是学习JavaScript和前端开发的优秀示例项目。读者可以在此基础上进一步扩展功能,或者优化现有实现。

完整代码:

<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>经典扫雷</title><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
</head><body><div class="game-container"><header><h1><i class="fas fa-bomb"></i> 经典扫雷</h1><div class="game-controls"><div class="difficulty-selector"><button class="active" data-level="easy">简单</button><button data-level="medium">中等</button><button data-level="hard">困难</button></div><div class="game-info"><span class="flags"><i class="fas fa-flag"></i> <span id="flag-count">10</span></span><button id="reset-btn"><i class="fas fa-redo"></i></button><span class="timer"><i class="fas fa-clock"></i> <span id="time">0</span></span></div></div></header><div class="game-board" id="game-board"></div><div class="game-status"><div id="message"></div><div class="game-stats"><div>最佳时间: <span id="best-time">-</span>秒</div><div>当前胜率: <span id="win-rate">0%</span></div></div></div></div><script>document.addEventListener('DOMContentLoaded', () => {const board = document.getElementById('game-board');const flagCountElement = document.getElementById('flag-count');const timerElement = document.getElementById('time');const messageElement = document.getElementById('message');const resetButton = document.getElementById('reset-btn');const difficultyButtons = document.querySelectorAll('.difficulty-selector button');const bestTimeElement = document.getElementById('best-time');const winRateElement = document.getElementById('win-rate');let boardSize = 10;let mineCount = 10;let boardData = [];let revealedCount = 0;let flagCount = 0;let gameOver = false;let timer = 0;let timerInterval = null;let gamesPlayed = 0;let gamesWon = 0;let bestTime = localStorage.getItem('minesweeperBestTime') || Infinity;// 初始化游戏function initGame() {clearInterval(timerInterval);timer = 0;timerElement.textContent = timer;revealedCount = 0;flagCount = 0;flagCountElement.textContent = mineCount;gameOver = false;messageElement.textContent = '';board.innerHTML = '';boardData = [];// 创建游戏板数据for (let i = 0; i < boardSize * boardSize; i++) {boardData.push({isMine: false,isRevealed: false,isFlagged: false,neighborMines: 0});}// 放置地雷let minesPlaced = 0;while (minesPlaced < mineCount) {const randomIndex = Math.floor(Math.random() * boardSize * boardSize);if (!boardData[randomIndex].isMine) {boardData[randomIndex].isMine = true;minesPlaced++;}}// 计算每个格子周围的地雷数for (let i = 0; i < boardSize; i++) {for (let j = 0; j < boardSize; j++) {const index = i * boardSize + j;if (!boardData[index].isMine) {boardData[index].neighborMines = countAdjacentMines(i, j);}}}// 创建游戏板UIfor (let i = 0; i < boardSize; i++) {for (let j = 0; j < boardSize; j++) {const index = i * boardSize + j;const cell = document.createElement('div');cell.className = 'cell';cell.dataset.row = i;cell.dataset.col = j;cell.addEventListener('click', () => handleCellClick(i, j));cell.addEventListener('contextmenu', (e) => {e.preventDefault();handleRightClick(i, j);});board.appendChild(cell);}}// 调整游戏板大小board.style.gridTemplateColumns = `repeat(${boardSize}, 1fr)`;}// 计算相邻地雷数function countAdjacentMines(row, col) {let count = 0;for (let i = Math.max(0, row - 1); i <= Math.min(boardSize - 1, row + 1); i++) {for (let j = Math.max(0, col - 1); j <= Math.min(boardSize - 1, col + 1); j++) {if (i === row && j === col) continue;const index = i * boardSize + j;if (boardData[index].isMine) count++;}}return count;}// 处理格子点击function handleCellClick(row, col) {if (gameOver) return;const index = row * boardSize + col;const cell = board.children[index];const cellData = boardData[index];// 开始游戏时启动计时器if (revealedCount === 0 && !cellData.isFlagged) {startTimer();}if (cellData.isRevealed || cellData.isFlagged) return;if (cellData.isMine) {// 点到地雷,游戏结束gameOver = true;revealAllMines();cell.classList.add('mine');messageElement.textContent = '游戏结束!';clearInterval(timerInterval);gamesPlayed++;updateStats();return;}revealCell(row, col);// 检查是否获胜if (revealedCount === boardSize * boardSize - mineCount) {gameOver = true;messageElement.textContent = '恭喜你赢了!';clearInterval(timerInterval);gamesWon++;if (timer < bestTime) {bestTime = timer;localStorage.setItem('minesweeperBestTime', bestTime);bestTimeElement.textContent = bestTime;}updateStats();}}// 处理右键点击(插旗)function handleRightClick(row, col) {if (gameOver) return;const index = row * boardSize + col;const cell = board.children[index];const cellData = boardData[index];if (cellData.isRevealed) return;if (cellData.isFlagged) {// 取消旗子cellData.isFlagged = false;cell.classList.remove('flagged');flagCount--;} else {// 插旗cellData.isFlagged = true;cell.classList.add('flagged');flagCount++;}flagCountElement.textContent = mineCount - flagCount;}// 揭示格子function revealCell(row, col) {const index = row * boardSize + col;const cell = board.children[index];const cellData = boardData[index];if (cellData.isRevealed || cellData.isFlagged) return;cellData.isRevealed = true;cell.classList.add('revealed');revealedCount++;if (cellData.neighborMines > 0) {cell.textContent = cellData.neighborMines;cell.style.color = getNumberColor(cellData.neighborMines);} else {// 如果是空白格子,递归揭示周围的格子for (let i = Math.max(0, row - 1); i <= Math.min(boardSize - 1, row + 1); i++) {for (let j = Math.max(0, col - 1); j <= Math.min(boardSize - 1, col + 1); j++) {if (i === row && j === col) continue;revealCell(i, j);}}}}// 获取数字颜色function getNumberColor(num) {const colors = ['',       // 0'#1976D2', // 1'#388E3C', // 2'#D32F2F', // 3'#7B1FA2', // 4'#FF8F00', // 5'#0097A7', // 6'#5D4037', // 7'#616161'  // 8];return colors[num];}// 揭示所有地雷function revealAllMines() {for (let i = 0; i < boardSize; i++) {for (let j = 0; j < boardSize; j++) {const index = i * boardSize + j;if (boardData[index].isMine) {const cell = board.children[index];cell.classList.add('mine');cell.innerHTML = '<i class="fas fa-bomb"></i>';}}}}// 开始计时器function startTimer() {clearInterval(timerInterval);timerInterval = setInterval(() => {timer++;timerElement.textContent = timer;}, 1000);}// 更新统计信息function updateStats() {bestTimeElement.textContent = bestTime === Infinity ? '-' : bestTime;winRateElement.textContent = gamesPlayed > 0 ? `${Math.round((gamesWon / gamesPlayed) * 100)}%` : '0%';}// 设置难度级别function setDifficulty(level) {difficultyButtons.forEach(btn => btn.classList.remove('active'));event.target.classList.add('active');switch (level) {case 'easy':boardSize = 10;mineCount = 10;break;case 'medium':boardSize = 16;mineCount = 40;break;case 'hard':boardSize = 20;mineCount = 80;break;}initGame();}// 事件监听resetButton.addEventListener('click', initGame);difficultyButtons.forEach(btn => {btn.addEventListener('click', () => setDifficulty(btn.dataset.level));});// 初始化游戏initGame();updateStats();});</script>
</body>
<style>body {font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);margin: 0;padding: 20px;min-height: 100vh;display: flex;justify-content: center;align-items: center;}.game-container {background: white;border-radius: 15px;box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);padding: 25px;width: 100%;max-width: 800px;}header h1 {color: #2c3e50;text-align: center;margin-bottom: 20px;}.game-controls {display: flex;justify-content: space-between;align-items: center;margin-bottom: 20px;flex-wrap: wrap;gap: 15px;}.difficulty-selector button {padding: 8px 15px;border: none;border-radius: 5px;background: #ecf0f1;cursor: pointer;transition: all 0.3s;}.difficulty-selector button.active {background: #3498db;color: white;}.game-info {display: flex;align-items: center;gap: 15px;}#reset-btn {background: #e74c3c;color: white;border: none;width: 40px;height: 40px;border-radius: 50%;cursor: pointer;transition: all 0.3s;}#reset-btn:hover {transform: rotate(360deg);}.flags,.timer {font-weight: bold;color: #2c3e50;}.game-board {display: grid;grid-template-columns: repeat(10, 1fr);gap: 3px;margin: 0 auto;background: #bdc3c7;padding: 5px;border-radius: 5px;}.cell {aspect-ratio: 1/1;display: flex;justify-content: center;align-items: center;background: #ecf0f1;border-radius: 3px;cursor: pointer;font-weight: bold;user-select: none;transition: all 0.2s;}.cell:hover {background: #d6eaf8;}.cell.revealed {background: #d5dbdb;}.cell.flagged {background: #f9e79f;}.cell.mine {background: #e74c3c;color: white;}.game-status {margin-top: 20px;text-align: center;}#message {font-size: 1.2em;font-weight: bold;min-height: 24px;margin-bottom: 10px;}.game-stats {display: flex;justify-content: center;gap: 20px;color: #7f8c8d;}@media (max-width: 600px) {.game-board {grid-template-columns: repeat(8, 1fr);}}
</style></html>

九、彩蛋

藏在星桥鹊语中的相思

http://www.dtcms.com/a/356997.html

相关文章:

  • 科技大会用了煽情BGM
  • 【技术分享】系统崩溃后产生的CHK文件如何恢复?完整图文教程(附工具推荐)
  • 论文阅读:GOAT: GO to Any Thing
  • 智慧工地系统:基于Java微服务与信创国产化的建筑施工数字化管理平台
  • 开关电源设计“反馈回路”部分器件分析
  • Nginx的主要配置文件nginx.conf详细解读——及其不间断重启nginx服务等操作
  • 从Cloudflare到EdgeOne:我的个人站点加速之旅与性能对比实测
  • LeetCode Hot 100 Python (11~20)
  • 微服务入门指南(一):从单体架构到服务注册发现
  • 将自己的jar包发布到maven中央仓库(2025-08-29)
  • 【Web安全】文件上传下载安全测试的全面剖析与实践指南
  • 如何在实际应用中选择Blaze或Apache Gluten?
  • 深入解析PCIe 6.0拓扑架构:从根复合体到端点的完整连接体系
  • 【国内电子数据取证厂商龙信科技】ES 数据库重建
  • shell命令扩展
  • Qt类-扩充_xiaozuo
  • ArcGIS Pro中 Nodata和nan 黑边的处理
  • 沃尔玛AI系统Wally深度拆解:零售业库存周转提速18%,动态定价争议与员工转型成热议点
  • 【lua】Lua 入门教程:从环境搭建到基础编程
  • Java深拷贝与浅拷贝核心解析
  • C primer plus (第六版)第十一章 编程练习第1,2,3,4题
  • typescript postgres@3.x jsonb数据插入绕过类型检测错误问题
  • Linux驱动学习-spi接口
  • 手写一个Spring框架
  • 【树论】树上启发式合并
  • ansible的playbook练习题
  • 短剧小程序系统开发:助力影视行业数字化转型
  • 算法---字符串
  • Speculation Rules API
  • PDF转图片工具实现