前端技术搭建扫雷小游戏(内含源码)
写在前面
上周我们实通过前端基础实现了贪吃蛇游戏,今天还是继续按照我们原定的节奏来带领大家完成一个游戏,功能也比较简单简单,也是想借助这样一个简单的功能,然后来帮助大家了解我们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已经搭建好了,但是页面什么都看不出来,下面我们简单的来配置一下样式吧,其实我们本专栏也是想带领大家掌握一些逻辑所以样式方面我们就一切从简;
-
.bigBox:这个类选择器用于样式化游戏的大容器。设置了背景颜色、宽度、居中对齐、内边距等样式,使游戏界面具有一定的外观和布局。
-
#reset:这是一个ID选择器,用于样式化重新开始按钮。设置了按钮的宽度、字体大小等样式。
-
table:这个选择器用于样式化表格元素。设置了表格的边框合并、居中对齐、外边距等样式,使雷区的方格在表格中呈现出合适的布局。
-
td:这个选择器用于样式化表格中的单元格(方格)。设置了单元格的宽度、高度、文本对齐、垂直对齐、边框等样式,使方格具有一致的外观。
-
button:这个选择器用于样式化按钮元素。设置了按钮的宽度、高度、内边距、外边距、字体大小、字体加粗、文本颜色、背景颜色等样式,使按钮呈现出一致的外观和按钮效果。
-
#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代码,实现我们游戏的功能吧,下面是对代码的简化解释:
-
const config = { ... }:这是一个对象,用于存储游戏的参数配置。根据难度级别(简单、中等、困难),配置了每个级别的行数、列数和地雷数量。
-
变量声明和初始化:声明了一系列变量用于存储游戏所需的各种信息,如游戏界面元素、游戏状态、地雷数量等。
-
事件监听器:通过监听重新开始按钮(reset)和难度级别选择器(level)的点击事件或改变事件,调用相应的初始化函数(init())来重新开始游戏或改变游戏难度。
-
init()函数:该函数用于初始化游戏。根据选择的难度级别,设置相应的行数、列数和地雷数量。然后,根据行数和列数动态生成游戏的表格布局,并为每个单元格(方格)添加按钮元素。最后,初始化地雷的位置,并更新地雷数量的显示。
-
clickCell()函数:该函数用于处理玩家点击单元格(方格)的操作。根据点击的单元格的位置,判断是否有地雷,如果有地雷则显示所有地雷并结束游戏;如果没有地雷,则根据周围的地雷数量显示相应的数字或递归地显示周围的方格。
-
revealNeighbors()函数:该函数用于递归地展开周围的方格,直到周围有地雷或有数字。
-
countMinesAround()函数:该函数用于计算给定方格周围的地雷数量。
-
revealMines()函数:该函数用于显示所有的地雷,将地雷方格的背景颜色设置为红色。
-
updateMinesCount()函数:该函数用于更新剩余地雷数量的显示。
-
showGameOver()函数:该函数用于展示游戏结束的提示信息,根据参数win的值判断是胜利还是失败。
-
checkWin()函数:该函数用于检查玩家是否已经胜利,遍历所有方格,如果有任何未揭示的非地雷方格,则返回false,否则返回true。
-
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>
本站大部分文章、数据、图片均来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了您的权益请来信告知我们删除。邮箱:1451803763@qq.com