扫雷游戏网页版

yangguang 发布于 2025-02-02 117 次阅读


今日闲来无事,自己随便做了个扫雷游戏网页版(点击此处体验),以下是源代码,感兴趣的也可以一起做一下。

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();

若有什么疑问或需要改进的地方欢迎在评论区交流❤️

此作者没有提供个人介绍
最后更新于 2025-02-02