html5:从零构建经典游戏-扫雷游戏

扫雷是Windows系统自带的经典游戏,陪伴了许多人的童年。

本文将详细解析一个用HTML、CSS和JavaScript实现的扫雷游戏代码,带你了解其背后的实现原理。

游戏概述

这个扫雷游戏实现包含以下核心功能:

  • 10×10的游戏棋盘

  • 15个随机分布的地雷

  • 左键点击揭开格子

  • 右键点击标记地雷

  • 自动展开空白区域

  • 胜利/失败判定

HTML结构

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>扫雷游戏</title>
    <!-- CSS样式 -->
</head>
<body>
    <h1>扫雷游戏</h1>
    <div id="game-board"></div>
    <!-- JavaScript代码 -->
</body>
</html>

结构非常简单,只有一个标题和游戏板容器。所有游戏逻辑都通过JavaScript动态生成。

CSS样式设计

CSS部分定义了游戏的外观:

html 复制代码
.cell {
    width: 30px;
    height: 30px;
    border: 1px solid #ccc;
    /* 其他样式 */
}
.cell.revealed {
    background-color: #fff;
}
.cell.mine {
    background-color: #ff4444;
}
.cell.flagged {
    background-color: #ffcc00;
}
  • 基本格子样式:灰色背景,浅灰色边框

  • 已揭开格子:白色背景

  • 地雷格子:红色背景

  • 标记格子:黄色背景

JavaScript游戏逻辑

1. 游戏初始化

javascript

html 复制代码
const BOARD_SIZE = 10;
const MINE_COUNT = 15;
let board = [];
let gameOver = false;

定义了游戏常量:棋盘大小和地雷数量,以及游戏状态变量。

2. 创建游戏板

createBoard()函数负责初始化游戏:

  1. 设置CSS Grid布局

  2. 创建格子DOM元素并添加到游戏板

  3. 初始化游戏数据结构

  4. 放置地雷并计算数字

javascript

html 复制代码
function createBoard() {
    // 设置Grid布局
    gameBoard.style.gridTemplateColumns = `repeat(${BOARD_SIZE}, 30px)`;
    
    // 创建格子
    for (let row = 0; row < BOARD_SIZE; row++) {
        board[row] = [];
        for (let col = 0; col < BOARD_SIZE; col++) {
            // 创建DOM元素
            const cell = document.createElement('div');
            // 添加事件监听
            cell.addEventListener('click', handleCellClick);
            cell.addEventListener('contextmenu', handleRightClick);
            // 初始化游戏数据
            board[row][col] = { 
                mine: false, 
                revealed: false, 
                flagged: false, 
                count: 0 
            };
        }
    }
    placeMines();
    calculateNumbers();
}

3. 随机放置地雷

placeMines()函数随机放置指定数量的地雷:

javascript

html 复制代码
function placeMines() {
    let minesPlaced = 0;
    while (minesPlaced < MINE_COUNT) {
        const row = Math.floor(Math.random() * BOARD_SIZE);
        const col = Math.floor(Math.random() * BOARD_SIZE);
        if (!board[row][col].mine) {
            board[row][col].mine = true;
            minesPlaced++;
        }
    }
}

使用while循环确保放置足够数量的地雷,避免重复放置。

4. 计算周围地雷数

calculateNumbers()为每个非地雷格子计算周围地雷数量:

javascript

html 复制代码
function calculateNumbers() {
    for (let row = 0; row < BOARD_SIZE; row++) {
        for (let col = 0; col < BOARD_SIZE; col++) {
            if (board[row][col].mine) continue;
            
            let count = 0;
            // 检查周围8个格子
            for (let i = -1; i <= 1; i++) {
                for (let j = -1; j <= 1; j++) {
                    const newRow = row + i;
                    const newCol = col + j;
                    // 边界检查
                    if (newRow >= 0 && newRow < BOARD_SIZE && 
                        newCol >= 0 && newCol < BOARD_SIZE && 
                        board[newRow][newCol].mine) {
                        count++;
                    }
                }
            }
            board[row][col].count = count;
        }
    }
}

5. 处理玩家点击

左键点击处理函数handleCellClick

javascript

html 复制代码
function handleCellClick(event) {
    if (gameOver) return;
    const cell = event.target;
    const row = parseInt(cell.dataset.row);
    const col = parseInt(cell.dataset.col);
    revealCell(row, col);
}

