1.经典玩法与现代设计结合,支持左键点击揭开方块、右键标记地雷
2.多种难度难度级别(初级、中级、高级、自定义),满足不同玩家需求
3.精美的动画效果,包括地雷爆炸、胜利庆祝烟花等
4.深色 / 浅色主题切换,适应不同使用环境
5.游戏计时功能和最高分排行榜,记录您的最佳成绩
6.响应式设计,适配不同屏幕尺寸
代码:复制该成html后缀即可
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>
<!-- Tailwind CSS v3 -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Font Awesome -->
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<!-- GSAP 动画库 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#3b82f6',
secondary: '#6366f1',
accent: '#f43f5e',
success: '#10b981',
warning: '#f59e0b',
info: '#06b6d4',
dark: '#1e293b',
light: '#f8fafc'
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
},
animation: {
'bounce-slow': 'bounce 2s infinite',
'pulse-slow': 'pulse 3s infinite',
'spin-slow': 'spin 3s linear infinite',
}
}
},
darkMode: 'class'
}
</script>
<style type="text/tailwindcss">
@layer utilities {
.cell-3d {
@apply relative transform transition-all duration-200 shadow-md;
}
.cell-3d:hover {
@apply -translate-y-1 shadow-lg;
}
.cell-3d:active {
@apply translate-y-0 shadow-sm;
}
.text-shadow {
text-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.text-shadow-lg {
text-shadow: 0 4px 8px rgba(0,0,0,0.2);
}
.bg-gradient-primary {
@apply bg-gradient-to-br from-primary to-secondary;
}
.bg-gradient-dark {
@apply bg-gradient-to-br from-dark to-slate-800;
}
.bg-gradient-light {
@apply bg-gradient-to-br from-light to-slate-100;
}
.number-1 { @apply text-blue-500; }
.number-2 { @apply text-green-500; }
.number-3 { @apply text-red-500; }
.number-4 { @apply text-blue-800; }
.number-5 { @apply text-red-800; }
.number-6 { @apply text-teal-500; }
.number-7 { @apply text-black; }
.number-8 { @apply text-gray-500; }
.confetti {
position: absolute;
width: 10px;
height: 10px;
opacity: 0;
animation: confetti 5s ease-in-out infinite;
}
@keyframes confetti {
0% {
transform: translateY(0) rotate(0deg);
opacity: 1;
}
100% {
transform: translateY(100vh) rotate(720deg);
opacity: 0;
}
}
.mine-explosion {
position: absolute;
width: 60px;
height: 60px;
border-radius: 50%;
background: radial-gradient(circle, rgba(255,69,0,1) 0%, rgba(255,140,0,1) 50%, rgba(255,165,0,1) 100%);
opacity: 0;
transform: scale(0);
animation: mineExplosion 0.5s ease-out forwards;
}
@keyframes mineExplosion {
0% {
opacity: 1;
transform: scale(0);
}
50% {
opacity: 1;
transform: scale(1);
}
100% {
opacity: 0;
transform: scale(1.5);
}
}
.flag-animation {
animation: flagWave 0.5s ease-in-out;
}
@keyframes flagWave {
0% { transform: rotate(0deg); }
25% { transform: rotate(10deg); }
50% { transform: rotate(0deg); }
75% { transform: rotate(-10deg); }
100% { transform: rotate(0deg); }
}
.smiley-face {
transition: transform 0.2s ease;
}
.smiley-face:hover {
transform: scale(1.1);
}
.smiley-face:active {
transform: scale(0.95);
}
.game-board {
display: grid;
grid-template-columns: repeat(var(--grid-cols), 1fr);
grid-template-rows: repeat(var(--grid-rows), 1fr);
gap: 2px;
}
.dark .cell {
@apply bg-slate-700 text-white;
}
.dark .cell.revealed {
@apply bg-slate-600;
}
.dark .cell:hover:not(.revealed) {
@apply bg-slate-600;
}
.light .cell {
@apply bg-slate-200 text-slate-800;
}
.light .cell.revealed {
@apply bg-slate-100;
}
.light .cell:hover:not(.revealed) {
@apply bg-slate-300;
}
.leaderboard-item {
@apply flex justify-between items-center p-2 border-b border-opacity-20;
}
.leaderboard-item:nth-child(odd) {
@apply bg-opacity-10;
}
.leaderboard-item:first-child {
@apply text-yellow-500 font-bold;
}
.leaderboard-item:nth-child(2) {
@apply text-gray-400 font-semibold;
}
.leaderboard-item:nth-child(3) {
@apply text-amber-700 font-medium;
}
.custom-slider {
@apply appearance-none w-full h-2 rounded-lg bg-slate-200 dark:bg-slate-700;
}
.custom-slider::-webkit-slider-thumb {
@apply appearance-none w-4 h-4 rounded-full bg-primary cursor-pointer;
}
.custom-slider::-moz-range-thumb {
@apply w-4 h-4 rounded-full bg-primary cursor-pointer border-0;
}
.modal {
@apply fixed inset-0 flex items-center justify-center z-50 transition-opacity duration-300;
background-color: rgba(0, 0, 0, 0.5);
}
.modal-content {
@apply relative bg-white dark:bg-slate-800 rounded-lg shadow-xl p-6 max-w-md w-full mx-4 transform transition-transform duration-300;
}
.modal.hidden {
@apply opacity-0 pointer-events-none;
}
.modal.hidden .modal-content {
@apply translate-y-8;
}
.tooltip {
@apply invisible absolute z-10 py-1 px-2 bg-slate-800 text-white text-xs rounded opacity-0 transition-opacity duration-300;
width: 120px;
bottom: 125%;
left: 50%;
transform: translateX(-50%);
}
.tooltip::after {
content: "";
@apply absolute top-full left-1/2 transform -translate-x-1/2 border-4 border-transparent border-t-slate-800;
}
.has-tooltip:hover .tooltip {
@apply visible opacity-100;
}
}
</style>
</head>
<body class="min-h-screen flex flex-col items-center justify-center p-4 bg-gradient-light dark:bg-gradient-dark transition-colors duration-300">
<div class="w-full max-w-3xl">
<!-- 游戏标题 -->
<header class="text-center mb-6">
<h1 class="text-4xl font-bold text-primary dark:text-blue-400 text-shadow-lg mb-2">扫雷</h1>
<p class="text-slate-600 dark:text-slate-300">经典游戏,全新体验</p>
</header>
<!-- 游戏控制面板 -->
<div class="bg-white dark:bg-slate-700 rounded-xl shadow-lg p-4 mb-6 transition-all duration-300">
<div class="flex flex-wrap items-center justify-between gap-4">
<!-- 难度选择 -->
<div class="flex items-center gap-2">
<label for="difficulty" class="text-sm font-medium text-slate-700 dark:text-slate-300">难度:</label>
<select id="difficulty" class="bg-slate-100 dark:bg-slate-600 border border-slate-200 dark:border-slate-500 text-slate-700 dark:text-slate-300 rounded-lg px-3 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-primary">
<option value="easy">初级 (9×9, 10雷)</option>
<option value="medium">中级 (16×16, 40雷)</option>
<option value="hard">高级 (16×30, 99雷)</option>
<option value="custom">自定义</option>
</select>
</div>
<!-- 游戏信息 -->
<div class="flex items-center gap-6">
<!-- 地雷计数器 -->
<div class="flex items-center gap-2">
<img src="https://p11-doubao-search-sign.byteimg.com/labis/3f387a46336d38186be6e8fb6dbbb292~tplv-be4g95zd3a-image.jpeg?rk3s=542c0f93&x-expires=1777797012&x-signature=Gw6lzs4Oao1sza428nsMPTnO4oY%3D" alt="地雷" class="w-6 h-6">
<span id="mine-counter" class="bg-slate-100 dark:bg-slate-600 text-accent font-mono text-xl px-3 py-1 rounded-lg min-w-[60px] text-center">0</span>
</div>
<!-- 重置按钮 -->
<button id="reset-btn" class="smiley-face bg-primary hover:bg-primary/90 text-white rounded-full w-10 h-10 flex items-center justify-center shadow-md transition-all">
<img src="https://p11-doubao-search-sign.byteimg.com/tos-cn-i-be4g95zd3a/1008012349039706117~tplv-be4g95zd3a-image.jpeg?rk3s=542c0f93&x-expires=1777797012&x-signature=H0VbbA99c4KalLNHqNO0gHq1JHY%3D" alt="笑脸" class="w-6 h-6">
</button>
<!-- 计时器 -->
<div class="flex items-center gap-2">
<i class="fa fa-clock-o text-slate-600 dark:text-slate-300 text-xl"></i>
<span id="timer" class="bg-slate-100 dark:bg-slate-600 text-primary font-mono text-xl px-3 py-1 rounded-lg min-w-[60px] text-center">0</span>
</div>
</div>
<!-- 主题切换和排行榜 -->
<div class="flex items-center gap-3">
<!-- 主题切换 -->
<button id="theme-toggle" class="bg-slate-100 dark:bg-slate-600 text-slate-700 dark:text-slate-300 p-2 rounded-lg hover:bg-slate-200 dark:hover:bg-slate-500 transition-colors">
<i class="fa fa-moon-o dark:hidden"></i>
<i class="fa fa-sun-o hidden dark:inline-block"></i>
</button>
<!-- 排行榜 -->
<button id="leaderboard-btn" class="bg-slate-100 dark:bg-slate-600 text-slate-700 dark:text-slate-300 p-2 rounded-lg hover:bg-slate-200 dark:hover:bg-slate-500 transition-colors has-tooltip">
<i class="fa fa-trophy"></i>
<span class="tooltip">查看排行榜</span>
</button>
</div>
</div>
</div>
<!-- 自定义难度设置 (默认隐藏) -->
<div id="custom-settings" class="bg-white dark:bg-slate-700 rounded-xl shadow-lg p-4 mb-6 hidden transition-all duration-300">
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label for="custom-width" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">宽度:</label>
<div class="flex items-center gap-2">
<input type="range" id="custom-width" min="5" max="30" value="16" class="custom-slider flex-1">
<span id="custom-width-value" class="text-sm font-medium text-slate-700 dark:text-slate-300 min-w-[30px] text-center">16</span>
</div>
</div>
<div>
<label for="custom-height" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">高度:</label>
<div class="flex items-center gap-2">
<input type="range" id="custom-height" min="5" max="24" value="16" class="custom-slider flex-1">
<span id="custom-height-value" class="text-sm font-medium text-slate-700 dark:text-slate-300 min-w-[30px] text-center">16</span>
</div>
</div>
<div>
<label for="custom-mines" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">地雷数:</label>
<div class="flex items-center gap-2">
<input type="range" id="custom-mines" min="1" max="99" value="40" class="custom-slider flex-1">
<span id="custom-mines-value" class="text-sm font-medium text-slate-700 dark:text-slate-300 min-w-[30px] text-center">40</span>
</div>
</div>
</div>
</div>
<!-- 游戏主区域 -->
<div class="relative bg-white dark:bg-slate-700 rounded-xl shadow-xl p-4 overflow-hidden transition-all duration-300">
<div id="game-board" class="game-board mx-auto" style="--grid-cols: 9; --grid-rows: 9;"></div>
<!-- 游戏结果覆盖层 -->
<div id="game-overlay" class="absolute inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden">
<div class="bg-white dark:bg-slate-800 rounded-xl p-6 text-center max-w-sm w-full mx-4 transform transition-all duration-300 scale-95 opacity-0" id="game-result">
<h2 id="result-title" class="text-2xl font-bold mb-4 text-primary">游戏胜利!</h2>
<p id="result-message" class="text-slate-700 dark:text-slate-300 mb-6">你成功找到了所有地雷!</p>
<div class="flex flex-col gap-3">
<div class="flex justify-center items-center gap-2">
<i class="fa fa-clock-o text-primary text-xl"></i>
<span id="result-time" class="text-xl font-mono">00:00</span>
</div>
<button id="play-again-btn" class="bg-primary hover:bg-primary/90 text-white py-2 px-4 rounded-lg transition-colors">再玩一次</button>
</div>
</div>
</div>
</div>
<!-- 游戏说明 -->
<div class="mt-6 bg-white dark:bg-slate-700 rounded-xl shadow-lg p-4 transition-all duration-300">
<h3 class="text-lg font-semibold text-slate-700 dark:text-slate-300 mb-2">游戏说明</h3>
<ul class="text-sm text-slate-600 dark:text-slate-400 space-y-1">
<li><span class="font-medium">左键点击:</span> 揭开方块</li>
<li><span class="font-medium">右键点击:</span> 标记地雷</li>
<li><span class="font-medium">数字:</span> 表示周围8个格子中的地雷数量</li>
<li><span class="font-medium">目标:</span> 揭开所有非地雷格子,标记所有地雷</li>
</ul>
</div>
</div>
<!-- 排行榜模态框 -->
<div id="leaderboard-modal" class="modal hidden">
<div class="modal-content">
<div class="flex justify-between items-center mb-4">
<h3 class="text-xl font-bold text-slate-700 dark:text-slate-300">排行榜</h3>
<button id="close-leaderboard" class="text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200">
<i class="fa fa-times"></i>
</button>
</div>
<div class="mb-4">
<select id="leaderboard-difficulty" class="w-full bg-slate-100 dark:bg-slate-600 border border-slate-200 dark:border-slate-500 text-slate-700 dark:text-slate-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary">
<option value="easy">初级 (9×9, 10雷)</option>
<option value="medium">中级 (16×16, 40雷)</option>
<option value="hard">高级 (16×30, 99雷)</option>
</select>
</div>
<div id="leaderboard-list" class="max-h-80 overflow-y-auto bg-slate-50 dark:bg-slate-700 rounded-lg">
<!-- 排行榜内容将通过JavaScript动态生成 -->
</div>
<div class="mt-4 text-center">
<button id="clear-leaderboard" class="text-sm text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300">清空排行榜</button>
</div>
</div>
</div>
<!-- 输入用户名模态框 -->
<div id="username-modal" class="modal hidden">
<div class="modal-content">
<h3 class="text-xl font-bold text-slate-700 dark:text-slate-300 mb-4">恭喜!</h3>
<p class="text-slate-600 dark:text-slate-400 mb-4">你创造了新的记录,请输入你的名字:</p>
<div class="mb-4">
<input type="text" id="username-input" maxlength="10" class="w-full bg-slate-100 dark:bg-slate-600 border border-slate-200 dark:border-slate-500 text-slate-700 dark:text-slate-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary" placeholder="输入你的名字">
</div>
<div class="flex justify-end">
<button id="save-username" class="bg-primary hover:bg-primary/90 text-white py-2 px-4 rounded-lg transition-colors">保存</button>
</div>
</div>
</div>
<script>
// 游戏状态
const gameState = {
width: 9,
height: 9,
mines: 10,
cells: [],
revealedCells: 0,
flaggedCells: 0,
isGameOver: false,
isGameWon: false,
startTime: null,
timer: null,
currentTime: 0,
difficulty: 'easy'
};
// 难度设置
const difficultySettings = {
easy: { width: 9, height: 9, mines: 10 },
medium: { width: 16, height: 16, mines: 40 },
hard: { width: 30, height: 16, mines: 99 }
};
// DOM 元素
const gameBoard = document.getElementById('game-board');
const mineCounter = document.getElementById('mine-counter');
const timerElement = document.getElementById('timer');
const resetBtn = document.getElementById('reset-btn');
const difficultySelect = document.getElementById('difficulty');
const customSettings = document.getElementById('custom-settings');
const customWidth = document.getElementById('custom-width');
const customHeight = document.getElementById('custom-height');
const customMines = document.getElementById('custom-mines');
const customWidthValue = document.getElementById('custom-width-value');
const customHeightValue = document.getElementById('custom-height-value');
const customMinesValue = document.getElementById('custom-mines-value');
const themeToggle = document.getElementById('theme-toggle');
const gameOverlay = document.getElementById('game-overlay');
const gameResult = document.getElementById('game-result');
const resultTitle = document.getElementById('result-title');
const resultMessage = document.getElementById('result-message');
const resultTime = document.getElementById('result-time');
const playAgainBtn = document.getElementById('play-again-btn');
const leaderboardBtn = document.getElementById('leaderboard-btn');
const leaderboardModal = document.getElementById('leaderboard-modal');
const closeLeaderboard = document.getElementById('close-leaderboard');
const leaderboardDifficulty = document.getElementById('leaderboard-difficulty');
const leaderboardList = document.getElementById('leaderboard-list');
const clearLeaderboard = document.getElementById('clear-leaderboard');
const usernameModal = document.getElementById('username-modal');
const usernameInput = document.getElementById('username-input');
const saveUsername = document.getElementById('save-username');
// 图标
const mineIcon = 'https://p11-doubao-search-sign.byteimg.com/labis/3f387a46336d38186be6e8fb6dbbb292~tplv-be4g95zd3a-image.jpeg?rk3s=542c0f93&x-expires=1777797012&x-signature=Gw6lzs4Oao1sza428nsMPTnO4oY%3D';
const flagIcon = 'https://p11-doubao-search-sign.byteimg.com/labis/3a96a1b270b1cd1a946f1f69f6cd371f~tplv-be4g95zd3a-image.jpeg?rk3s=542c0f93&x-expires=1777797012&x-signature=VzkBIqZPOtoBAypRKnlzuCDvUGg%3D';
const smileyIcon = 'https://p11-doubao-search-sign.byteimg.com/tos-cn-i-be4g95zd3a/1008012349039706117~tplv-be4g95zd3a-image.jpeg?rk3s=542c0f93&x-expires=1777797012&x-signature=H0VbbA99c4KalLNHqNO0gHq1JHY%3D';
const smileyOohIcon = 'https://p11-doubao-search-sign.byteimg.com/tos-cn-i-be4g95zd3a/1978633691853619215~tplv-be4g95zd3a-image.jpeg?rk3s=542c0f93&x-expires=1777797012&x-signature=it5Lye0g%2FGmLUCwDRjlWZ55Ogi4%3D';
const smileyLoseIcon = 'https://p11-doubao-search-sign.byteimg.com/tos-cn-i-be4g95zd3a/1008012349039706117~tplv-be4g95zd3a-image.jpeg?rk3s=542c0f93&x-expires=1777797012&x-signature=H0VbbA99c4KalLNHqNO0gHq1JHY%3D';
const smileyWinIcon = 'https://p11-doubao-search-sign.byteimg.com/tos-cn-i-be4g95zd3a/1008012349039706117~tplv-be4g95zd3a-image.jpeg?rk3s=542c0f93&x-expires=1777797012&x-signature=H0VbbA99c4KalLNHqNO0gHq1JHY%3D';
// 颜色映射
const numberColors = [
'', // 0
'number-1', // 1
'number-2', // 2
'number-3', // 3
'number-4', // 4
'number-5', // 5
'number-6', // 6
'number-7', // 7
'number-8' // 8
];
// 初始化游戏
function initGame() {
// 重置游戏状态
gameState.cells = [];
gameState.revealedCells = 0;
gameState.flaggedCells = 0;
gameState.isGameOver = false;
gameState.isGameWon = false;
gameState.startTime = null;
gameState.currentTime = 0;
// 更新地雷计数器
updateMineCounter();
// 重置计时器
resetTimer();
// 更新笑脸图标
updateSmileyIcon('normal');
// 创建游戏板
createGameBoard();
// 隐藏游戏结果覆盖层
gameOverlay.classList.add('hidden');
}
// 创建游戏板
function createGameBoard() {
// 清空游戏板
gameBoard.innerHTML = '';
// 设置网格大小
gameBoard.style.setProperty('--grid-cols', gameState.width);
gameBoard.style.setProperty('--grid-rows', gameState.height);
// 计算单元格大小
const maxWidth = gameBoard.parentElement.clientWidth - 16; // 减去内边距
const maxHeight = window.innerHeight * 0.6; // 最大高度为窗口高度的60%
const cellWidth = maxWidth / gameState.width;
const cellHeight = maxHeight / gameState.height;
const cellSize = Math.min(cellWidth, cellHeight);
// 创建单元格
for (let row = 0; row < gameState.height; row++) {
gameState.cells[row] = [];
for (let col = 0; col < gameState.width; col++) {
// 创建单元格对象
const cell = {
row,
col,
isMine: false,
isRevealed: false,
isFlagged: false,
adjacentMines: 0
};
gameState.cells[row][col] = cell;
// 创建单元格元素
const cellElement = document.createElement('div');
cellElement.classList.add('cell', 'cell-3d', 'flex', 'items-center', 'justify-center', 'rounded-lg', 'cursor-pointer', 'text-lg', 'font-bold');
cellElement.style.width = `${cellSize}px`;
cellElement.style.height = `${cellSize}px`;
cellElement.dataset.row = row;
cellElement.dataset.col = col;
// 添加点击事件
cellElement.addEventListener('click', () => handleCellClick(row, col));
cellElement.addEventListener('contextmenu', (e) => {
e.preventDefault();
handleCellRightClick(row, col);
});
// 添加鼠标按下和释放事件以更新笑脸图标
cellElement.addEventListener('mousedown', () => {
if (!gameState.isGameOver && !gameState.isGameWon) {
updateSmileyIcon('ooh');
}
});
document.addEventListener('mouseup', () => {
if (!gameState.isGameOver && !gameState.isGameWon) {
updateSmileyIcon('normal');
}
});
gameBoard.appendChild(cellElement);
}
}
// 放置地雷
placeMines();
// 计算相邻地雷数量
calculateAdjacentMines();
}
// 放置地雷
function placeMines() {
let minesPlaced = 0;
while (minesPlaced < gameState.mines) {
const row = Math.floor(Math.random() * gameState.height);
const col = Math.floor(Math.random() * gameState.width);
if (!gameState.cells[row][col].isMine) {
gameState.cells[row][col].isMine = true;
minesPlaced++;
}
}
}
// 计算相邻地雷数量
function calculateAdjacentMines() {
for (let row = 0; row < gameState.height; row++) {
for (let col = 0; col < gameState.width; col++) {
if (!gameState.cells[row][col].isMine) {
let count = 0;
// 检查周围8个单元格
for (let r = Math.max(0, row - 1); r <= Math.min(gameState.height - 1, row + 1); r++) {
for (let c = Math.max(0, col - 1); c <= Math.min(gameState.width - 1, col + 1); c++) {
if (gameState.cells[r][c].isMine) {
count++;
}
}
}
gameState.cells[row][col].adjacentMines = count;
}
}
}
}
// 处理单元格点击
function handleCellClick(row, col) {
const cell = gameState.cells[row][col];
// 如果游戏已结束、单元格已揭开或已标记,则不做任何操作
if (gameState.isGameOver || gameState.isGameWon || cell.isRevealed || cell.isFlagged) {
return;
}
// 如果是第一次点击,开始计时
if (!gameState.startTime) {
startTimer();
}
// 如果点击的是地雷,游戏结束
if (cell.isMine) {
revealMine(row, col);
gameOver(false);
return;
}
// 揭开单元格
revealCell(row, col);
// 检查游戏是否胜利
checkGameWin();
}
// 处理单元格右键点击
function handleCellRightClick(row, col) {
const cell = gameState.cells[row][col];
// 如果游戏已结束或单元格已揭开,则不做任何操作
if (gameState.isGameOver || gameState.isGameWon || cell.isRevealed) {
return;
}
// 如果是第一次点击,开始计时
if (!gameState.startTime) {
startTimer();
}
// 切换标记状态
cell.isFlagged = !cell.isFlagged;
// 更新游戏状态
if (cell.isFlagged) {
gameState.flaggedCells++;
} else {
gameState.flaggedCells--;
}
// 更新地雷计数器
updateMineCounter();
// 更新单元格显示
updateCellDisplay(row, col);
// 添加旗帜动画
const cellElement = getCellElement(row, col);
if (cell.isFlagged) {
cellElement.classList.add('flag-animation');
setTimeout(() => {
cellElement.classList.remove('flag-animation');
}, 500);
}
}
// 揭开单元格
function revealCell(row, col) {
const cell = gameState.cells[row][col];
// 如果单元格已揭开、已标记或是地雷,则不做任何操作
if (cell.isRevealed || cell.isFlagged || cell.isMine) {
return;
}
// 揭开单元格
cell.isRevealed = true;
gameState.revealedCells++;
// 更新单元格显示
updateCellDisplay(row, col);
// 如果相邻地雷数量为0,递归揭开周围单元格
if (cell.adjacentMines === 0) {
for (let r = Math.max(0, row - 1); r <= Math.min(gameState.height - 1, row + 1); r++) {
for (let c = Math.max(0, col - 1); c <= Math.min(gameState.width - 1, col + 1); c++) {
revealCell(r, c);
}
}
}
}
// 揭开地雷
function revealMine(row, col) {
const cell = gameState.cells[row][col];
// 揭开地雷
cell.isRevealed = true;
// 更新单元格显示
updateCellDisplay(row, col);
// 添加爆炸动画
const cellElement = getCellElement(row, col);
const explosion = document.createElement('div');
explosion.classList.add('mine-explosion');
// 计算爆炸位置
const rect = cellElement.getBoundingClientRect();
const boardRect = gameBoard.getBoundingClientRect();
explosion.style.left = `${rect.left - boardRect.left + rect.width / 2}px`;
explosion.style.top = `${rect.top - boardRect.top + rect.height / 2}px`;
gameBoard.appendChild(explosion);
// 移除爆炸动画
setTimeout(() => {
explosion.remove();
}, 500);
// 显示所有地雷
for (let r = 0; r < gameState.height; r++) {
for (let c = 0; c < gameState.width; c++) {
if (gameState.cells[r][c].isMine && !gameState.cells[r][c].isRevealed) {
gameState.cells[r][c].isRevealed = true;
updateCellDisplay(r, c);
}
}
}
}
// 更新单元格显示
function updateCellDisplay(row, col) {
const cell = gameState.cells[row][col];
const cellElement = getCellElement(row, col);
// 清空单元格内容
cellElement.innerHTML = '';
// 如果单元格已揭开
if (cell.isRevealed) {
cellElement.classList.add('revealed');
cellElement.classList.remove('cursor-pointer');
// 如果是地雷,显示地雷图标
if (cell.isMine) {
const img = document.createElement('img');
img.src = mineIcon;
img.alt = '地雷';
img.classList.add('w-6', 'h-6');
cellElement.appendChild(img);
}
// 如果有相邻地雷,显示数字
else if (cell.adjacentMines > 0) {
cellElement.textContent = cell.adjacentMines;
cellElement.classList.add(numberColors[cell.adjacentMines]);
}
}
// 如果单元格已标记,显示旗帜图标
else if (cell.isFlagged) {
const img = document.createElement('img');
img.src = flagIcon;
img.alt = '旗帜';
img.classList.add('w-6', 'h-6');
cellElement.appendChild(img);
}
}
// 获取单元格元素
function getCellElement(row, col) {
return document.querySelector(`.cell[data-row="${row}"][data-col="${col}"]`);
}
// 更新地雷计数器
function updateMineCounter() {
const remainingMines = gameState.mines - gameState.flaggedCells;
mineCounter.textContent = remainingMines < 0 ? 0 : remainingMines;
}
// 开始计时器
function startTimer() {
gameState.startTime = Date.now();
// 清除之前的计时器
if (gameState.timer) {
clearInterval(gameState.timer);
}
// 设置新的计时器
gameState.timer = setInterval(() => {
gameState.currentTime = Math.floor((Date.now() - gameState.startTime) / 1000);
updateTimerDisplay();
}, 1000);
}
// 重置计时器
function resetTimer() {
if (gameState.timer) {
clearInterval(gameState.timer);
gameState.timer = null;
}
gameState.currentTime = 0;
updateTimerDisplay();
}
// 更新计时器显示
function updateTimerDisplay() {
const minutes = Math.floor(gameState.currentTime / 60);
const seconds = gameState.currentTime % 60;
timerElement.textContent = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
}
// 更新笑脸图标
function updateSmileyIcon(state) {
let iconSrc;
switch (state) {
case 'normal':
iconSrc = smileyIcon;
break;
case 'ooh':
iconSrc = smileyOohIcon;
break;
case 'win':
iconSrc = smileyWinIcon;
break;
case 'lose':
iconSrc = smileyLoseIcon;
break;
default:
iconSrc = smileyIcon;
}
resetBtn.innerHTML = `<img src="${iconSrc}" alt="笑脸" class="w-6 h-6">`;
}
// 检查游戏是否胜利
function checkGameWin() {
// 计算需要揭开的非地雷单元格数量
const totalNonMineCells = gameState.width * gameState.height - gameState.mines;
// 如果已揭开所有非地雷单元格,游戏胜利
if (gameState.revealedCells === totalNonMineCells) {
gameOver(true);
}
}
// 游戏结束
function gameOver(isWin) {
gameState.isGameOver = true;
gameState.isGameWon = isWin;
// 停止计时器
if (gameState.timer) {
clearInterval(gameState.timer);
}
// 更新笑脸图标
updateSmileyIcon(isWin ? 'win' : 'lose');
// 显示游戏结果
showGameResult(isWin);
// 如果是胜利,添加庆祝效果
if (isWin) {
createConfetti();
// 检查是否是新记录
checkNewRecord();
}
}
// 显示游戏结果
function showGameResult(isWin) {
// 设置结果标题和消息
resultTitle.textContent = isWin ? '游戏胜利!' : '游戏失败!';
resultMessage.textContent = isWin ? '你成功找到了所有地雷!' : '你踩到地雷了!';
resultMessage.className = isWin ? 'text-green-600 dark:text-green-400 mb-6' : 'text-red-600 dark:text-red-400 mb-6';
// 设置结果时间
resultTime.textContent = timerElement.textContent;
// 显示游戏结果覆盖层
gameOverlay.classList.remove('hidden');
// 添加动画效果
setTimeout(() => {
gameResult.classList.remove('scale-95', 'opacity-0');
gameResult.classList.add('scale-100', 'opacity-100');
}, 10);
}
// 创建庆祝效果
function createConfetti() {
const colors = ['#f94144', '#f3722c', '#f8961e', '#f9c74f', '#90be6d', '#43aa8b', '#577590'];
const confettiContainer = document.createElement('div');
confettiContainer.classList.add('absolute', 'inset-0', 'pointer-events-none');
gameBoard.appendChild(confettiContainer);
// 创建100个彩色纸屑
for (let i = 0; i < 100; i++) {
const confetti = document.createElement('div');
confetti.classList.add('confetti');
confetti.style.left = `${Math.random() * 100}%`;
confetti.style.backgroundColor = colors[Math.floor(Math.random() * colors.length)];
confetti.style.animationDelay = `${Math.random() * 5}s`;
confettiContainer.appendChild(confetti);
}
// 5秒后移除庆祝效果
setTimeout(() => {
confettiContainer.remove();
}, 5000);
}
// 检查是否是新记录
function checkNewRecord() {
// 如果是自定义难度,不记录成绩
if (gameState.difficulty === 'custom') {
return;
}
// 获取排行榜数据
const leaderboard = getLeaderboard(gameState.difficulty);
// 如果成绩能进入排行榜前10名,或者排行榜未满10名
if (leaderboard.length < 10 || gameState.currentTime < leaderboard[leaderboard.length - 1].time) {
// 显示输入用户名模态框
showUsernameModal();
}
}
// 显示输入用户名模态框
function showUsernameModal() {
usernameModal.classList.remove('hidden');
usernameInput.focus();
}
// 保存用户名和成绩
function saveUserScore(username) {
// 如果用户名为空,使用默认名称
if (!username.trim()) {
username = '匿名玩家';
}
// 获取排行榜数据
const leaderboard = getLeaderboard(gameState.difficulty);
// 添加新成绩
leaderboard.push({
username: username.trim(),
time: gameState.currentTime,
date: new Date().toISOString()
});
// 按时间排序
leaderboard.sort((a, b) => a.time - b.time);
// 只保留前10名
if (leaderboard.length > 10) {
leaderboard.pop();
}
// 保存排行榜数据
localStorage.setItem(`minesweeper_leaderboard_${gameState.difficulty}`, JSON.stringify(leaderboard));
// 隐藏输入用户名模态框
usernameModal.classList.add('hidden');
// 显示排行榜
showLeaderboard(gameState.difficulty);
}
// 获取排行榜数据
function getLeaderboard(difficulty) {
const leaderboardData = localStorage.getItem(`minesweeper_leaderboard_${difficulty}`);
return leaderboardData ? JSON.parse(leaderboardData) : [];
}
// 显示排行榜
function showLeaderboard(difficulty) {
// 获取排行榜数据
const leaderboard = getLeaderboard(difficulty);
// 清空排行榜列表
leaderboardList.innerHTML = '';
// 如果排行榜为空,显示提示信息
if (leaderboard.length === 0) {
const emptyMessage = document.createElement('div');
emptyMessage.classList.add('p-4', 'text-center', 'text-slate-500', 'dark:text-slate-400');
emptyMessage.textContent = '暂无记录';
leaderboardList.appendChild(emptyMessage);
return;
}
// 添加排行榜项目
leaderboard.forEach((entry, index) => {
const item = document.createElement('div');
item.classList.add('leaderboard-item');
// 格式化时间
const minutes = Math.floor(entry.time / 60);
const seconds = entry.time % 60;
const formattedTime = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
// 格式化日期
const date = new Date(entry.date);
const formattedDate = `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`;
item.innerHTML = `
<span class="font-medium">${index + 1}</span>
<span class="truncate">${entry.username}</span>
<span class="font-mono">${formattedTime}</span>
<span class="text-xs text-slate-500 dark:text-slate-400">${formattedDate}</span>
`;
leaderboardList.appendChild(item);
});
// 显示排行榜模态框
leaderboardModal.classList.remove('hidden');
}
// 清空排行榜
function clearLeaderboardData(difficulty) {
if (confirm('确定要清空排行榜吗?')) {
localStorage.removeItem(`minesweeper_leaderboard_${difficulty}`);
showLeaderboard(difficulty);
}
}
// 初始化主题
function initTheme() {
// 检查用户偏好
if (localStorage.getItem('minesweeper_theme') === 'dark' ||
(!localStorage.getItem('minesweeper_theme') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
}
// 切换主题
function toggleTheme() {
if (document.documentElement.classList.contains('dark')) {
document.documentElement.classList.remove('dark');
localStorage.setItem('minesweeper_theme', 'light');
} else {
document.documentElement.classList.add('dark');
localStorage.setItem('minesweeper_theme', 'dark');
}
}
// 事件监听器
function setupEventListeners() {
// 重置按钮
resetBtn.addEventListener('click', initGame);
// 难度选择
difficultySelect.addEventListener('change', () => {
const difficulty = difficultySelect.value;
// 更新游戏状态
gameState.difficulty = difficulty;
// 如果是自定义难度,显示自定义设置
if (difficulty === 'custom') {
customSettings.classList.remove('hidden');
// 使用自定义设置
gameState.width = parseInt(customWidth.value);
gameState.height = parseInt(customHeight.value);
gameState.mines = parseInt(customMines.value);
} else {
customSettings.classList.add('hidden');
// 使用预设难度
const settings = difficultySettings[difficulty];
gameState.width = settings.width;
gameState.height = settings.height;
gameState.mines = settings.mines;
// 更新自定义设置滑块
customWidth.value = settings.width;
customWidthValue.textContent = settings.width;
customHeight.value = settings.height;
customHeightValue.textContent = settings.height;
customMines.value = settings.mines;
customMinesValue.textContent = settings.mines;
}
// 初始化游戏
initGame();
});
// 自定义宽度滑块
customWidth.addEventListener('input', () => {
const value = parseInt(customWidth.value);
customWidthValue.textContent = value;
// 更新游戏状态
gameState.width = value;
// 更新地雷数量限制
const maxMines = value * gameState.height - 1;
customMines.max = maxMines;
if (gameState.mines > maxMines) {
gameState.mines = maxMines;
customMines.value = maxMines;
customMinesValue.textContent = maxMines;
}
// 如果当前难度是自定义,重新初始化游戏
if (gameState.difficulty === 'custom') {
initGame();
}
});
// 自定义高度滑块
customHeight.addEventListener('input', () => {
const value = parseInt(customHeight.value);
customHeightValue.textContent = value;
// 更新游戏状态
gameState.height = value;
// 更新地雷数量限制
const maxMines = gameState.width * value - 1;
customMines.max = maxMines;
if (gameState.mines > maxMines) {
gameState.mines = maxMines;
customMines.value = maxMines;
customMinesValue.textContent = maxMines;
}
// 如果当前难度是自定义,重新初始化游戏
if (gameState.difficulty === 'custom') {
initGame();
}
});
// 自定义地雷数量滑块
customMines.addEventListener('input', () => {
const value = parseInt(customMines.value);
customMinesValue.textContent = value;
// 更新游戏状态
gameState.mines = value;
// 如果当前难度是自定义,重新初始化游戏
if (gameState.difficulty === 'custom') {
initGame();
}
});
// 主题切换
themeToggle.addEventListener('click', toggleTheme);
// 再玩一次按钮
playAgainBtn.addEventListener('click', initGame);
// 排行榜按钮
leaderboardBtn.addEventListener('click', () => {
showLeaderboard(gameState.difficulty);
});
// 关闭排行榜
closeLeaderboard.addEventListener('click', () => {
leaderboardModal.classList.add('hidden');
});
// 排行榜难度选择
leaderboardDifficulty.addEventListener('change', () => {
showLeaderboard(leaderboardDifficulty.value);
});
// 清空排行榜
clearLeaderboard.addEventListener('click', () => {
clearLeaderboardData(leaderboardDifficulty.value);
});
// 保存用户名
saveUsername.addEventListener('click', () => {
saveUserScore(usernameInput.value);
});
// 用户名输入框回车事件
usernameInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
saveUserScore(usernameInput.value);
}
});
// 点击模态框背景关闭模态框
leaderboardModal.addEventListener('click', (e) => {
if (e.target === leaderboardModal) {
leaderboardModal.classList.add('hidden');
}
});
usernameModal.addEventListener('click', (e) => {
if (e.target === usernameModal) {
usernameModal.classList.add('hidden');
}
});
// 窗口大小改变时重新调整游戏板大小
window.addEventListener('resize', () => {
if (!gameState.isGameOver && !gameState.isGameWon) {
createGameBoard();
}
});
}
// 初始化游戏
function init() {
// 初始化主题
initTheme();
// 设置事件监听器
setupEventListeners();
// 初始化游戏
initGame();
}
// 启动游戏
init();
</script>
</body>
</html>
展示画面





