今日闲来无事,自己随便做了个扫雷游戏网页版(点击此处体验),以下是源代码,感兴趣的也可以一起做一下。
index.html
<!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="style.css">
</head>
<body>
<div class="game-controls">
<select id="difficulty" onchange="initGame()">
<option value="easy">初级 (9×9)</option>
<option value="medium">中级 (16×16)</option>
<option value="hard">高级 (24×24)</option>
</select>
<div class="best-time">最佳成绩: <span id="best-time">-</span>秒</div>
</div>
<div class="game-describe"
<div><h2>操作说明</h2></div>
<li>PC端左键单击以揭示内容,移动端轻点以揭示内容</li>
<li>PC端右键标记/取消标记地雷,移动端长按0.5s标记/取消标记地雷</li>
</div>
<div class="game-info">
<div>剩余地雷: <span id="mine-count">10</span></div>
<button onclick="initGame()">重新开始</button>
<div>时间: <span id="timer">0</span>秒</div>
</div>
<div id="board" class="game-board"></div>
<script src="script.js"></script>
</body>
</html>
style.css
body {
font-family: "Microsoft YaHei", Arial, sans-serif;
-webkit-font-smoothing: antialiased;
display: flex;
flex-direction: column;
align-items: center;
background: #f0f0f0;
margin: 20px;
}
.game-controls {
margin-bottom: 15px;
display: flex;
gap: 20px;
align-items: center;
}
select {
padding: 8px 15px;
font-size: 16px;
border-radius: 4px;
border: 1px solid #666;
}
.game-info {
display: flex;
gap: 20px;
margin-bottom: 15px;
align-items: center;
font-size: 18px;
}
button {
padding: 8px 20px;
font-size: 16px;
background: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background: #45a049;
}
.game-board {
display: grid;
gap: 1px;
background: #999;
padding: 10px;
border: 3px solid #666;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
}
.cell {
width: 30px;
height: 30px;
background: #bdbdbd;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
font-weight: bold;
border: 3px outset #fff;
font-size: 16px;
user-select: none;
touch-action: manipulation;
transition: transform 0.1s;
}
.cell:active {
transform: scale(0.95);
}
.revealed {
border: 3px inset #808080 !important;
background: #c0c0c0;
}
.flagged::after {
content: "🚩";
font-size: 18px;
}
.flagged-overflow {
background-color: #ffcccc !important;
}
.mine::after {
content: "💣";
font-size: 20px;
}
.mine-exploded {
background-color: #ff4444;
animation: mine-explode 0.3s ease 2;
}
@keyframes mine-explode {
0% { transform: scale(1); }
50% { transform: scale(1.2); }
100% { transform: scale(1); }
}
.number-1 { color: #0000FF; }
.number-2 { color: #008000; }
.number-3 { color: #FF0000; }
.number-4 { color: #000080; }
.number-5 { color: #800080; }
.number-6 { color: #008080; }
.number-7 { color: #000000; }
.number-8 { color: #808080; }
@media (max-width: 768px) {
.cell {
width: 35px;
height: 35px;
font-size: 18px;
}
.game-info {
flex-wrap: wrap;
justify-content: center;
}
}
.best-time {
font-weight: bold;
color: #2196F3;
}
script.js
const DIFFICULTY_SETTINGS = {
easy: { size: 9, mines: 10 },
medium: { size: 16, mines: 40 },
hard: { size: 24, mines: 99 }
};
let gameConfig = DIFFICULTY_SETTINGS.easy;
let board = [];
let gameOver = false;
let firstClick = true;
let timer = 0;
let timerInterval;
let touchStartTime = 0;
// 本地存储最佳成绩
function getBestTime() {
const stored = localStorage.getItem(`minesweeper-best-${gameConfig.size}`);
return stored ? parseInt(stored) : Infinity;
}
function setBestTime(time) {
localStorage.setItem(`minesweeper-best-${gameConfig.size}`, time);
document.getElementById('best-time').textContent = time;
}
function initGame() {
const difficulty = document.getElementById('difficulty').value;
gameConfig = DIFFICULTY_SETTINGS[difficulty];
document.getElementById('mine-count').textContent = gameConfig.mines;
gameOver = false;
firstClick = true;
timer = 0;
clearInterval(timerInterval);
document.getElementById('timer').textContent = timer;
const bestTime = getBestTime();
document.getElementById('best-time').textContent = isFinite(bestTime) ? bestTime : '-';
createEmptyBoard();
renderBoard();
}
function createEmptyBoard() {
board = Array(gameConfig.size).fill().map(() =>
Array(gameConfig.size).fill().map(() => ({
isMine: false,
revealed: false,
flagged: false,
neighborMines: 0
}))
);
}
function generateMinesAfterFirstClick(safeX, safeY) {
let minesPlaced = 0;
const safeArea = getSafeArea(safeX, safeY);
while (minesPlaced < gameConfig.mines) {
const x = Math.floor(Math.random() * gameConfig.size);
const y = Math.floor(Math.random() * gameConfig.size);
if (!safeArea.has(`${x},${y}`) && !board[x][y].isMine) {
board[x][y].isMine = true;
minesPlaced++;
}
}
calculateNeighborMines();
}
function getSafeArea(x, y) {
const safeCells = new Set();
for (let dx = -1; dx <= 1; dx++) {
for (let dy = -1; dy <= 1; dy++) {
const nx = x + dx;
const ny = y + dy;
if (nx >= 0 && nx < gameConfig.size && ny >= 0 && ny < gameConfig.size) {
safeCells.add(`${nx},${ny}`);
}
}
}
return safeCells;
}
function calculateNeighborMines() {
for (let x = 0; x < gameConfig.size; x++) {
for (let y = 0; y < gameConfig.size; y++) {
if (!board[x][y].isMine) {
board[x][y].neighborMines = countNeighborMines(x, y);
}
}
}
}
function countNeighborMines(x, y) {
let count = 0;
for (let dx = -1; dx <= 1; dx++) {
for (let dy = -1; dy <= 1; dy++) {
const nx = x + dx;
const ny = y + dy;
if (nx >= 0 && nx < gameConfig.size &&
ny >= 0 && ny < gameConfig.size &&
board[nx][ny].isMine) {
count++;
}
}
}
return count;
}
function renderBoard() {
const boardElement = document.getElementById('board');
boardElement.style.gridTemplateColumns = `repeat(${gameConfig.size}, 30px)`;
boardElement.innerHTML = '';
const flaggedCount = board.flat().filter(cell => cell.flagged).length;
const isOverflow = flaggedCount > gameConfig.mines;
for (let x = 0; x < gameConfig.size; x++) {
for (let y = 0; y < gameConfig.size; y++) {
const cell = document.createElement('div');
const cellData = board[x][y];
cell.className = 'cell';
cell.dataset.x = x;
cell.dataset.y = y;
if (cellData.revealed) {
cell.classList.add('revealed');
if (cellData.isMine) {
cell.classList.add('mine');
if (cellData.exploded) cell.classList.add('mine-exploded');
} else if (cellData.neighborMines > 0) {
cell.classList.add(`number-${cellData.neighborMines}`);
cell.textContent = cellData.neighborMines;
}
} else if (cellData.flagged) {
cell.classList.add('flagged');
if (isOverflow) cell.classList.add('flagged-overflow');
}
cell.addEventListener('click', () => handleClick(x, y, false));
cell.addEventListener('contextmenu', (e) => {
e.preventDefault();
handleClick(x, y, true);
});
boardElement.appendChild(cell);
}
}
}
function handleClick(x, y, isRightClick) {
if (gameOver || board[x][y].revealed) return;
if (firstClick) {
firstClick = false;
generateMinesAfterFirstClick(x, y);
timerInterval = setInterval(() => {
document.getElementById('timer').textContent = ++timer;
}, 1000);
}
if (isRightClick) {
if (!board[x][y].revealed) {
const flaggedCount = board.flat().filter(cell => cell.flagged).length;
// 标记数量限制
if (!board[x][y].flagged && flaggedCount >= gameConfig.mines) return;
board[x][y].flagged = !board[x][y].flagged;
updateMineCount();
}
} else {
if (board[x][y].isMine) {
board[x][y].exploded = true;
gameOver = true;
revealAll();
clearInterval(timerInterval);
renderBoard();
setTimeout(() => alert('游戏结束!'), 300);
} else {
reveal(x, y);
checkWin();
}
}
renderBoard();
}
function reveal(x, y) {
if (x < 0 || x >= gameConfig.size ||
y < 0 || y >= gameConfig.size ||
board[x][y].revealed ||
board[x][y].flagged) return;
board[x][y].revealed = true;
if (board[x][y].neighborMines === 0) {
for (let dx = -1; dx <= 1; dx++) {
for (let dy = -1; dy <= 1; dy++) {
reveal(x + dx, y + dy);
}
}
}
}
function revealAll() {
for (let x = 0; x < gameConfig.size; x++) {
for (let y = 0; y < gameConfig.size; y++) {
board[x][y].revealed = true;
}
}
}
function checkWin() {
const win = board.flat().every(cell =>
(cell.isMine && !cell.revealed) || (!cell.isMine && cell.revealed)
);
if (win) {
gameOver = true;
clearInterval(timerInterval);
if (timer < getBestTime()) setBestTime(timer);
setTimeout(() => alert('恭喜,你赢了!'), 100);
}
}
function updateMineCount() {
const flaggedCount = board.flat().filter(cell => cell.flagged).length;
const remaining = Math.max(gameConfig.mines - flaggedCount, 0);
document.getElementById('mine-count').textContent = remaining;
}
// 移动端支持
document.getElementById('board').addEventListener('touchstart', function(e) {
touchStartTime = Date.now();
e.preventDefault();
}, { passive: false });
document.getElementById('board').addEventListener('touchend', function(e) {
const touchDuration = Date.now() - touchStartTime;
const touch = e.changedTouches[0];
const target = document.elementFromPoint(touch.clientX, touch.clientY);
if (target?.dataset?.x && target?.dataset?.y) {
const x = parseInt(target.dataset.x);
const y = parseInt(target.dataset.y);
handleClick(x, y, touchDuration > 500);
}
e.preventDefault();
});
// 初始化游戏
initGame();
若有什么疑问或需要改进的地方欢迎在评论区交流❤️
Comments 2 条评论
赞👍
@白夜✨极光 感谢支持❤️