右键点击处理函数handleRightClick用于标记地雷:

javascript

html 复制代码
function handleRightClick(event) {
    event.preventDefault(); // 阻止上下文菜单
    if (gameOver) return;
    
    const cell = event.target;
    const row = parseInt(cell.dataset.row);
    const col = parseInt(cell.dataset.col);
    
    if (!board[row][col].revealed) {
        board[row][col].flagged = !board[row][col].flagged;
        cell.classList.toggle('flagged', board[row][col].flagged);
    }
}

6. 揭开格子逻辑

revealCell()是游戏核心函数,处理格子揭开逻辑:

javascript

html 复制代码
function revealCell(row, col) {
    // 已揭开或已标记则返回
    if (board[row][col].revealed || board[row][col].flagged) return;
    
    const cell = document.querySelector(`.cell[data-row='${row}'][data-col='${col}']`);
    board[row][col].revealed = true;
    cell.classList.add('revealed');
    
    // 踩到地雷
    if (board[row][col].mine) {
        cell.classList.add('mine');
        gameOver = true;
        alert('游戏结束!你踩到了地雷!');
        return;
    }
    
    // 显示周围地雷数
    cell.textContent = board[row][col].count || '';
    
    // 如果是空白格子,自动展开周围
    if (board[row][col].count === 0) {
        for (let i = -1; i <= 1; i++) {
            for (let j = -1; j <= 1; j++) {
                const newRow = row + i;
                const newCol = col + j;
                // 边界检查
                if (newRow >= 0 && newRow < BOARD_SIZE && 
                    newCol >= 0 && newCol < BOARD_SIZE) {
                    revealCell(newRow, newCol);
                }
            }
        }
    }
    
    checkWin();
}

7. 胜利判定

checkWin()检查玩家是否已揭开所有安全格子:

javascript

html 复制代码
function checkWin() {
    let unrevealedSafeCells = 0;
    for (let row = 0; row < BOARD_SIZE; row++) {
        for (let col = 0; col < BOARD_SIZE; col++) {
            if (!board[row][col].mine && !board[row][col].revealed) {
                unrevealedSafeCells++;
            }
        }
    }
    if (unrevealedSafeCells === 0) {
        gameOver = true;
        alert('恭喜你,你赢了!');
    }
}

游戏特点

  1. 递归展开:当点击空白格子时,会自动展开相邻的所有空白格子

  2. 即时反馈:点击地雷立即结束游戏,显示失败

  3. 标记功能:右键点击可以标记疑似地雷的格子

  4. 响应式设计:使用CSS Grid布局,适应不同屏幕大小

总结

这个扫雷游戏实现展示了如何使用基本的Web技术创建经典游戏。

通过合理的数据结构和算法设计,实现了游戏的核心逻辑。代码结构清晰,适合初学者学习和扩展。

html代码

如下:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>扫雷游戏</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            text-align: center;
        }
        #game-board {
            display: grid;
            margin: 20px auto;
            border: 2px solid #333;
        }
        .cell {
            width: 30px;
            height: 30px;
            border: 1px solid #ccc;
            text-align: center;
            line-height: 30px;
            cursor: pointer;
            background-color: #eee;
        }
        .cell.revealed {
            background-color: #fff;
            cursor: default;
        }
        .cell.mine {
            background-color: #ff4444;
        }
        .cell.flagged {
            background-color: #ffcc00;
        }
    </style>
