搭建扫雷平台是一个结合逻辑思维、前端开发与用户体验设计的实践项目,既能重温经典游戏的乐趣,又能锻炼编程能力,本文将从需求分析、技术选型、分步实现到优化扩展,详细讲解如何独立完成一个功能完善的扫雷平台,适合有一定编程基础(如HTML/CSS/JavaScript)的开发者或爱好者参考。

在开始编码前,明确扫雷平台的核心功能与用户体验需求是关键,经典扫雷的核心逻辑包括:网格布局、地雷随机分布、点击交互(左键揭开/右键标记)、胜负判定,而完善平台还需考虑以下细节:
扫雷平台属于前端交互密集型应用,推荐使用轻量级技术栈,兼顾开发效率与性能:
HTML结构:
使用<div class="game-container">作为容器,内部包含:难度选择区(<div class="difficulty">)、游戏状态栏(剩余雷数<span class="mine-count">、计时器<span class="timer">)、重新开始按钮<button class="restart">、游戏网格<div class="game-board">(动态生成格子)。
<div class="game-container">
<div class="difficulty">
<button data-level="beginner">初级</button>
<button data-level="intermediate">中级</button>
<button data-level="expert">高级</button>
<button data-level="custom">自定义</button>
</div>
<div class="status-bar">
<span class="mine-count">010</span>
<button class="restart">😊</button>
<span class="timer">000</span>
</div>
<div class="game-board"></div>
</div>
CSS样式:

