前端技术搭建扫雷小游戏(内含源码)

2023-11-17


✨ 写在前面

上周我们实通过前端基础实现了贪吃蛇游戏,今天还是继续按照我们原定的节奏来带领大家完成一个游戏,功能也比较简单简单,也是想借助这样一个简单的功能,然后来帮助大家了解我们JavaScript在前端中的作用, 在前面的文章当中我们也提及到我们在本系列的专栏是循序渐进从简单到复杂的过程,后续会带领大家用前端实现翻卡片、扫雷等有趣的小游戏,纯前端语言实现,都会陆续带给大家。欢迎大家订阅我们这份前端小游戏的专栏。订阅链接:https://blog.csdn.net/jhxl_/category_12261013.html


✨ 功能介绍

扫雷是一款经典的单人益智游戏,目标是在雷区中揭开所有非雷方块而避免触雷。以下是游戏的玩法说明和规则:游戏开始时,你将面对一个方块组成的地图,其中包含隐藏的雷方块和非雷方块。你需要逐个点击方块来揭开它们。如果揭开的方块是雷方块,你失败并游戏结束。揭开的方块可能是数字方块,它会显示周围八个方块中雷的数量。根据这些数字,你可以推断其他方块的状态。如果揭开的方块是空白方块,它将自动展开,揭开相邻的空白方块,直到边界或有数字的方块。通过观察数字方块周围的雷的数量,你可以推断出潜在的雷区,并使用右键进行标记,帮助记忆和避免揭开雷方块。如果你成功揭开所有非雷方块,而没有触雷,你将获得胜利。

在这里插入图片描述

在扫雷游戏中,推理、记忆和谨慎是取得胜利的关键。通过观察和推断雷的位置,并运用合适的标记策略,你可以在雷区中探索出更多安全的方块,并最终解开所有的谜题。记住要保持冷静,小心行事,以在这个挑战性的益智游戏中获得成功!


✨ 页面搭建

创建文件

首先呢我们创建我们的HTML文件,这里我就直接命名为 扫雷.html 了,大家可以随意命名, 文件创建生成后我们通过编辑器打开,这里我用的是VScode, 然后初始化我们的代码结构,那在这里告诉大家一个快捷键,就是我们敲上我们英文的一个 ! 我们敲击回车直接就会给我们生成基础版本的前端代码结构。

在这里插入图片描述

文档声明和编码设置: 在HTML文档的头部,使用<!DOCTYPE>声明HTML文档类型,确保浏览器以正确的方式渲染网页内容。同时,设置UTF-8编码,以确保浏览器能够正确地解析和显示中文字符。下面我就开始搭建我们的DOM结构了!

DOM结构搭建

这段HTML代码是一个简单的扫雷游戏界面的布局。让我为你解释一下每个部分的作用: <div class="bigBox">:这是一个包含整个游戏内容的大容器,它用来将游戏界面的各个元素进行组合和布局。 <div id="controls">:这个 <div> 元素包含了游戏的控制面板,用于设置游戏的难度级别和重新开始游戏。<form>:这是一个表单元素,用于包裹控制面板中的各个控件。 <label for="level">难度级别:</label>:这是一个用于显示文本的 <label> 元素,它与下面的下拉菜单(<select>)建立了关联,通过 for 属性指定了关联的控件的 id<select id="level">:这是一个下拉菜单(选择框)控件,用于选择游戏的难度级别。它有三个选项:简单、中等和困难。 <button id="reset">重新开始</button>:这是一个按钮控件,用于重新开始游戏。 <table id="board"></table>:这是一个空的表格元素,用于承载扫雷游戏的方格(格子)。在游戏开始后,这个表格会被动态生成,并显示雷区的方格布局。

总体而言,这段HTML代码定义了一个扫雷游戏的基本界面结构,包括了难度选择、重新开始按钮和雷区的表格容器。通过这些元素,玩家可以进行难度选择,并在雷区中进行游戏操作。