</head>
<body>
    <h1>扫雷游戏</h1>
    <div id="game-board"></div>
    <script>
        const BOARD_SIZE = 10;
        const MINE_COUNT = 15;
        let board = [];
        let gameOver = false;

        function createBoard() {
            const gameBoard = document.getElementById('game-board');
            gameBoard.style.gridTemplateColumns = `repeat(${BOARD_SIZE}, 30px)`;
            
            for (let row = 0; row < BOARD_SIZE; row++) {
                board[row] = [];
                for (let col = 0; col < BOARD_SIZE; col++) {
                    const cell = document.createElement('div');
                    cell.classList.add('cell');
                    cell.dataset.row = row;
                    cell.dataset.col = col;
                    cell.addEventListener('click', handleCellClick);
                    cell.addEventListener('contextmenu', handleRightClick);
                    gameBoard.appendChild(cell);
                    board[row][col] = { mine: false, revealed: false, flagged: false, count: 0 };
                }
            }
            placeMines();
            calculateNumbers();
        }

        function placeMines() {
            let minesPlaced = 0;
            while (minesPlaced < MINE_COUNT) {
                const row = Math.floor(Math.random() * BOARD_SIZE);
                const col = Math.floor(Math.random() * BOARD_SIZE);
                if (!board[row][col].mine) {
                    board[row][col].mine = true;
                    minesPlaced++;
                }
            }
        }

        function calculateNumbers() {
            for (let row = 0; row < BOARD_SIZE; row++) {
                for (let col = 0; col < BOARD_SIZE; col++) {
                    if (board[row][col].mine) continue;
                    let count = 0;
                    for (let i = -1; i <= 1; i++) {
                        for (let j = -1; j <= 1; j++) {
                            const newRow = row + i;
                            const newCol = col + j;
                            if (newRow >= 0 && newRow < BOARD_SIZE && newCol >= 0 && newCol < BOARD_SIZE && board[newRow][newCol].mine) {
                                count++;
                            }
                        }
                    }
                    board[row][col].count = count;
                }
            }
        }

        function handleCellClick(event) {
            if (gameOver) return;
            const cell = event.target;
            const row = parseInt(cell.dataset.row);
            const col = parseInt(cell.dataset.col);
            revealCell(row, col);
        }

        function handleRightClick(event) {
            event.preventDefault();
            if (gameOver) return;
            const cell = event.target;
            const row = parseInt(cell.dataset.row);
            const col = parseInt(cell.dataset.col);
            if (!board[row][col].revealed) {
                board[row][col].flagged = !board[row][col].flagged;
                cell.classList.toggle('flagged', board[row][col].flagged);
            }
        }

        function revealCell(row, col) {
            if (board[row][col].revealed || board[row][col].flagged) return;
            const cell = document.querySelector(`.cell[data-row='${row}'][data-col='${col}']`);
            board[row][col].revealed = true;
            cell.classList.add('revealed');
            if (board[row][col].mine) {
                cell.classList.add('mine');
                gameOver = true;
                alert('游戏结束!你踩到了地雷!');
                return;
            }
            cell.textContent = board[row][col].count || '';
            if (board[row][col].count === 0) {
                for (let i = -1; i <= 1; i++) {
                    for (let j = -1; j <= 1; j++) {
                        const newRow = row + i;
                        const newCol = col + j;
                        if (newRow >= 0 && newRow < BOARD_SIZE && newCol >= 0 && newCol < BOARD_SIZE) {
                            revealCell(newRow, newCol);
                        }
                    }
                }
            }
            checkWin();
        }

        function checkWin() {
            let unrevealedSafeCells = 0;
            for (let row = 0; row < BOARD_SIZE; row++) {
                for (let col = 0; col < BOARD_SIZE; col++) {
                    if (!board[row][col].mine && !board[row][col].revealed) {
                        unrevealedSafeCells++;
                    }
                }
            }
            if (unrevealedSafeCells === 0) {
                gameOver = true;
                alert('恭喜你,你赢了!');
            }
        }

        createBoard();
    </script>
</body>
</html>
相关推荐
夕水9 分钟前
这个提升效率宝藏级工具一定要收藏使用
前端·javascript·trae
会飞的鱼先生23 分钟前
vue3 内置组件KeepAlive的使用
前端·javascript·vue.js
斯~内克37 分钟前
前端浏览器窗口交互完全指南:从基础操作到高级控制
前端
Mike_jia1 小时前
Memos:知识工作者的理想开源笔记系统
前端
前端大白话1 小时前
前端崩溃瞬间救星!10 个 JavaScript 实战技巧大揭秘
前端·javascript
loveoobaby1 小时前
Shadertoy着色器移植到Three.js经验总结
前端
蓝易云1 小时前
在Linux、CentOS7中设置shell脚本开机自启动服务
前端·后端·centos
浩龙不eMo1 小时前
前端获取环境变量方式区分(Vite)
前端·vite
土豆骑士1 小时前
monorepo 实战练习
前端
土豆骑士2 小时前
monorepo最佳实践
前端