<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<title>扫雷游戏</title>
</head>
<body>
<div id="game-controls">
<select id="difficulty">
<option value="easy">简单 (9x9, 10雷)</option>
<option value="medium">中等 (16x16, 40雷)</option>
<option value="hard">困难 (16x30, 99雷)</option>
</select>
<button id="restart">重新开始</button>
<button id="pause">暂停</button>
<span id="timer">00:00</span>
<span id="mines-left">雷数: 0</span>
</div>
<div id="game-board"></div>
<div id="game-message"></div>
</body>
</html>
body {
font-family: Arial, sans-serif;
background: #e0e0e0;
text-align: center;
margin: 0;
padding: 0;
}
#game-controls {
margin: 20px auto;
display: flex;
justify-content: center;
align-items: center;
gap: 15px;
}
#game-board {
display: inline-block;
margin: 20px auto;
background: #bdbdbd;
border: 4px solid #888;
box-shadow: 2px 2px 8px #888;
}
.row {
display: flex;
}
.cell {
width: 30px;
height: 30px;
background: #d0d0d0;
border: 1px solid #888;
font-size: 18px;
font-weight: bold;
color: #333;
cursor: pointer;
user-select: none;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.1s;
}
.cell.open {
background: #f0f0f0;
cursor: default;
}
.cell.flag {
background: #ffe082;
color: #d84315;
}
.cell.mine {
background: #e57373;
color: #fff;
}
#game-message {
margin: 20px auto;
font-size: 24px;
color: #d84315;
min-height: 32px;
}
button,
select {
font-size: 16px;
padding: 4px 10px;
}
const DIFFICULTY = {
easy: { rows: 9, cols: 9, mines: 10 },
medium: { rows: 16, cols: 16, mines: 40 },
hard: { rows: 16, cols: 30, mines: 99 }
};
let board = [];
let rows, cols, minesCount;
let minesLeft, openedCells, totalCells;
let timer = 0, timerInterval = null, paused = false, gameOver = false, firstClick = true;
const boardDiv = document.getElementById('game-board');
const timerSpan = document.getElementById('timer');
const minesLeftSpan = document.getElementById('mines-left');
const messageDiv = document.getElementById('game-message');
const restartBtn = document.getElementById('restart');
const pauseBtn = document.getElementById('pause');
const difficultySel = document.getElementById('difficulty');
function initGame() {
const diff = DIFFICULTY[difficultySel.value];
rows = diff.rows;
cols = diff.cols;
minesCount = diff.mines;
minesLeft = minesCount;
openedCells = 0;
totalCells = rows * cols;
timer = 0;
firstClick = true;
gameOver = false;
paused = false;
clearInterval(timerInterval);
timerSpan.textContent = "00:00";
pauseBtn.textContent = "暂停";
messageDiv.textContent = "";
minesLeftSpan.textContent = `雷数: ${minesLeft}`;
createBoard();
renderBoard();
}
function createBoard() {
board = [];
for (let r = 0; r < rows; r++) {
let row = [];
for (let c = 0; c < cols; c++) {
row.push({
r, c,
mine: false,
open: false,
flag: false,
count: 0
});
}
board.push(row);
}
}
function placeMines(excludeR, excludeC) {
let placed = 0;
while (placed < minesCount) {
let r = Math.floor(Math.random() * rows);
let c = Math.floor(Math.random() * cols);
// 不在第一次点击的格子及其周围
if (Math.abs(r - excludeR) <= 1 && Math.abs(c - excludeC) <= 1) continue;
if (!board[r][c].mine) {
board[r][c].mine = true;
placed++;
}
}
// 计算周围雷数
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
board[r][c].count = countMines(r, c);
}
}
}
function countMines(r, c) {
let cnt = 0;
for (let dr = -1; dr <= 1; dr++) {
for (let dc = -1; dc <= 1; dc++) {
if (dr === 0 && dc === 0) continue;
let nr = r + dr, nc = c + dc;
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols) {
if (board[nr][nc].mine) cnt++;
}
}
}
return cnt;
}
function renderBoard() {
boardDiv.innerHTML = '';
for (let r = 0; r < rows; r++) {
let rowDiv = document.createElement('div');
rowDiv.className = 'row';
for (let c = 0; c < cols; c++) {
let cell = board[r][c];
let cellDiv = document.createElement('div');
cellDiv.className = 'cell';
cellDiv.dataset.r = r;
cellDiv.dataset.c = c;
if (cell.open) {
cellDiv.classList.add('open');
if (cell.mine) {
cellDiv.classList.add('mine');
cellDiv.textContent = '💣';
} else if (cell.count > 0) {
cellDiv.textContent = cell.count;
cellDiv.style.color = getNumberColor(cell.count);
}
} else if (cell.flag) {
cellDiv.classList.add('flag');
cellDiv.textContent = '🚩';
}
rowDiv.appendChild(cellDiv);
}
boardDiv.appendChild(rowDiv);
}
}
function getNumberColor(n) {
const colors = ['#1976d2', '#388e3c', '#d32f2f', '#7b1fa2', '#fbc02d', '#0097a7', '#e65100', '#616161'];
return colors[n - 1] || '#333';
}
function openCell(r, c) {
let cell = board[r][c];
if (cell.open || cell.flag) return;
cell.open = true;
openedCells++;
if (cell.mine) {
cell.open = true;
gameOver = true;
revealMines();
renderBoard();
messageDiv.textContent = "游戏失败!";
clearInterval(timerInterval);
return;
}
if (cell.count === 0) {
for (let dr = -1; dr <= 1; dr++) {
for (let dc = -1; dc <= 1; dc++) {
let nr = r + dr, nc = c + dc;
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols) {
if (!board[nr][nc].open) openCell(nr, nc);
}
}
}
}
if (openedCells === totalCells - minesCount) {
gameOver = true;
revealMines(true);
renderBoard();
messageDiv.textContent = "恭喜你,胜利!";
clearInterval(timerInterval);
}
}
function revealMines(win = false) {
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
let cell = board[r][c];
if (cell.mine) {
cell.open = true;
}
if (win && cell.mine && !cell.flag) {
cell.flag = true;
}
}
}
}
function toggleFlag(r, c) {
let cell = board[r][c];
if (cell.open) return;
cell.flag = !cell.flag;
minesLeft += cell.flag ? -1 : 1;
minesLeftSpan.textContent = `雷数: ${minesLeft}`;
}
function handleCellClick(e) {
if (gameOver || paused) return;
const target = e.target;
if (!target.classList.contains('cell')) return;
const r = +target.dataset.r, c = +target.dataset.c;
if (firstClick) {
placeMines(r, c);
startTimer();
firstClick = false;
}
openCell(r, c);
renderBoard();
}
function handleCellRightClick(e) {
e.preventDefault();
if (gameOver || paused) return;
const target = e.target;
if (!target.classList.contains('cell')) return;
const r = +target.dataset.r, c = +target.dataset.c;
toggleFlag(r, c);
renderBoard();
}
function startTimer() {
clearInterval(timerInterval);
timerInterval = setInterval(() => {
if (!paused) {
timer++;
timerSpan.textContent = formatTime(timer);
}
}, 1000);
}
function formatTime(t) {
let m = Math.floor(t / 60).toString().padStart(2, '0');
let s = (t % 60).toString().padStart(2, '0');
return `${m}:${s}`;
}
function pauseGame() {
if (gameOver || firstClick) return;
paused = !paused;
pauseBtn.textContent = paused ? "继续" : "暂停";
messageDiv.textContent = paused ? "已暂停" : "";
}
restartBtn.onclick = initGame;
pauseBtn.onclick = pauseGame;
difficultySel.onchange = initGame;
boardDiv.addEventListener('click', handleCellClick);
boardDiv.addEventListener('contextmenu', handleCellRightClick);
initGame();