<body>

  <div class="bigBox">
    <div id="controls">
      <form>
        <label for="level">难度级别:</label>
        <select id="level">
          <option value="easy">简单</option>
          <option value="medium">中等</option>
          <option value="hard">困难</option>
        </select>
        <button id="reset">重新开始</button>
      </form>
    </div>

    <table id="board"></table>
  </div>


</body>

在这里插入图片描述


✨ 样式设置

我们看到了上面的的DOM已经搭建好了,但是页面什么都看不出来,下面我们简单的来配置一下样式吧,其实我们本专栏也是想带领大家掌握一些逻辑所以样式方面我们就一切从简;

  1. .bigBox:这个类选择器用于样式化游戏的大容器。设置了背景颜色、宽度、居中对齐、内边距等样式,使游戏界面具有一定的外观和布局。

  2. #reset:这是一个ID选择器,用于样式化重新开始按钮。设置了按钮的宽度、字体大小等样式。

  3. table:这个选择器用于样式化表格元素。设置了表格的边框合并、居中对齐、外边距等样式,使雷区的方格在表格中呈现出合适的布局。

  4. td:这个选择器用于样式化表格中的单元格(方格)。设置了单元格的宽度、高度、文本对齐、垂直对齐、边框等样式,使方格具有一致的外观。

  5. button:这个选择器用于样式化按钮元素。设置了按钮的宽度、高度、内边距、外边距、字体大小、字体加粗、文本颜色、背景颜色等样式,使按钮呈现出一致的外观和按钮效果。

  6. #controls:这个ID选择器用于样式化控制面板的样式。设置了控制面板的顶部外边距,使其与上方的元素保持一定的间距。

总体而言,这段CSS代码通过设置不同的选择器和样式属性,为扫雷游戏的界面元素和控制面板元素提供了外观和布局的样式。这样可以使游戏界面看起来更加美观、整洁,并提供良好的用户体验。

/* 游戏布局样式 */
.bigBox {
  background-color: rgb(163, 159, 159);
  width: 40%;
  margin: 5% auto;
  text-align: center;
  padding: 20px;
}

#reset {
  width: 100px;
  font-size: 15px;
}

table {
  border-collapse: collapse;
  margin: 30px auto;
}

td {
  width: 30px;
  height: 30px;
  text-align: center;
  vertical-align: middle;
  border: 1px solid #ccc;
}

button {
  width: 100%;
  height: 100%;
  padding: 0;
  margin: 0;
  font-size: 16px;
  font-weight: bold;
  color: #fff;
  background-color: #333;
  border: none;
}

/* 控制面板样式 */
#controls {
  margin-top: 20px;
}

在这里插入图片描述


✨ 逻辑部分

上面我们搭建了基本的样式,下面呢我们就通过js代码,实现我们游戏的功能吧,下面是对代码的简化解释:

  1. const config = { ... }:这是一个对象,用于存储游戏的参数配置。根据难度级别(简单、中等、困难),配置了每个级别的行数、列数和地雷数量。

  2. 变量声明和初始化:声明了一系列变量用于存储游戏所需的各种信息,如游戏界面元素、游戏状态、地雷数量等。

  3. 事件监听器:通过监听重新开始按钮(reset)和难度级别选择器(level)的点击事件或改变事件,调用相应的初始化函数(init())来重新开始游戏或改变游戏难度。

  4. init() 函数:该函数用于初始化游戏。根据选择的难度级别,设置相应的行数、列数和地雷数量。然后,根据行数和列数动态生成游戏的表格布局,并为每个单元格(方格)添加按钮元素。最后,初始化地雷的位置,并更新地雷数量的显示。

  5. clickCell() 函数:该函数用于处理玩家点击单元格(方格)的操作。根据点击的单元格的位置,判断是否有地雷,如果有地雷则显示所有地雷并结束游戏;如果没有地雷,则根据周围的地雷数量显示相应的数字或递归地显示周围的方格。

  6. revealNeighbors() 函数:该函数用于递归地展开周围的方格,直到周围有地雷或有数字。

  7. countMinesAround() 函数:该函数用于计算给定方格周围的地雷数量。

  8. revealMines() 函数:该函数用于显示所有的地雷,将地雷方格的背景颜色设置为红色。

  9. updateMinesCount() 函数:该函数用于更新剩余地雷数量的显示。

  10. showGameOver() 函数:该函数用于展示游戏结束的提示信息,根据参数 win 的值判断是胜利还是失败。

  11. checkWin() 函数:该函数用于检查玩家是否已经胜利,遍历所有方格,如果有任何未揭示的非地雷方格,则返回 false,否则返回 true

  12. init() 函数调用:在脚本末尾,调用 init() 函数来初始化游戏。

