javascript,jquery实现的扫雷小游戏,可以下载到本地直接双击index.html即可运行
游戏展示
开始
失败
代码展示
html文件
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>扫雷游戏</title>
<link rel="stylesheet" href="style.css">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<div class="container">
<div class="header">
<h1>扫雷游戏</h1>
<div class="controls">
<button id="newGame">新游戏</button>
<span id="mineCount">剩余地雷: 10</span>
<span id="timer">时间: 0</span>
</div>
<div class="instructions">
<h3>游戏说明:</h3>
<p>1. 左键点击格子揭开方块</p>
<p>2. 右键点击标记地雷位置(最多标记10个)</p>
<p>3. 找出所有非地雷位置即可获胜</p>
<p>4. 点击地雷则游戏结束</p>
</div>
</div>
<div id="gameBoard"></div>
</div>
<div id="gameOverModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<img src="data:image/svg+xml;base64,PHN2ZyB0PSIxNzA5MTk4NTI3NDU3IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjI3ODEiIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCI+PHBhdGggZD0iTTUxMiA2NEMyNjQuNiA2NCA2NCAyNjQuNiA2NCA1MTJzMjAwLjYgNDQ4IDQ0OCA0NDggNDQ4LTIwMC42IDQ0OC00NDhTNzU5LjQgNjQgNTEyIDY0eiBtMCA4MjBjLTIwNS40IDAtMzcyLTE2Ni42LTM3Mi0zNzJzMTY2LjYtMzcyIDM3Mi0zNzIgMzcyIDE2Ni42IDM3MiAzNzJ6IiBmaWxsPSIjZTc0YzNjIiBwLWlkPSIyNzgyIj48L3BhdGg+PHBhdGggZD0iTTUxMiAxNDBjLTIwNS40IDAtMzcyIDE2Ni42LTM3MiAzNzJzMTY2LjYgMzcyIDM3MiAzNzIgMzcyLTE2Ni42IDM3Mi0zNzItMTY2LjYtMzcyLTM3Mi0zNzJ6TTQ2NCA2ODhjLTQuNCAwLTguOC0yLTExLjYtNS45TDM0OCA1NjQuOGMtNS00LjEtNS45LTExLjQtMi4xLTE2LjQgMy44LTUgMTAuOS02IDE1LjktMi4ybDk2LjEgNzguNSAyMDIuMy0yNzEuOGM0LjEtNS41IDExLjktNi42IDE3LjQtMi41IDUuNSA0LjEgNi42IDExLjkgMi41IDE3LjRMNDc3LjggNjgxLjRjLTIuNiAzLjUtNi43IDUuNi0xMS4xIDYuMi0wLjkgMC4zLTEuOCAwLjQtMi43IDAuNHoiIGZpbGw9IiNlNzRjM2MiIHAtaWQ9IjI3ODMiPjwvcGF0aD48L3N2Zz4=" class="modal-icon" />
<h2 id="modalTitle">游戏结束</h2>
</div>
<div class="modal-body">
<p id="modalMessage">很遗憾,你踩到了地雷!</p>
<div class="game-stats">
<div class="stat-item">
<span class="stat-label">用时</span>
<span id="finalTime" class="stat-value">0秒</span>
</div>
<div class="stat-item">
<span class="stat-label">已排除</span>
<span id="clearedCells" class="stat-value">0格</span>
</div>
</div>
</div>
<div class="modal-footer">
<button id="newGameBtn" class="btn primary">重新开始</button>
<button id="continueBtn" class="btn secondary">继续查看</button>
</div>
</div>
</div>
<script src="game.js"></script>
</body>
</html>
css文件
body {
background: #f0f2f5;
font-family: 'Microsoft YaHei', sans-serif;
}
.container {
max-width: 600px;
margin: 20px auto;
padding: 20px;
background: white;
border-radius: 10px;
box-shadow: 0 0 20px rgba(0,0,0,0.1);
}
.header {
text-align: center;
margin-bottom: 20px;
}
.header h1 {
color: #2c3e50;
margin-bottom: 20px;
}
.controls {
margin: 20px 0;
display: flex;
justify-content: center;
gap: 20px;
}
#newGame {
padding: 8px 20px;
background: #3498db;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background 0.3s;
}
#newGame:hover {
background: #2980b9;
}
#mineCount, #timer {
background: linear-gradient(145deg, #2c3e50, #34495e);
color: white;
padding: 8px 15px;
border-radius: 5px;
min-width: 120px;
display: inline-block;
font-weight: bold;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
transition: all 0.3s ease;
}
#gameBoard {
display: grid;
grid-template-columns: repeat(9, 40px);
gap: 2px;
background: #bdc3c7;
border: 2px solid #95a5a6;
border-radius: 5px;
width: fit-content;
margin: 0 auto;
padding: 2px;
}
.cell {
width: 40px;
height: 40px;
background: #ecf0f1;
display: flex;
align-items: center;
justify-content: center;
cursor: default;
font-weight: bold;
border-radius: 3px;
transition: all 0.2s;
font-size: 18px;
user-select: none;
}
.cell.unrevealed {
background: linear-gradient(145deg, #e6e6e6, #ffffff);
cursor: pointer;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.cell.unrevealed:hover {
background: #d5d8dc;
transform: scale(0.95);
}
.cell.revealed {
background: #ecf0f1;
box-shadow: inset 0 1px 3px rgba(0,0,0,0.1);
}
.cell.mine {
background: #e74c3c;
animation: explosion 0.5s ease-out;
}
.cell.flagged {
background: #f1c40f;
}
.instructions {
text-align: left;
margin: 20px 0;
padding: 15px;
background: #f8f9fa;
border-radius: 8px;
border-left: 4px solid #3498db;
}
.instructions h3 {
color: #2c3e50;
margin-top: 0;
}
.instructions p {
color: #34495e;
margin: 8px 0;
}
/* 数字颜色 */
.cell[data-number="1"] { color: #3498db; }
.cell[data-number="2"] { color: #27ae60; }
.cell[data-number="3"] { color: #e74c3c; }
.cell[data-number="4"] { color: #8e44ad; }
.cell[data-number="5"] { color: #c0392b; }
.cell[data-number="6"] { color: #16a085; }
.cell[data-number="7"] { color: #2c3e50; }
.cell[data-number="8"] { color: #7f8c8d; }
/* 模态框样式 */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
}
.modal-content {
position: relative;
background-color: #fff;
margin: 15% auto;
padding: 20px;
width: 90%;
max-width: 400px;
border-radius: 12px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
}
.modal-header {
text-align: center;
margin-bottom: 20px;
}
.modal-icon {
width: 48px;
height: 48px;
margin-bottom: 10px;
}
.modal-header h2 {
margin: 0;
color: #2c3e50;
font-size: 24px;
}
.modal-body {
text-align: center;
margin-bottom: 20px;
}
.modal-body p {
color: #34495e;
font-size: 16px;
margin-bottom: 20px;
}
.game-stats {
display: flex;
justify-content: center;
gap: 30px;
margin: 20px 0;
}
.stat-item {
display: flex;
flex-direction: column;
align-items: center;
}
.stat-label {
color: #7f8c8d;
font-size: 14px;
margin-bottom: 5px;
}
.stat-value {
color: #2c3e50;
font-size: 20px;
font-weight: bold;
}
.modal-footer {
display: flex;
justify-content: center;
gap: 15px;
margin-top: 20px;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 6px;
font-size: 16px;
cursor: pointer;
transition: all 0.3s;
margin: 0 10px;
}
.btn.primary {
background-color: #3498db;
color: white;
}
.btn.primary:hover {
background-color: #2980b9;
transform: translateY(-2px);
}
.btn.secondary {
background-color: #ecf0f1;
color: #34495e;
}
.btn.secondary:hover {
background-color: #bdc3c7;
transform: translateY(-2px);
}
/* 动画 */
@keyframes explosion {
0% { transform: scale(1); opacity: 1; }
20% { transform: scale(1.2); background: #e74c3c; }
40% { transform: scale(0.9); background: #c0392b; }
60% { transform: scale(1.1); background: #e74c3c; }
80% { transform: scale(0.95); background: #c0392b; }
100% { transform: scale(1); background: #e74c3c; }
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes slideIn {
from { transform: translateY(-20px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
/* 提示消息样式 */
.tip-message {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
background-color: rgba(44, 62, 80, 0.95);
color: white;
padding: 12px 24px;
border-radius: 25px;
font-size: 14px;
z-index: 1000;
opacity: 0;
transition: opacity 0.3s ease;
box-shadow: 0 3px 10px rgba(0,0,0,0.2);
}
.tip-message.animate {
opacity: 1;
animation: tipBounce 0.5s ease-out;
}
.tip-message.fade-out {
opacity: 0;
}
@keyframes tipBounce {
0% { transform: translate(-50%, -20px); }
50% { transform: translate(-50%, 5px); }
100% { transform: translate(-50%, 0); }
}
/* 胜利样式 */
.modal-content.victory {
background: linear-gradient(135deg, #43cea2 0%, #185a9d 100%);
}
.modal-content.victory .modal-header h2,
.modal-content.victory .modal-body p,
.modal-content.victory .stat-label,
.modal-content.victory .stat-value {
color: white;
}
js文件
$(document).ready(function() {
const BOARD_SIZE = 9;
const MINE_COUNT = 10;
let board = [];
let revealed = [];
let flagged = [];
let gameOver = false;
let timer = 0;
let timerInterval;
let firstClick = true;
function bindEvents() {
$('#gameBoard').off('click contextmenu');
$('#gameBoard').on('click', '.cell', function(e) {
if(gameOver) return;
const x = parseInt($(this).attr('data-x'));
const y = parseInt($(this).attr('data-y'));
console.log(`Clicking cell at (${x}, ${y})`);
if(isNaN(x) || isNaN(y)) {
console.log('Invalid coordinates');
return;
}
if(firstClick) {
ensureSafeFirstClick(x, y);
firstClick = false;
startTimer();
}
revealCell(x, y);
});
$('#gameBoard').on('contextmenu', '.cell', function(e) {
e.preventDefault();
if(gameOver) return;
const x = parseInt($(this).attr('data-x'));
const y = parseInt($(this).attr('data-y'));
console.log(`Right clicking cell at (${x}, ${y})`);
if(isNaN(x) || isNaN(y)) {
console.log('Invalid coordinates');
return;
}
toggleFlag(x, y);
});
}
function ensureSafeFirstClick(clickX, clickY) {
if(board[clickX][clickY] === -1) {
let found = false;
for(let i = 0; i < BOARD_SIZE && !found; i++) {
for(let j = 0; j < BOARD_SIZE && !found; j++) {
if(board[i][j] !== -1) {
board[clickX][clickY] = 0;
board[i][j] = -1;
found = true;
}
}
}
recalculateNumbers();
}
}
function recalculateNumbers() {
for(let i = 0; i < BOARD_SIZE; i++) {
for(let j = 0; j < BOARD_SIZE; j++) {
if(board[i][j] !== -1) {
board[i][j] = countAdjacentMines(i, j);
}
}
}
}
function revealCell(x, y) {
console.log(`Attempting to reveal cell at (${x}, ${y})`);
if(x < 0 || x >= BOARD_SIZE || y < 0 || y >= BOARD_SIZE ||
gameOver || revealed[x][y] || flagged[x][y]) {
console.log('Invalid reveal attempt');
return;
}
revealed[x][y] = true;
console.log(`Cell value at (${x}, ${y}): ${board[x][y]}`);
if(board[x][y] === -1) {
gameOver = true;
createExplosionEffect(x, y);
setTimeout(() => {
revealAllMines();
showGameOverModal(false);
}, 1000);
clearInterval(timerInterval);
return;
}
if(board[x][y] === 0) {
for(let i = -1; i <= 1; i++) {
for(let j = -1; j <= 1; j++) {
const newX = x + i;
const newY = y + j;
if(newX >= 0 && newX < BOARD_SIZE &&
newY >= 0 && newY < BOARD_SIZE &&
!revealed[newX][newY]) {
revealCell(newX, newY);
}
}
}
}
renderBoard();
checkWin();
}
function initGame() {
board = [];
revealed = [];
flagged = [];
gameOver = false;
timer = 0;
firstClick = true;
clearInterval(timerInterval);
$('#timer').text('时间: 0');
$('#mineCount').text(`剩余地雷: ${MINE_COUNT}`);
for(let i = 0; i < BOARD_SIZE; i++) {
board[i] = new Array(BOARD_SIZE).fill(0);
revealed[i] = new Array(BOARD_SIZE).fill(false);
flagged[i] = new Array(BOARD_SIZE).fill(false);
}
let minesPlaced = 0;
while(minesPlaced < MINE_COUNT) {
const x = Math.floor(Math.random() * BOARD_SIZE);
const y = Math.floor(Math.random() * BOARD_SIZE);
if(board[x][y] !== -1) {
board[x][y] = -1;
minesPlaced++;
}
}
recalculateNumbers();
renderBoard();
bindEvents();
}
function countAdjacentMines(x, y) {
let count = 0;
for(let i = -1; i <= 1; i++) {
for(let j = -1; j <= 1; j++) {
const newX = x + i;
const newY = y + j;
if(newX >= 0 && newX < BOARD_SIZE &&
newY >= 0 && newY < BOARD_SIZE &&
board[newX][newY] === -1) {
count++;
}
}
}
return count;
}
function renderBoard() {
$('#gameBoard').empty();
for(let i = 0; i < BOARD_SIZE; i++) {
for(let j = 0; j < BOARD_SIZE; j++) {
const cell = $('<div>')
.addClass('cell')
.attr('data-x', i)
.attr('data-y', j);
if(revealed[i][j]) {
cell.addClass('revealed');
if(board[i][j] === -1) {
cell.addClass('mine').text('💣');
} else if(board[i][j] > 0) {
cell.text(board[i][j])
.attr('data-number', board[i][j]);
}
} else if(flagged[i][j]) {
cell.addClass('flagged').text('🚩');
}
$('#gameBoard').append(cell);
}
}
}
function toggleFlag(x, y) {
if(gameOver || revealed[x][y]) return;
const currentFlagged = flagged.flat().filter(f => f).length;
if(!flagged[x][y] && currentFlagged >= MINE_COUNT) {
showTipMessage('最多只能标记10个地雷!');
return;
}
flagged[x][y] = !flagged[x][y];
const remainingMines = MINE_COUNT - flagged.flat().filter(f => f).length;
$('#mineCount').text(`剩余地雷: ${remainingMines}`);
renderBoard();
}
function revealAllMines() {
for(let i = 0; i < BOARD_SIZE; i++) {
for(let j = 0; j < BOARD_SIZE; j++) {
if(board[i][j] === -1) {
revealed[i][j] = true;
}
}
}
renderBoard();
}
function checkWin() {
let win = true;
for(let i = 0; i < BOARD_SIZE; i++) {
for(let j = 0; j < BOARD_SIZE; j++) {
if(board[i][j] !== -1 && !revealed[i][j]) {
win = false;
break;
}
}
}
if(win) {
gameOver = true;
clearInterval(timerInterval);
showGameOverModal(true);
}
}
function startTimer() {
timer = 0;
timerInterval = setInterval(() => {
timer++;
$('#timer').text(`时间: ${timer}`);
}, 1000);
}
function createExplosionEffect(x, y) {
const cell = $(`.cell[data-x="${x}"][data-y="${y}"]`);
if (!cell.length) return;
cell.addClass('mine');
const cellRect = cell[0].getBoundingClientRect();
const centerX = cellRect.left + cellRect.width / 2;
const centerY = cellRect.top + cellRect.height / 2;
for (let i = 0; i < 20; i++) {
createExplosionParticle(centerX, centerY);
}
$('#gameBoard').css('animation', 'shake 0.5s ease-out');
}
function createExplosionParticle(centerX, centerY) {
const particle = $('<div>').addClass('explosion-particle');
const angle = Math.random() * Math.PI * 2;
const velocity = 100 + Math.random() * 50;
const tx = Math.cos(angle) * velocity;
const ty = Math.sin(angle) * velocity;
particle.css({
left: centerX + 'px',
top: centerY + 'px',
'--tx': tx + 'px',
'--ty': ty + 'px'
});
$('body').append(particle);
setTimeout(() => particle.remove(), 800);
}
function showGameOverModal(isVictory) {
const modal = $('#gameOverModal');
const modalContent = modal.find('.modal-content');
const modalTitle = $('#modalTitle');
const modalMessage = $('#modalMessage');
const finalTime = $('#finalTime');
const clearedCells = $('#clearedCells');
if (isVictory) {
modalContent.addClass('victory');
modalTitle.text('恭喜胜利!');
modalMessage.text('太棒了!你成功找出了所有地雷!');
} else {
modalContent.removeClass('victory');
modalTitle.text('游戏结束');
modalMessage.text('很遗憾,你踩到了地雷!');
}
finalTime.text(timer + '秒');
const revealedCount = revealed.flat().filter(r => r).length;
clearedCells.text(revealedCount + '格');
modal.fadeIn(300);
bindModalEvents();
}
function showTipMessage(message) {
$('.tip-message').remove();
const tip = $('<div>')
.addClass('tip-message')
.text(message)
.appendTo('body');
tip.addClass('animate');
setTimeout(() => {
tip.removeClass('animate').addClass('fade-out');
setTimeout(() => tip.remove(), 300);
}, 3000);
}
function bindModalEvents() {
$('#newGameBtn').off('click').on('click', function() {
console.log('New game button clicked');
$('#gameOverModal').fadeOut(300, function() {
initGame();
});
});
$('#continueBtn').off('click').on('click', function() {
console.log('Continue button clicked');
$('#gameOverModal').fadeOut(300);
});
$('#newGame').off('click').on('click', function() {
console.log('Header new game button clicked');
$('#gameOverModal').fadeOut(300);
initGame();
});
}
initGame();
bindModalEvents();
function printBoardState() {
console.log('Current board state:');
for(let i = 0; i < BOARD_SIZE; i++) {
let row = '';
for(let j = 0; j < BOARD_SIZE; j++) {
if(revealed[i][j]) {
row += board[i][j] === -1 ? ' * ' : ` ${board[i][j]} `;
} else if(flagged[i][j]) {
row += ' F ';
} else {
row += ' - ';
}
}
console.log(row);
}
}
});
总结
下载本地直接双击index.html文件运行即可体验游戏效果。