代码解析
<!DOCTYPE html>
<html lang="zh">
- 声明文档类型为 HTML5,并设置页面语言为中文
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>扫雷游戏</title>
<meta charset="UTF-8">
: 设置字符编码为 UTF-8,确保页面正确显示各种语言字符<meta name="viewport" content="width=device-width, initial-scale=1.0">
: 控制页面的视口宽度,确保在移动设备上呈现良好<title>扫雷游戏</title>
: 设置浏览器标签上的页面标题为“扫雷游戏”<style> body { background-color: #222; color: #fff; margin: 0; font-family: Arial, sans-serif; display: flex; justify-content: center; align-items: center; min-height: 100vh; /* 确保页面高度 */ overflow: auto; /* 允许滚动 */ }
background-color: #222;
: 设置页面背景色为深灰色color: #fff;
: 设置文本颜色为白色margin: 0;
: 移除页面默认的外边距font-family: Arial, sans-serif;
: 设置字体为 Arial,后备字体为 sans-serifdisplay: flex;
: 使用 Flexbox 布局模型justify-content: center;
: 水平居中内容align-items: center;
: 垂直居中内容min-height: 100vh;
: 确保页面至少占满视口高度overflow: auto;
: 允许页面内容溢出时出现滚动条#minesweeper { display: flex; flex-direction: column; align-items: center; padding: 20px; overflow: auto; /* 确保内容溢出时可以滚动 */ margin: 20px 0; /* 添加间距以允许滚动 */ }
display: flex;
: 使用 Flexbox 布局模型flex-direction: column;
: 垂直排列子元素align-items: center;
: 水平居中子元素padding: 20px;
: 添加内边距overflow: auto;
: 内容溢出时出现滚动条margin: 20px 0;
: 添加上下外边距,允许滚动#header { text-align: center; margin-bottom: 10px; }
text-align: center;
: 水平居中对齐文本margin-bottom: 10px;
: 设置底部外边距为 10 像素#difficulty { display: flex; justify-content: center; margin-bottom: 10px; }
display: flex;
: 使用 Flexbox 布局模型justify-content: center;
: 水平居中对齐子元素margin-bottom: 10px;
: 设置底部外边距为 10 像素.button { background-color: #556; border: 1px solid #444; padding: 10px; margin: 0 5px; cursor: pointer; } .button:hover { background-color: #667; }
.button
: 设置按钮样式,背景色为 #556,边框为 1 像素实线,内边距 10 像素,左右外边距 5 像素,光标为手形.button:hover
: 鼠标悬停时,背景色变为 #667#status { display: flex; justify-content: space-between; width: 200px; margin-bottom: 10px; }
display: flex;
: 使用 Flexbox 布局模型justify-content: space-between;
: 在子元素之间分配空间width: 200px;
: 设置宽度为 200 像素margin-bottom: 10px;
: 设置底部外边距为 10 像素#board { display: grid; gap: 1px; /* 设置格子之间的较小间隙 */ background-color: #222; /* 间隙的背景颜色 */ padding: 1px; /* 确保最外层边框一致 */ box-sizing: border-box; /* 将填充和边框包含在元素的总宽度和高度内 */ }
display: grid;
: 使用网格布局模型gap: 1px;
: 设置网格间隙为 1 像素background-color: #222;
: 设置背景色为深灰色padding: 1px;
: 添加内边距 1 像素box-sizing: border-box;
: 包含填充和边框在元素的总宽度和高度内.cell { width: 20px; height: 20px; background-color: #334; display: flex; justify-content: center; align-items: center; cursor: pointer; box-sizing: border-box; /* 将填充和边框包含在元素的总宽度和高度内 */ } .cell.open { background-color: #ccc; } .cell.flag { background-color: #add8e6; /* 标记单元格的浅蓝色 */ } .cell.mine { background-color: #f00; color: #fff; /* 白色星号 */ } .face-button { background-color: transparent; border: none; font-size: 24px; cursor: pointer; } </style> </head> <body> <div id="minesweeper"> <div id="header"> <h1>扫雷</h1> </div> <div id="difficulty"> <div class="button" data-difficulty="easy">简单</div> <div class="button" data-difficulty="medium">中等</div> <div class="button" data-difficulty="hard">困难</div> </div> <div id="status"> <div id="time">000</div> <button id="face" class="face-button">😊</button> <div id="mines-count">010</div> </div> <div id="board"> <!-- 单元格将由JavaScript添加 --> </div> </div>
.cell
: 单元格的基本样式,包括宽高、背景色、居中对齐和手形光标.cell.open
: 单元格被打开时的背景色.cell.flag
: 单元格被标记为旗帜时的背景色.cell.mine
: 单元格为地雷时的背景色和文本颜色.face-button
: 面板按钮的样式,包括透明背景、无边框、字体大小和手形光标<script> const board = document.getElementById('board'); const timeDisplay = document.getElementById('time'); const minesCountDisplay = document.getElementById('mines-count'); const faceDisplay = document.getElementById('face'); let timer; let time = 0; let minesCount = 0; let gameOver = false; let gameStarted = false; // 添加标志以检查游戏是否已开始 let currentDifficulty = 'easy'; // 默认难度 let rows, cols, mines; let cells, minePositions;
- 获取 HTML 元素的引用,如
board
、timeDisplay
、minesCountDisplay
和faceDisplay
- 定义变量来管理游戏状态,如计时器、时间、地雷计数、游戏结束标志、游戏开始标志、当前难度、行数、列数、地雷数、单元格和地雷位置
function createBoard(rows, cols, mines) { board.innerHTML = ''; board.style.gridTemplateColumns = `repeat(${cols}, 20px)`; board.style.gridTemplateRows = `repeat(${rows}, 20px)`; minesCount = mines; gameOver = false; gameStarted = false; // 重置游戏开始标志 minesCountDisplay.textContent = mines.toString().padStart(3, '0'); cells = []; minePositions = new Set();
- 清空棋盘内容
- 设置棋盘的网格布局
- 初始化地雷数、游戏结束标志、游戏开始标志
- 更新地雷计数显示
- 初始化单元格和地雷位置
while (minePositions.size < mines) { minePositions.add(Math.floor(Math.random() * rows * cols)); }
-
随机生成地雷的位置,确保地雷数目正确
for (let i = 0; i < rows * cols; i++) { const cell = document.createElement('div'); cell.classList.add('cell'); board.appendChild(cell); cells.push(cell);
-
创建单元格并添加到棋盘中,同时将单元格添加到
cells
数组cell.addEventListener('contextmenu', (e) => { e.preventDefault(); if (!cell.classList.contains('open') && !gameOver) { cell.classList.toggle('flag'); minesCount += cell.classList.contains('flag') ? -1 : 1; minesCountDisplay.textContent = minesCount.toString().padStart(3, '0'); } });
-
右键单击单元格时,切换标记状态,并更新地雷计数
cell.addEventListener('click', () => { if (!cell.classList.contains('open') && !cell.classList.contains('flag') && !gameOver) { if (!gameStarted) { startTimer(); gameStarted = true; } openCell(i); } }); } }
-
左键单击单元格时,开始计时并打开单元格
function openCell(index) { const cell = cells[index]; if (minePositions.has(index)) { cell.classList.add('mine'); cell.textContent = '*'; gameOver = true; faceDisplay.textContent = '😭'; clearInterval(timer); showAllMines(); } else { cell.classList.add('open'); const minesAround = countMinesAround(index); if (minesAround > 0) { cell.textContent = minesAround; } else { openSurroundingCells(index); } } }
-
打开单元格。如果单元格是地雷,显示所有地雷并结束游戏;如果不是地雷,则显示周围地雷数目或打开周围单元格
function showAllMines() { minePositions.forEach(index => { const mineCell = cells[index]; if (!mineCell.classList.contains('mine')) { mineCell.classList.add('mine'); mineCell.textContent = '*'; } }); }
-
显示所有地雷位置
function countMinesAround(index) { const row = Math.floor(index / cols); const col = index % cols; let count = 0; for (let i = -1; i <= 1; i++) { for (let j = -1; j <= 1; j++) { if (i === 0 && j === 0) continue; const newRow = row + i; const newCol = col + j; if (newRow >= 0 && newRow < rows && newCol >= 0 && newCol < cols) { const newIndex = newRow * cols + newCol; if (minePositions.has(newIndex)) { count++; } } } } return count; }
-
计算给定单元格周围的地雷数目
function openSurroundingCells(index) { const row = Math.floor(index / cols); const col = index % cols; for (let i = -1; i <= 1; i++) { for (let j = -1; j <= 1; j++) { if (i === 0 && j === 0) continue; const newRow = row + i; const newCol = col + j; if (newRow >= 0 && newRow < rows && newCol >= 0 && newCol < cols) { const newIndex = newRow * cols + newCol; const newCell = cells[newIndex]; if (!newCell.classList.contains('open') && !newCell.classList.contains('flag')) { openCell(newIndex); } } } } }
-
打开指定单元格周围的所有单元格
function startTimer() { timer = setInterval(() => { time++; timeDisplay.textContent = time.toString().padStart(3, '0'); }, 1000); }
-
启动计时器,每秒更新一次时间显示
function startGame(difficulty) { clearInterval(timer); time = 0; timeDisplay.textContent = '000'; faceDisplay.textContent = '😊'; currentDifficulty = difficulty; switch (difficulty) { case 'easy': rows = cols = 8; mines = 10; break; case 'medium': rows = cols = 16; mines = 40; break; case 'hard': rows = cols = 24; mines = 99; break; } createBoard(rows, cols, mines); }
-
启动游戏,根据难度设置行数、列数和地雷数,清空计时器并重置时间和面板按钮
document.querySelectorAll('.button').forEach(button => { button.addEventListener('click', () => { const difficulty = button.dataset.difficulty; startGame(difficulty); }); }); faceDisplay.addEventListener('click', () => { startGame(currentDifficulty); }); // 使用默认难度开始游戏 startGame(currentDifficulty); </script> </body> </html>
- 为每个难度按钮添加点击事件处理程序,启动对应难度的游戏
- 为面板按钮添加点击事件处理程序,重启游戏
- 使用默认难度启动游戏
全部代码
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>扫雷游戏</title>
<style>
body {
background-color: #222;
color: #fff;
margin: 0;
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh; /* 确保页面高度 */
overflow: auto; /* 允许滚动 */
}
#minesweeper {
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
overflow: auto; /* 确保内容溢出时可以滚动 */
margin: 20px 0; /* 添加间距以允许滚动 */
}
#header {
text-align: center;
margin-bottom: 10px;
}
#difficulty {
display: flex;
justify-content: center;
margin-bottom: 10px;
}
.button {
background-color: #556;
border: 1px solid #444;
padding: 10px;
margin: 0 5px;
cursor: pointer;
}
.button:hover {
background-color: #667;
}
#status {
display: flex;
justify-content: space-between;
width: 200px;
margin-bottom: 10px;
}
#board {
display: grid;
gap: 1px; /* 设置格子之间的较小间隙 */
background-color: #222; /* 间隙的背景颜色 */
padding: 1px; /* 确保最外层边框一致 */
box-sizing: border-box; /* 将填充和边框包含在元素的总宽度和高度内 */
}
.cell {
width: 20px;
height: 20px;
background-color: #334;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
box-sizing: border-box; /* 将填充和边框包含在元素的总宽度和高度内 */
}
.cell.open {
background-color: #ccc;
}
.cell.flag {
background-color: #add8e6; /* 标记单元格的浅蓝色 */
}
.cell.mine {
background-color: #f00;
color: #fff; /* 白色星号 */
}
.face-button {
background-color: transparent;
border: none;
font-size: 24px;
cursor: pointer;
}
</style>
</head>
<body>
<div id="minesweeper">
<div id="header">
<h1>扫雷</h1>
</div>
<div id="difficulty">
<div class="button" data-difficulty="easy">简单</div>
<div class="button" data-difficulty="medium">中等</div>
<div class="button" data-difficulty="hard">困难</div>
</div>
<div id="status">
<div id="time">000</div>
<button id="face" class="face-button">😊</button>
<div id="mines-count">010</div>
</div>
<div id="board">
<!-- 单元格将由JavaScript添加 -->
</div>
</div>
<script>
const board = document.getElementById('board');
const timeDisplay = document.getElementById('time');
const minesCountDisplay = document.getElementById('mines-count');
const faceDisplay = document.getElementById('face');
let timer;
let time = 0;
let minesCount = 0;
let gameOver = false;
let gameStarted = false; // 添加标志以检查游戏是否已开始
let currentDifficulty = 'easy'; // 默认难度
let rows, cols, mines;
let cells, minePositions;
function createBoard(rows, cols, mines) {
board.innerHTML = '';
board.style.gridTemplateColumns = `repeat(${cols}, 20px)`;
board.style.gridTemplateRows = `repeat(${rows}, 20px)`;
minesCount = mines;
gameOver = false;
gameStarted = false; // 重置游戏开始标志
minesCountDisplay.textContent = mines.toString().padStart(3, '0');
cells = [];
minePositions = new Set();
while (minePositions.size < mines) {
minePositions.add(Math.floor(Math.random() * rows * cols));
}
for (let i = 0; i < rows * cols; i++) {
const cell = document.createElement('div');
cell.classList.add('cell');
board.appendChild(cell);
cells.push(cell);
cell.addEventListener('contextmenu', (e) => {
e.preventDefault();
if (!cell.classList.contains('open') && !gameOver) {
cell.classList.toggle('flag');
minesCount += cell.classList.contains('flag') ? -1 : 1;
minesCountDisplay.textContent = minesCount.toString().padStart(3, '0');
}
});
cell.addEventListener('click', () => {
if (!cell.classList.contains('open') && !cell.classList.contains('flag') && !gameOver) {
if (!gameStarted) {
startTimer();
gameStarted = true;
}
openCell(i);
}
});
}
}
function openCell(index) {
const cell = cells[index];
if (minePositions.has(index)) {
cell.classList.add('mine');
cell.textContent = '*';
gameOver = true;
faceDisplay.textContent = '😭';
clearInterval(timer);
showAllMines();
} else {
cell.classList.add('open');
const minesAround = countMinesAround(index);
if (minesAround > 0) {
cell.textContent = minesAround;
} else {
openSurroundingCells(index);
}
}
}
function showAllMines() {
minePositions.forEach(index => {
const mineCell = cells[index];
if (!mineCell.classList.contains('mine')) {
mineCell.classList.add('mine');
mineCell.textContent = '*';
}
});
}
function countMinesAround(index) {
const row = Math.floor(index / cols);
const col = index % cols;
let count = 0;
for (let i = -1; i <= 1; i++) {
for (let j = -1; j <= 1; j++) {
if (i === 0 && j === 0) continue;
const newRow = row + i;
const newCol = col + j;
if (newRow >= 0 && newRow < rows && newCol >= 0 && newCol < cols) {
const newIndex = newRow * cols + newCol;
if (minePositions.has(newIndex)) {
count++;
}
}
}
}
return count;
}
function openSurroundingCells(index) {
const row = Math.floor(index / cols);
const col = index % cols;
for (let i = -1; i <= 1; i++) {
for (let j = -1; j <= 1; j++) {
if (i === 0 && j === 0) continue;
const newRow = row + i;
const newCol = col + j;
if (newRow >= 0 && newRow < rows && newCol >= 0 && newCol < cols) {
const newIndex = newRow * cols + newCol;
const newCell = cells[newIndex];
if (!newCell.classList.contains('open') && !newCell.classList.contains('flag')) {
openCell(newIndex);
}
}
}
}
}
function startTimer() {
timer = setInterval(() => {
time++;
timeDisplay.textContent = time.toString().padStart(3, '0');
}, 1000);
}
function startGame(difficulty) {
clearInterval(timer);
time = 0;
timeDisplay.textContent = '000';
faceDisplay.textContent = '😊';
currentDifficulty = difficulty;
switch (difficulty) {
case 'easy':
rows = cols = 8;
mines = 10;
break;
case 'medium':
rows = cols = 16;
mines = 40;
break;
case 'hard':
rows = cols = 24;
mines = 99;
break;
}
createBoard(rows, cols, mines);
}
document.querySelectorAll('.button').forEach(button => {
button.addEventListener('click', () => {
const difficulty = button.dataset.difficulty;
startGame(difficulty);
});
});
faceDisplay.addEventListener('click', () => {
startGame(currentDifficulty);
});
// 使用默认难度开始游戏
startGame(currentDifficulty);
</script>
</body>
</html>