通过这段JavaScript代码,扫雷游戏实现了玩家点击方格、展开周围方格、计算地雷数量等功能,并提供了重新开始游戏和切换难度级别的功能。

<script>
  // 游戏参数配置
  const config = {
    easy: {
      rows: 8,
      cols: 8,
      mines: 10,
    },
    medium: {
      rows: 10,
      cols: 10,
      mines: 20,
    },
    hard: {
      rows: 12,
      cols: 12,
      mines: 30,
    },
  };
  // 初始化游戏
  let board = document.getElementById("board");
  let level = document.getElementById("level");
  let reset = document.getElementById("reset");
  let cells = [];
  let gameover = false;
  let minesLeft = 0;
  let minesCount = 0;
  let rows, cols, mines;

  reset.addEventListener("click", init);

  level.addEventListener("change", function () {
    init();
  });

  function init () {
    // 初始化游戏参数
    let levelConfig = config[level.value];
    rows = levelConfig.rows;
    cols = levelConfig.cols;
    mines = levelConfig.mines;
    minesLeft = mines;
    minesCount = 0;
    gameover = false;
    // 初始化游戏布局
    board.innerHTML = "";
    cells = [];
    for (let i = 0; i < rows; i++) {
      let row = [];
      let tr = document.createElement("tr");
      for (let j = 0; j < cols; j++) {
        let td = document.createElement("td");
        let button = document.createElement("button");
        button.addEventListener("click", function () {
          if (!gameover) {
            clickCell(i, j);
          }
        });
        td.appendChild(button);
        tr.appendChild(td);
        row.push({ button: button, hasMine: false, revealed: false });
      }
      cells.push(row);
      board.appendChild(tr);
    }
    // 初始化地雷
    for (let i = 0; i < mines; i++) {
      let row, col;
      do {
        row = Math.floor(Math.random() * rows);
        col = Math.floor(Math.random() * cols);
      } while (cells[row][col].hasMine);
      cells[row][col].hasMine = true;
    }
    // 更新地雷数目显示
    updateMinesCount();
  }

  function clickCell (row, col) {
    let cell = cells[row][col];
    if (cell.revealed) {
      return;
    }
    if (cell.hasMine) {
      revealMines();
      showGameOver(false);
      return;
    }
    cell.revealed = true;
    cell.button.style.backgroundColor = "#ddd";
    let minesAround = countMinesAround(row, col);
    if (minesAround > 0) {
      cell.button.textContent = minesAround;
    } else {
      revealNeighbors(row, col);
    }
    if (checkWin()) {
      showGameOver(true);
    }
  }

  function revealNeighbors (row, col) {
    for (let i = row - 1; i <= row + 1; i++) {
      for (let j = col - 1; j <= col + 1; j++) {
        if (i >= 0 && i < rows && j >= 0 && j < cols && !(i == row && j == col)) {
          clickCell(i, j);
        }
      }
    }
  }

  function countMinesAround (row, col) {
    let count = 0;
    for (let i = row - 1; i <= row + 1; i++) {
      for (let j = col - 1; j <= col + 1; j++) {
        if (i >= 0 && i < rows && j >= 0 && j < cols && cells[i][j].hasMine) {
          count++;
        }
      }
    }
    return count;
  }

  function revealMines () {
    for (let i = 0; i < rows; i++) {
      for (let j = 0; j < cols; j++) {
        if (cells[i][j].hasMine) {
          cells[i][j].button.style.backgroundColor = "#f00";
        }
      }
    }
  }

  function updateMinesCount () {
    console.log('这是哈哈', minesLeft)
    // minesCountElem.textContent = minesLeft;
  }

  function showGameOver (win) {
    gameover = true;
    let message = win ? "You Win!" : "You Lose!";
    alert(message);
  }

  function checkWin () {
    for (let i = 0; i < rows; i++) {
      for (let j = 0; j < cols; j++) {
        let cell = cells[i][j];
        if (!cell.hasMine && !cell.revealed) {
          return false;
        }
      }
    }
    return true;
  }

  init();

