文章目录
前言
本文是一篇关于如何用Javascript实现扫雷的博客,文章末尾有作者自己编写的扫雷程序以及下载链接,代码可能有缺陷,图片是本人自己绘制的,可能不太美观,还请高手指教。
一、扫雷是什么?
扫雷是一款大众类的益智小游戏。游戏目标是在最短的时间内根据点击格子出现的数字找出所有非雷格子,同时避免踩雷,踩到一个雷即全盘皆输。(抄百度的)
二、游戏流程
1.初始界面
上图是游戏运行时的初始界面,其组成分为两个部分,第一个是背景,第二个是按钮,两个部分。背景其实就是一张图片,而按钮是多个对象,分别为“开始游戏”、“无尽模式”、“游戏设置”、“游戏教程”四个对象,每个对象的属性包括:图片的资源路径,图片横纵坐标,图片的长和宽。与此同时,每个对象也有它自己的方法,而方法是用户点击了按钮后要执行的代码。例如“开始游戏”按钮就需要把游戏状态修改成“GAMEING”状态,生成一组游戏数据,并开始计时。而我是选择把这些对象存在一个数组中,然后专门定义两个函数分别用于绘制按钮和响应按钮点击。
2.游戏过程
上图是游戏中的截图,要实现上方所展示的功能,需要很多步骤,主要包括:绘制背景色,绘制底部方块,绘制数字,绘制遮挡方块,绘制标记,绘制计时以及地雷数,六个步骤。
(1)绘制背景色
绘制背景色的步骤比较简单,可以通过绘图软件,画出一个长和宽都为1px的图片,然后在绘制背景色时,把这个图片的坐标定在(0,0)然后再把图片拉宽拉长以填充整个画面。
(2)绘制底部方块
上图所示为底部方块,在游戏中主要是为了划分每一个方格,让游戏更加人性化,绘制方式是在一个循环中,根据生成的游戏数据,把每一个方格的坐标,大小计算出来,然后再开始绘制,嵌套两层循环即可。
(3)绘制数字
上图所示为数字,程序中需要先计算每个方格应该显示什么,在点击了开始游戏的按钮后生成了游戏数据,其中包括的就有每一个底部方块绘制的位置,每个方格应该显示什么,并存储在一个二维数组中。而决定流程如下:
我在我的游戏中用0表示什么都不绘制,而’B’(Bomb)表示炸弹,相应的数字代表周边的地雷数,存储方式如下图:
在数据计算完毕后,我们需要通过循环来绘制数字,在遍历二维数组的时候不要直接绘制,要判断一下这个方块是否被遮挡了,如果被遮挡方块遮挡了,就不需要绘制了。
(4)绘制遮挡方块
上图是游戏中的遮挡方块,它是这个游戏中必不可少的一个部分,其绘制方式是和数字的绘制方法一样的,但是需要注意的是,需要定义第二个二维数组,用来存储是否需要绘制遮挡方块,并在绘制时添加一个判断语句:是否被遮挡。
(5)绘制标记
上图为标记图标,程序中只需要在遮挡方块的上方再绘制一个标记就好了,而关于标记的数据是存储在第二个二维数组中的。我在设计程序是用’D’(Dug)表示已经被挖开的方块,用’U’(unknown)表示未被挖掘的遮挡方块,用’S’(Signed)表示被标记的方块,具体存储方式如下图所示:
(6)绘制计时以及地雷数
绘制计时以及地雷数前,需要知道绘制什么,也就是用户现在已经开始游戏了多少秒了,游戏中还剩多少个地雷没有被标记?而具体的处理方法是:
- 对于计时,我们需要在游戏开始前先定义一个变量,用于存储当前游戏开始的时间,而初始值为0,然后在用户开始游戏的时候开始一个定时器,时间定为1秒,每一秒就这个变量的数值自增1。
- 对于地雷数是在开始游戏前生成游戏数据的时候,会有预定需要放置多少个地雷的一个具体数值,而在开始游戏后,把这个数据备份一个,然后程序只需要在用户每一次标记地雷的时候,把这个备份出来的数字自减1,取消标记的时候自增1。
处理完上述的数据后,就是绘制上的问题了。我们所需要做的是把提前绘制好的“用时:”和“剩余:”两个图片的对象通过计算,得出在屏幕上的合适位置绘制上,同时空出所需绘制数字的空间,再后来把数字绘制在恰当的位置。
3.游戏结束
(1)游戏胜利
上图是游戏胜利后的图片,需要显示“用时”,“游戏板得分”,“总分”。因此,在游戏中需要有分数统计,例如打开一个方块就加分,标记了一个地雷,就加分。而且不同的难度对得分要有影响,比如,简单模式下,分数一般不超过1000,普通模式下,分数在5000附近徘徊……还有就是用时也要对分数有影响。而游戏板得分指的是不计算时间影响下的分数。
在有了分数之后,需要把胜利后的图片绘制在画面的中央,然后再把具体的文字信息写在相应的位置。
(2)游戏失败
上图是游戏引爆地雷后的图片,也需要显示“用时”,“游戏板得分”,“总分”,具体处理方法与成功相似,区别是背景图是失败的背景图,以及在引爆炸弹时的特效。
(3)结束后的处理
结束后我们只需要相应任意一个鼠标点击,只要用户点击了画布,我们就把游戏状态调回初始界面。
4.无尽模式
在无尽模式中,我们需要添加一个新的方块,是楼梯,每当用户挖开了楼梯,并且再次点击了这个楼梯,用户就可以下楼,到达新的一层,但是游戏中新生成的长和宽都会增加,难度会不断的上升。
5.游戏设置
在游戏设置中用户可以选择调整游戏的难度,分为“简单”,“普通”,“困难”三个难度,当用户点击了这个按钮之后,它会变亮,而其具体的实现方式就是调用在初始界面绘制和响应按钮点击的程序,再添加一点点的改动即可。
6.游戏教程
游戏教程功能我还没有开发,但是整体思路是在用户点击了游戏教程的按钮后,从下往上以一定的加速度落下一个板子,上面写着游戏的教程,然后当用户点击了右上角的“X”按钮之后就返回初始界面。
7.游戏细节
(1)炸弹爆炸
在用户不小心引爆炸弹之后,会在炸弹的相应位置播放一个爆炸的特效。要实现特效,就要知道实现播放视频的具体方法,一个视频对象的组成,也就是视频这个类的具体内容如下:
// 生成视频对象
obj = {
video: video, // 存储视频图片序列
doPlay: false, // 定义是否需要绘制
x: 0, // 定义绘制坐标
y: 0, // 定义绘制坐标
frameNum: 0, // 定义当前视频播放到第几帧
intervalTime: intervalTime, // 定义视频FPS
side: 100, // 定义视频缩放
lastTime: new Date().getTime(), // 获得当前时间戳
stayLastFrame: stayLastFrame, // 视频是否需要停止在最后一帧
loopPlayback: loopPlayback // 视频是否需要循环播放
}
接下来,我们要知道如何播放一个视频对象。我们需要知道当前的时间,然后比对上一帧是多少秒之前播放的,然后如果当前的时间已经和上一帧播放的时间有一定的时间的时候,我们就要绘制下一帧,也就是把目前播放的帧数自增1,具体代码如下:
currentTime = new Date().getTime()
if (currentTime - obj.lastTime >= obj.intervalTime) {
// 如果图片的下标等于数组的长度,则重置图片下标为0
if (obj.frameNum < obj.video.length) {
ctx.drawImage(obj.video[obj.frameNum], obj.x, obj.y, obj.side, obj.side)
// 加 1 得到下一张图片的下标
obj.frameNum++
} else if (obj.stayLastFrame == true) {
ctx.drawImage(obj.video[obj.video.length - 1], obj.x, obj.y, obj.side, obj.side)
} else if (obj.loopPlayback == true) {
obj.frameNum = 0
}
}
知道了视频对象的组成,播放的原理之后,我们就可以设计一个开始播放的函数了,只需要把这个视频对象调整为“开始播放”状态,然后把需要绘制的位置计算好,缩放的大小确定好就可以了,具体代码如下:
// 调整为开始播放
obj.doPlay = true
// 计算好绘制坐标
obj.x = x * coefficient + xAddNum
obj.y = y * coefficient + yAddNum
// 存入缩放大小
obj.side = side * sideMultiple
// 调整为从头播放
obj.frameNum = 0
(2)自动开零
在用户点击了一个空的方格时,程序需要帮助用户把与这一个0相连的所有的0及其周围一圈的方格都点开,而步骤分两步,第一步,打开所有相连的0,第二步,打开所有0周围的方格。第一步需要通过递归算法来实现,具体代码如下:
if (blockInfoAry[y][x] == 0) {
// 将该方块变为已打开并加分
openBlock(y, x)
// 递归算法找出所有未打开的连续的方格
if (0 < y - 1 && blockisDigAry[y - 1][x] != 'D') {
if (0 < x - 1 && blockisDigAry[y - 1][x - 1] != 'D') {
open0(x - 1, y - 1)
}
open0(x, y - 1)
if (x + 1 < blockWidthNumOutter + 1 && blockisDigAry[y - 1][x + 1] != 'D') {
open0(x + 1, y - 1)
}
}
if (0 < x - 1 && blockisDigAry[y][x - 1] != 'D') {
open0(x - 1, y)
}
if (x + 1 < blockWidthNumOutter + 1 && blockisDigAry[y][x + 1] != 'D') {
open0(x + 1, y)
}
if (y + 1 < blockHeightNumOutter + 1 && blockisDigAry[y + 1][x] != 'D') {
if (0 < x - 1 && blockisDigAry[y + 1][x - 1] != 'D') {
open0(x - 1, y + 1)
}
open0(x, y + 1)
if (x + 1 < blockWidthNumOutter + 1 && blockisDigAry[y + 1][x + 1] != 'D') {
open0(x + 1, y + 1)
}
}
}
第二步就比较简单了,只需要根据目前打开的这个方格,然后把周围的方格在第二个二维数组中标记为’D’即可,具体实现方法如下:
function openBeside() {
for (var i = 1; i < blockHeightNumOutter + 1; i++) {
for (var j = 1; j < blockWidthNumOutter + 1; j++) {
// 将所有已打开的0格周边的方格打开
if (blockInfoAry[i][j] == 0 && blockisDigAry[i][j] == 'D') {
// 加分
boardScore = boardScore + 1
blockisDigAry[i - 1][j - 1] = 'D'
blockisDigAry[i - 1][j] = 'D'
blockisDigAry[i - 1][j + 1] = 'D'
blockisDigAry[i][j - 1] = 'D'
blockisDigAry[i][j + 1] = 'D'
blockisDigAry[i + 1][j - 1] = 'D'
blockisDigAry[i + 1][j] = 'D'
blockisDigAry[i + 1][j + 1] = 'D'
}
}
}
}
总结
以上就是今天要讲的内容,本文仅仅简单介绍了如何用Javascript实现扫雷,也希望大家能够指正我的不足,让我们一起进步!
下载方式
链接:https://pan.baidu.com/s/15xTr8U28TmJ3KR4bSVM2lw
提取码:woww
代码内容
<!DOCTYPE html>
<html lang="ch">
<!--
COPYRIGHT(C): Jerry Chen
File NAME: 1.0.0.20210530_alpha.html
Author: Jerry Chen
Version: 1.0.0.20210530_alpha
Date: 1.0.0.20210530
DESCRIPTION: real-time display of timer and number of remaining bombs
Function List:
1. gameHandler: Draw the game interface.
2. clickResponse: Respond to the user clicking the canvas.
3. drawItem: Operate on the blocks of the game according to the relevant data.
4. responseButton: Respond to the button on the start screen.
5. drawButton: Draws the button on the start screen.
6. drawBlock: Draw the bottom box.
7. drawBomb: Draw bombs or numbers in the game.
8. drawBaffle: Draw a baffle to hide numbers or bombs.
9. open0: Determine if the current square is a "0" square, and if so, open all successive "0" squares around it.
10. openBeside: Open all the squares around the open "0" square.
11. generateBasicInfo: Calculate the relevant parameters of drawing, lay mines randomly, draw numbers according to mines, and open non-mine blocks randomly.
12. isWin: Judge the game.
13. drawFloatWindow: Paint the floating window after victory.
14. importVideo: Import the image of each frame of the video.
15. startPlay: Adjust the state of the video object to start playing.
16. playVideo: Play a video according to the properties of the video object and its methods.
17. countPoint: award bonus points for the number of bombs marked.
18. CalTotalScore: Calculate the total score.
19. timer: Timers and remaining bombs displayed in the game.
-->
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>扫雷</title>
<style>
body {
text-align: center
}
<