display: grid,根据难度动态设置grid-template-columns(如初级9列,每列宽30px)。.number-1到.number-8设置不同颜色(如.number-1 { color: blue; })。.game-board {
display: grid;
gap: 1px;
background: #999;
padding: 1px;
margin: 0 auto;
}
.cell {
width: 30px;
height: 30px;
background: #c0c0c0;
border: 2px solid #999;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
font-weight: bold;
cursor: pointer;
user-select: none;
}
.cell:hover:not(.revealed):not(.flagged) {
background: #d0d0d0;
}
.cell.revealed {
background: #fff;
border: 1px solid #ccc;
cursor: default;
}
.cell.flagged::after {
content: "🚩";
}
.cell.question::after {
content: "?";
}
.cell.mine {
background: #ff0000;
}
.cell.number-1 { color: #0000ff; }
.cell.number-2 { color: #008000; }
/* 其他数字颜色类略 */
(1)游戏状态初始化
定义游戏配置对象,存储不同难度的行列数、地雷数,以及当前游戏状态(是否进行中、是否胜利、网格数据、计时器等)。
const config = {
beginner: { rows: 9, cols: 9, mines: 10 },
intermediate: { rows: 16, cols: 16, mines: 40 },
expert: { rows: 16, cols: 30, mines: 99 }
};
let gameState = {
board: [], // 二维数组,存储每个格子的数据(是否地雷、周围雷数、状态等)
isPlaying: false, // 游戏是否进行中
isGameOver: false, // 游戏是否结束
mineCount: 0, // 剩余地雷数
timer: 0, // 计时器(秒)
firstClick: true // 是否首次点击(用于确保首次点击不是地雷)
};
(2)生成网格与布置地雷
根据难度创建二维网格,每个格子对象包含isMine(是否地雷)、neighborMines(周围地雷数)、isRevealed(是否揭开)、isFlagged(是否标记)等属性,首次点击后再布置地雷,确保点击位置及其周围无雷。
function createBoard(rows, cols, firstClickRow, firstClickCol) {
const board = [];
for (let i = 0; i < rows; i++) {
board[i] = [];
for (let j = 0; j < cols; j++) {
board[i][j] = {
isMine: false,
neighborMines: 0,
isRevealed: false,
isFlagged: false
};
}
}
// 随机布置地雷(避开首次点击及其周围8格)
let minesPlaced = 0;
const totalMines = config[gameState.difficulty].mines;
while (minesPlaced < totalMines) {
const row = Math.floor(Math.random() * rows);
const col = Math.floor(Math.random() * cols);
// 检查是否在首次点击周围
const isNearFirstClick = Math.abs(row firstClickRow) <= 1 && Math.abs(col firstClickCol) <= 1;
if (!board[row][col].isMine && !isNearFirstClick) {
board[row][col].isMine = true;
minesPlaced++;
}
}
// 计算每个格子周围的地雷数
for (let i = 0; i < rows; i++) {
for (let j = 0; j < cols; j++) {
if (!board[i][j].isMine) {
board[i][j].neighborMines = countNeighborMines(board, i, j);
}
}
}
return board;
}
function countNeighborMines(board, row, col) {
let count = 0;
for (let i = -1; i <= 1; i++) {
for (let j = -1; j <= 1; j++) {
const newRow = row + i;
const newCol = col + j;
if (newRow >= 0 && newRow < board.length && newCol >= 0 && newCol < board[0].length) {
if (board[newRow][newCol].isMine) count++;
}
}
}
return count;
}
(3)点击事件处理
neighborMines=0),递归揭开相邻的空白格,直到遇到数字格;若踩雷,游戏结束,显示所有地雷。function handleCellClick(row, col, isRightClick) {
if (!gameState.isPlaying || gameState.isGameOver) return;
const cell = gameState.board[row][col];
if (isRightClick) {
// 右键标记
if (!cell.isRevealed) {
if (cell.isFlagged) {
cell.isFlagged = false;
cell.isQuestion = true;
gameState.mineCount++;
} else if (cell.isQuestion) {
cell.isQuestion = false;
} else {
cell.isFlagged = true;
gameState.mineCount--;
}
updateMineCount();
}
} else {
// 左键揭开
if (cell.isFlagged || cell.isRevealed) return;
if (gameState.firstClick) {
gameState.firstClick = false;
gameState.board = createBoard(gameState.rows, gameState.cols, row, col);
startTimer();
}
revealCell(row, col);
if (cell.isMine) {
gameOver(false);
} else {
checkWin();
}
}
}
function revealCell(row, col) {
const cell = gameState.board[row][col];
if (cell.isRevealed || cell.isFlagged) return;
cell.isRevealed = true;
updateCellDisplay(row, col);
if (cell.neighborMines === 0) {
// 递归揭开相邻空白格
for (let i = -1; i <= 1; i++) {
for (let j = -1; j <= 1; j++) {
const newRow = row + i;
const newCol = col + j;
if (newRow >= 0 && newRow < gameState.rows && newCol >= 0 && newCol < gameState.cols) {
revealCell(newRow, newCol);
}
}
}
}
}
(4)游戏状态管理

setInterval,首次点击后开始计时,每秒更新显示;游戏结束时停止。function startTimer() {
gameState.timerInterval = setInterval(() => {
gameState.timer++;
document.querySelector(".timer").textContent = String(gameState.timer).padStart(3, "0");
}, 1000);
}
function gameOver(isWin) {
gameState.isGameOver = true;
gameState.isPlaying = false;
clearInterval(gameState.timerInterval);
const restartBtn = document.querySelector(".restart");
restartBtn.textContent = isWin ? "😎" : "😵";
if (!isWin) {
// 显示所有地雷
for (let i = 0; i < gameState.rows; i++) {
for (let j = 0; j < gameState.cols; j++) {
if (gameState.board[i][j].isMine) {
gameState.board[i][j].isRevealed = true;
updateCellDisplay(i, j);
}
}
}
} else {
// 保存成绩
saveScore();
}
}
function checkWin() {
let revealedCount = 0;
for (let i = 0; i < gameState.rows; i++) {
for (let j = 0; j < gameState.cols; j++) {
if (gameState.board[i][j].isRevealed && !gameState.board[i][j].isMine) {
revealedCount++;
}
}
}
const totalNonMineCells = gameState.rows * gameState.cols config[gameState.difficulty].mines;
if (revealedCount === totalNonMineCells) {
gameOver(true);
}
}
(1)自定义难度
添加自定义难度弹窗,输入行数(5-30)、列数(5-30)、地雷数(≤行列数×80%),点击确定后初始化游戏。
// 自定义难度按钮点击事件
document.querySelector("[data-level='custom']").addEventListener("click", () => {
const rows = parseInt(prompt("请输入行数(5-30):", "9"));
const cols = parseInt(prompt("请输入列数(5-30):", "9"));
const mines = parseInt(prompt("请输入地雷数(≤" + rows * cols * 0.8 + "):", "10"));
if (rows >= 5 && rows <= 30 && cols >= 5 && cols <= 30 && mines > 0 && mines <= rows * cols * 0.8) {
startGame(rows, cols, mines, "custom");
}
});
(2)排行榜(LocalStorage)
定义saveScore和loadScores函数,将胜利时的难度、时间、日期存入LocalStorage,并在页面加载时读取显示。
function saveScore() {
const scores = JSON.parse(localStorage.getItem("minesweeper-scores") || "[]");
scores.push({
difficulty: gameState.difficulty,
time: gameState.timer,
date: new Date().toLocaleDateString()
});
// 按时间升序排序,保留前10名
scores.sort((a, b) => a.time b.time);
localStorage.setItem("minesweeper-scores", JSON.stringify(scores.slice(0, 10)));
}
function loadScores() {
const scores = JSON.parse(localStorage.getItem("minesweeper-scores") || "[]");
const scoresList = document.querySelector(".scores-list");
scoresList.innerHTML = scores.map(score =>
`<li>${score.difficulty}: ${score.time}秒 ${score.date}</li>`
).join("");
}
(3)键盘操作
监听键盘事件,实现方向键移动光标、空格揭开、F键标记。
document.addEventListener("keydown", (e) => {
if (!gameState.isPlaying) return;
const currentCell = document.querySelector(".cell.active");
if (!currentCell) return;
const row = parseInt(currentCell.dataset.row);
const col = parseInt(currentCell.dataset.col);
switch (e.key) {
case "ArrowUp":
moveActiveCursor(row 1, col);
break;
case "ArrowDown":
moveActiveCursor(row + 1, col);
break;
case "ArrowLeft":
moveActiveCursor(row, col 1);
break;
case "ArrowRight":
moveActiveCursor(row, col + 1);
break;
case " ":
e.preventDefault();
handleCellClick(row, col, false);
break;
case "f":
case "F":
handleCellClick(row, col, true);
break;
}
});
搭建扫雷平台的核心在于清晰的游戏逻辑设计(尤其是地雷布置与递归展开)与流畅的用户交互体验,从基础的HTML/CSS/JavaScript实现,到自定义难度、排行榜等扩展功能,每一步都能加深对前端开发的理解,通过不断测试与优化,可以打造一个既经典又现代化的扫雷游戏,同时在这个过程中提升代码能力与用户体验设计思维。