</script>

完整代码

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    /* 游戏布局样式 */
    .bigBox {
      background-color: rgb(163, 159, 159);
      width: 40%;
      margin: 5% auto;
      text-align: center;
      padding: 20px;
    }

    #reset {
      width: 100px;
      font-size: 15px;
    }

    table {
      border-collapse: collapse;
      margin: 30px auto;
    }

    td {
      width: 30px;
      height: 30px;
      text-align: center;
      vertical-align: middle;
      border: 1px solid #ccc;
    }

    button {
      width: 100%;
      height: 100%;
      padding: 0;
      margin: 0;
      font-size: 16px;
      font-weight: bold;
      color: #fff;
      background-color: #333;
      border: none;
    }

    /* 控制面板样式 */
    #controls {
      margin-top: 20px;
    }
  </style>
</head>

<body>

  <div class="bigBox">
    <div id="controls">
      <form>
        <label for="level">难度级别:</label>
        <select id="level">
          <option value="easy">简单</option>
          <option value="medium">中等</option>
          <option value="hard">困难</option>
        </select>
        <button id="reset">重新开始</button>
      </form>
    </div>

    <table id="board"></table>
  </div>


</body>

<script>
  // 游戏参数配置
  const config = {
    easy: {
      rows: 8,
      cols: 8,
      mines: 10,
    },
    medium: {
      rows: 10,
      cols: 10,
      mines: 20,
    },
    hard: {
      rows: 12,
      cols: 12,
      mines: 30,
    },
  };
  // 初始化游戏
  let board = document.getElementById("board");
  let level = document.getElementById("level");
  let reset = document.getElementById("reset");
  let cells = [];
  let gameover = false;
  let minesLeft = 0;
  let minesCount = 0;
  let rows, cols, mines;

  reset.addEventListener("click", init);

  level.addEventListener("change", function () {
    init();
  });

  function init () {
    // 初始化游戏参数
    let levelConfig = config[level.value];
    rows = levelConfig.rows;
    cols = levelConfig.cols;
    mines = levelConfig.mines;
    minesLeft = mines;
    minesCount = 0;
    gameover = false;
    // 初始化游戏布局
    board.innerHTML = "";
    cells = [];
    for (let i = 0; i < rows; i++) {
      let row = [];
      let tr = document.createElement("tr");
      for (let j = 0; j < cols; j++) {
        let td = document.createElement("td");
        let button = document.createElement("button");
        button.addEventListener("click", function () {
          if (!gameover) {
            clickCell(i, j);
          }
        });
        td.appendChild(button);
        tr.appendChild(td);
        row.push({ button: button, hasMine: false, revealed: false });
      }
      cells.push(row);
      board.appendChild(tr);
    }
    // 初始化地雷
    for (let i = 0; i < mines; i++) {
      let row, col;
      do {
        row = Math.floor(Math.random() * rows);
        col = Math.floor(Math.random() * cols);
      } while (cells[row][col].hasMine);
      cells[row][col].hasMine = true;
    }
    // 更新地雷数目显示
    updateMinesCount();
  }

  function clickCell (row, col) {
    let cell = cells[row][col];
    if (cell.revealed) {
      return;
    }
    if (cell.hasMine) {
      revealMines();
      showGameOver(false);
      return;
    }
    cell.revealed = true;
    cell.button.style.backgroundColor = "#ddd";
    let minesAround = countMinesAround(row, col);
    if (minesAround > 0) {
      cell.button.textContent = minesAround;
    } else {
      revealNeighbors(row, col);
    }
    if (checkWin()) {
      showGameOver(true);
    }
  }

  function revealNeighbors (row, col) {
    for (let i = row - 1; i <= row + 1; i++) {
      for (let j = col - 1; j <= col + 1; j++) {
        if (i >= 0 && i < rows && j >= 0 && j < cols && !(i == row && j == col)) {
          clickCell(i, j);
        }
      }
    }
  }

  function countMinesAround (row, col) {
    let count = 0;
    for (let i = row - 1; i <= row + 1; i++) {
      for (let j = col - 1; j <= col + 1; j++) {
        if (i >= 0 && i < rows && j >= 0 && j < cols && cells[i][j].hasMine) {
          count++;
        }
      }
    }
    return count;
  }

  function revealMines () {
    for (let i = 0; i < rows; i++) {
      for (let j = 0; j < cols; j++) {
        if (cells[i][j].hasMine) {
          cells[i][j].button.style.backgroundColor = "#f00";
        }
      }
    }
  }

  function updateMinesCount () {
    console.log('这是哈哈', minesLeft)
    // minesCountElem.textContent = minesLeft;
  }

  function showGameOver (win) {
    gameover = true;
    let message = win ? "You Win!" : "You Lose!";
    alert(message);
  }

  function checkWin () {
    for (let i = 0; i < rows; i++) {
      for (let j = 0; j < cols; j++) {
        let cell = cells[i][j];
        if (!cell.hasMine && !cell.revealed) {
          return false;
        }
      }
    }
    return true;
  }

  init();

</script>

</html>

本期推荐 查看详情

在这里插入图片描述

原创不易,还希望各位大佬支持一下 \textcolor{blue}{原创不易,还希望各位大佬支持一下} 原创不易,还希望各位大佬支持一下

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

前端技术搭建扫雷小游戏(内含源码) 的相关文章

随机推荐

  • 第十课.图片风格迁移和GAN

    目录 Neural Style Transfer Neural Style Transfer原理 准备工作 定义模型并加载预训练的模型参数 训练target以及结果可视化 生成对抗网络GAN GAN原理 GAN生成Mnist 准备工作 模型
  • 博客系统文章的数据库存储方式

    在通常的博客系统中 我们发表文章的时候 在数据库中存储的一般不仅仅是文章的文字 还包括文章的样式 而且很多时候都是所见即所得的效果 这就要求我们以html 文字这样存进数据库中 通过查找资料 可以用专门的文字编辑器可以实现 使用方法如下 F
  • 系统架构设计师之网络安全-各个层次的网络安全保障

    系统架构设计师之网络安全 各个层次的网络安全保障
  • Map集合知识点整理

    目录 一 Map集合概述 1 特点 2 Map集合的基本功能 二 实现类HashMap集合 1 概述 2 常用方法总结 3 HashMap的底层实现 4 LinkedHashMap 5 TreeMap 三 Map集合的遍历方式 四 案例演示
  • 文献管理软件--zotero基本使用

    文章目录 一 下载与安装 1 下载插件 以火狐浏览器为例 2 注册账户 3 下载桌面版 二 文献导入 1 新建文件 2 导入文献 3 本地导入 4 支持批量下载 三 文献管理 1 添加标签 2 添加子目录 3 添加笔记 四 添加插件 五 数
  • MyBatis的逆向工程

    MyBatis的逆向工程 正向工程 先创建Java实体类 由框架负责根据实体类生成数据库表 Hibernate是支持正向工程的 逆向工程 先创建数据库表 由框架负责根据数据库表 反向生成如下资源 Java实体类 Mapper接口 Mappe
  • 迭代法求解线性方程组(C++实现)

    本系列是数值分析相关算法的文章 这次用迭代法求解线性方程组 不同于上次用高斯消元法之类的求解 迭代法对于稀疏矩阵方程组的运算 会大大提高 而如果用高斯相关的算法求解 会浪费大量资源计算无用的东西 所以有必要研究此算法 本文章主要使用了3个算
  • android之service

    Service的启动有两种方式 context startService 和 context bindService 通过startService 启动的服务处于 启动的 状态 一旦启动 service就在后台运行 即使启动它的应用组件已经
  • Matlab2023a最新详解

    MATLAB 2023版的深度学习工具箱 提供了完整的工具链 使您能够在一个集成的环境中进行深度学习的建模 训练和部署 与Python相比 MATLAB的语法简洁 易于上手 无需繁琐的配置和安装 让您能够更快地实现深度学习的任务 MATLA
  • 【CTF/MISC】图片隐写题(binwalk/foremost/010editer配合使用)

    图片隐写 题目 解题思路 binwalk工具查看是否有隐藏文件 foremost工具分离文件 010editer查看二进制数据 寻找解压密码 解题心得 题目连接 题目 题目是一张图片 寻找题目中隐藏的flag 解题思路 一般来说我碰到图片隐
  • SSM框架下实现简单增删查改的具体细节(代码)

    UserInfo public class UserInfo private int id private String username private String password public int getId return id
  • 电子检测报告如何盖骑缝章?

    检测评估报告通常有多页 几十页的报告也不少见 文件中除了要在检测机构盖公章处盖章 还需要盖骑缝章 为了防范风险 防止报告内容被更换的情况 就需要骑缝章的加盖来保证检测报告的整体性 下面以微签为例 展示一下电子检测报告如何盖骑缝章 微签在检测
  • Java线程池面试题整理总结【实习打卡01】

    ThreadLocal GC 之后 key 是否为 null 不一定 1 当使用new ThreadLocal lt gt set s 定义threadlocal时 没有在栈中声明一个变量指向他 那他就是只被弱引用 在gc后 那这个thre
  • 获取微信小程序码实例,建议拿到字节流后可以base64编码再上传图片,得到图片地址进行使用

    接口调用凭证 access token public static String postToken String appId String appKey throws Exception String requestUrl https a
  • 【无人车】用于无人地面车辆的路径跟踪算法(Matlab代码实现)

    欢迎来到本博客 博主优势 博客内容尽量做到思维缜密 逻辑清晰 为了方便读者 座右铭 行百里者 半于九十 本文目录如下 目录 1 概述 2 运行结果 3 参考文献 4 Matlab代码实现 1 概述 无人驾驶技术是当前社会的热门技术之一 无人
  • 【Unity&UGUI&Shader】创建材质球Material&代码控制更换物体材质球

    UGUI Unity Graphical User Interface 简称 UGUI 又称图形用户接口 如何创建材质球 NGUI Next generation GUI 下一代图形用户接口 Assets Create Material如下
  • 泛型,序列化

    何为泛型 1 JDK1 5以后推出的一种新的参数化的类型 2 通常可以理解为一种一种编译类型 在运行时无效 3 类型生活中的标签 为什么使用泛型 1 约束类中属性类型 方法参数类型 方法返回值类型 2 提高运行时性能 List s new
  • 西门子PLC的TCP通讯(不同项目下)②--TRCV_C指令

    西门子PLC的TCP通讯 不同项目下 TRCV C指令 上期主要了解了TSEND C指令的各项参数的意义 隐藏参数LEN等可以默认 本期将了解另一个配套组合指令块TRCV C 这是个接收指令块 大概呢跟发送指令TSEND C差不多 1 0
  • pinia的基本使用

    npm install pinia 安装pinia import createPinia from pinia app use createPinia main js中使用pinia 新建一个store文件夹 类似于vuex src sto
  • 前端技术搭建扫雷小游戏(内含源码)

    The sand accumulates to form a pagoda 写在前面 功能介绍 页面搭建 样式设置 逻辑部分 写在前面 上周我们实通过前端基础实现了贪吃蛇游戏 今天还是继续按照我们原定的节奏来带领大家完成一个游戏 功能也比较