引言:
扫雷是系统自带的经典小游戏,以前上学那会上机的时候就经常玩这个,趁着五一假期的最后一天,用canvas编写了这个小游戏,看着还行,不知道会不会有什么我没发现的bug,难度目前是设置成简易的,如果要改难度,代码稍做修改即可。
效果图
实现思路
1.创建9行9列的二维数组。
2.设置雷:随机行数 i 和列数 j,根据随机到 i、j 从二维数组中取出对应的元素,将取到的元素设置一个属性type等于1,表示当前元素已经是雷,并且递增雷计数器,然后递归调用;如果取到的元素已经是雷了,则跳过继续执行,雷计数器达到设定的最大值就跳出递归。
3.计算每个元素周围的雷数量(周围指的是 左上、上、右上、右、右下、下、左下、左 这8个位置),当前位置显示对应的数字(待会内容里面细说)
4.同样根据这个二维数组来创建遮罩的小方块,正好盖住之前创建的图形。
5.点击这个遮罩的小方块则触发揭开,揭开后根据对应的数字或者雷做不同的操作。
代码实现
创建背景及相关元素
-
//绘制背景及相关默认元素
-
Saolei.prototype.drawBG=
function(){
-
var image,img,sx=
0,sy=
0,sWidth=
141,sHeight=
54,dx=
20,dy=
340,dWidth=
141,dHeight=
54;
-
//计时
-
image =
this.imgObj[
'common'][
15];
-
img =
new _.ImageDraw({
image:image,
sx:sx,
sy:sy,
sWidth:sWidth,
sHeight:sHeight,
dx:dx,
dy:dy ,
dWidth:dWidth,
dHeight:dHeight});
-
this.renderArr.push(img);
-
-
sx=
0,sy=
0,sWidth=
141,sHeight=
52,dx=
180,dy=
340,dWidth=
141,dHeight=
52;
-
//计雷
-
image =
this.imgObj[
'common'][
14];
-
img =
new _.ImageDraw({
image:image,
sx:sx,
sy:sy,
sWidth:sWidth,
sHeight:sHeight,
dx:dx,
dy:dy ,
dWidth:dWidth,
dHeight:dHeight});
-
this.renderArr.push(img);
-
//创建一个方形区域
-
var rect =
new _.Rect({
-
x:
24,
-
y:
44,
-
width:
289,
-
height:
289,
-
stroke:
true
-
})
-
this.renderArr.push(rect);
-
-
sx=
0,sy=
0,sWidth=
100,sHeight=
40,dx=
120,dy=
2,dWidth=
100,dHeight=
40;
-
//重新开始按钮
-
image =
this.imgObj[
'common'][
21];
-
img =
new _.ImageDraw({
image:image,
sx:sx,
sy:sy,
sWidth:sWidth,
sHeight:sHeight,
dx:dx,
dy:dy ,
dWidth:dWidth,
dHeight:dHeight});
-
this.renderArr.push(img);
-
this.reStartObj=img;
-
}
创建雷和显示对应的图片
1.随机row 和 col,并从二维数组中获取到这个对象;
2.判断他是否是雷,如果是则跳过当前;
3.如果当前不是雷,则标记当前对象为雷对象,并且更改图片;
4.递归,当达到设定的数量时跳出。
-
//创建被遮盖
-
Saolei.prototype.createUnder=function(){
-
var image,img,sx=
0,sy=
0,sWidth=
79,sHeight=
79,dx=
0,dy=
0,dWidth=
32,dHeight=
32;
-
var rows =
this.rows;
//行
-
var cols =
this.cols;
//列
-
image =
this.imgObj[
'common'][
9];
-
//二维网格数组
-
var gridArr=[];
-
var arr =
this.gridArr,cell;
-
for(
var i=
0;i<rows;i++){
//行
-
dy =
45+i*dHeight;
-
gridArr[i]=[];
-
for(
var j=
0;j<cols;j++){
//列
-
dx =
25+j*dWidth;
-
img = new _.ImageDraw({image:image,sx:sx,sy:sy,sWidth:sWidth,sHeight:sHeight, dx:dx, dy:dy ,dWidth:dWidth,dHeight:dHeight});
-
img.type=
0;
-
this.renderArr.push(img);
-
-
gridArr[i][j]=img;
-
}
-
}
-
-
this.gridArr=gridArr;
-
-
//创建雷
-
this.createLei();
-
-
}
-
-
//创建雷
-
Saolei.prototype.createLei=function(){
-
//当达到设定的数量时跳出
-
if(
this.leiMaxCount<=
0) {
-
return ;
-
}
-
var arr =
this.gridArr;
-
/*
-
1.随机row 和 col,并从二维数组中获取到这个对象
-
2.判断他是否是雷,如果是则跳过当前
-
3.如果当前不是雷,则标记当前对象为雷对象,并且更改图片
-
4.递归,当达到设定的数量时跳出
-
*/
-
var row = _.getRandom(
0,
this.rows);
-
var col = _.getRandom(
0,
this.cols);
-
var cell = arr[row][col];
-
if(cell.type==
0){
-
//标记为雷
-
cell.type=
1;
-
cell.image =
this.imgObj[
'common'][
18];
-
this.leiMaxCount--;
-
console.log(row,col);
-
}
-
//递归
-
this.createLei();
-
}
计算周围雷的数量并显示
1.循环之前定义的二维数组
2.如果当前元素的下标是(i,j),则左上为(i-1,j-1),上为(i-1,j ),右上为(i-1,j+1),以此类推,如下图所示:
3.分别取出这些元素,并判断他们是不是雷,如果是则计数累加,最后将计数对应到相应的图片,然后显示出来。
-
//计算周边雷的数量并更改对象的相关参数
-
Saolei.prototype.computedLei=function(){
-
var arr =
this.gridArr,cell;
-
for(
var i=
0;i<arr.length;i++){
//行
-
for(
var j=
0;j<arr[i].length;j++){
//列
-
cell = arr[i][j];
-
if(cell.type==
1){
//当前是雷则直接跳过
-
continue;
-
}
-
var count=
0;
-
-
//左上
-
var ci = i-
1,cj = j-
1,ccell;
-
if(ci>=
0 && cj>=
0){
-
ccell = arr[ci][cj];
-
if(ccell.type==
1){
-
count++;
-
}
-
}
-
//上
-
ci = i-
1,cj = j,ccell;
-
if(ci>=
0 && cj>=
0){
-
ccell = arr[ci][cj];
-
if(ccell.type==
1){
-
count++;
-
}
-
}
-
//右上
-
ci = i-
1,cj = j+
1,ccell;
-
if(ci>=
0 && cj<
this.cols){
-
ccell = arr[ci][cj];
-
if(ccell.type==
1){
-
count++;
-
}
-
}
-
//右
-
ci = i,cj = j+
1,ccell;
-
if(cj<
this.cols){
-
ccell = arr[ci][cj];
-
if(ccell.type==
1){
-
count++;
-
}
-
}
-
//右下
-
ci = i+
1,cj = j+
1,ccell;
-
if(ci<
this.rows && cj<
this.cols){
-
ccell = arr[ci][cj];
-
if(ccell.type==
1){
-
count++;
-
}
-
}
-
//下
-
ci = i+
1,cj = j,ccell;
-
if(ci<
this.rows){
-
ccell = arr[ci][cj];
-
if(ccell.type==
1){
-
count++;
-
}
-
}
-
//左下
-
ci = i+
1,cj = j-
1,ccell;
-
if(ci<
this.rows && cj >=
0){
-
ccell = arr[ci][cj];
-
if(ccell.type==
1){
-
count++;
-
}
-
}
-
//左
-
ci = i,cj = j-
1,ccell;
-
if(cj >=
0){
-
ccell = arr[ci][cj];
-
if(ccell.type==
1){
-
count++;
-
}
-
}
-
//设定周围雷的数量
-
cell.count=count;
-
-
if(count==
0){
//因为0那张图片下标用的9
-
count=
9;
-
}
-
//更换图片
-
cell.image =
this.imgObj[
'common'][count];
-
}
-
}
-
}
创建遮罩
-
//创建遮盖
-
Saolei.prototype.createOver=function(){
-
var image,img,sx=
0,sy=
0,sWidth=
79,sHeight=
79,dx=
0,dy=
0,dWidth=
32,dHeight=
32;
-
image =
this.imgObj[
'common'][
10];
-
var arr =
this.gridArr;
-
for(var i=
0;i<arr.length;i++){
//行
-
this.overArr[i]=[];
-
for(var j=
0;j<arr[i].length;j++){
//列
-
dy =
45+i*dHeight;
-
dx =
25+j*dWidth;
-
img =
new _.ImageDraw({
image:image,
sx:sx,
sy:sy,
sWidth:sWidth,
sHeight:sHeight,
dx:dx,
dy:dy ,
dWidth:dWidth,
dHeight:dHeight});
-
img.i=i,img.j=j;
-
this.renderArr.push(img);
-
this.overArr[i][j]=img;
-
}
-
}
-
}
创建计时和计数器
-
//创建计数和计时器
-
Saolei.prototype.createCount=function(){
-
//计时器
-
var x=
115,y=
382,content=
0;
-
var text = new _.Text({
-
x:x,
-
y:y,
-
text:content,
-
font:
'26px ans-serif',
-
textAlign:
'center',
-
fill:
true,
-
fillStyle:
'white'
-
});
-
this.renderArr.push(text);
-
this.timeCountObj=text;
-
-
x=
222,y=
382,content=
this.leiCount;
-
var text = new _.Text({
-
x:x,
-
y:y,
-
text:content,
-
font:
'26px ans-serif',
-
textAlign:
'center',
-
fill:
true,
-
fillStyle:
'white'
-
});
-
this.renderArr.push(text);
-
this.leiCountObj=text;
-
}
加入鼠标移动事件
-
//鼠标移动事件
-
Saolei.prototype.mouseMove=function(e){
-
if(
this.endAnimate)
return ;
-
-
var pos = _.getOffset(e);
//获取鼠标位置
-
var isCatch=
false;
-
if(
this.reStartObj.isPoint(pos)){
-
this.el.style.cursor =
'pointer';
//改为手状形态
-
}
else{
-
this.el.style.cursor =
'';
//改为普通形态
-
}
-
-
if(
this.end)
return ;
//结束了已经
-
if(!isCatch){
-
//循环遮罩数组
-
var arr =
this.overArr,cell;
-
for(
var i=
0;i<arr.length;i++){
//行
-
for(
var j=
0;j<arr[i].length;j++){
//列
-
cell = arr[i][j];
-
if(cell.isPoint(pos)&& !cell.
open){
//鼠标捕捉,被打开的同样不捕获
-
if(!cell.state){
//打上标记的不做处理
-
cell.image=
this.imgObj[
'common'][
11];
-
}
-
}
else{
-
if(!cell.state){
//打上标记的不做处理
-
cell.image=
this.imgObj[
'common'][
10];
-
}
-
}
-
}
-
}
-
this.render();
-
}
-
}
加入鼠标点击事件
-
//鼠标点击事件
-
Saolei.prototype.mouseClick=function(e){
-
if(
this.endAnimate)
return ;
//结束动画的时候不许点击
-
-
var pos = _.getOffset(e);
//获取鼠标位置
-
if(
this.reStartObj.isPoint(pos)){
//重新开始被点击
-
this.restart();
-
return ;
-
}
-
if(
this.end)
return ;
//结束了已经
-
-
//循环遮罩数组
-
var arr =
this.overArr,cell,cellArr=
this.gridArr;
-
for(
var i=
0;i<arr.length;i++){
//行
-
for(
var j=
0;j<arr[i].length;j++){
//列
-
cell = arr[i][j];
-
if(cell.isPoint(pos) && !cell.
open){
//鼠标捕捉,被打开的同样不捕获
-
if(!
this.start){
-
doStart.call(
this);
-
}
-
//移出当前对象
-
cell.
open=
true;
//被打开
-
this.clearAssign(
this.renderArr,cell);
-
//获取对应的格子对象
-
var item = cellArr[i][j];
-
if(item.type==
1){
//如果是雷,则显示爆炸动画并提示失败
-
this.boom(item);
-
}
else{
//如不是雷
-
if(item.count==
0){
//判断周围雷数量,如果是0则打开周围,并依次递归
-
this.openOver(cell.i,cell.j);
-
}
-
//判断是否达到胜利的条件
-
this.successOrNot();
-
}
-
}
-
}
-
}
-
-
this.render();
-
-
function doStart(){
-
this.start=
true;
-
this.timmer = setInterval(function(){
-
this.timmerCount++;
-
this.timeCountObj.text=
this.timmerCount;
-
this.render();
-
}.bind(
this),
1000);
-
}
-
}
成功判定1
未打开的数量与雷的数量相同
-
//判断是否成功
-
Saolei.prototype.successOrNot=function(cell){
-
var arr =
this.overArr,cell,count=
0;
-
for(
var i=
0;i<arr.length;i++){
//行
-
for(
var j=
0;j<arr[i].length;j++){
//列
-
cell = arr[i][j];
-
if(!cell.
open){
-
count++;
-
}
-
}
-
}
-
if(count==
this.leiSucCount){
//未打开的数量和雷的数量一样,表示成功
-
this.end=
true;
-
clearInterval(
this.timmer);
//清除计时器的定时任务
-
//打开所有的雷
-
this.openAllLei();
-
//显示成功表情(延时)
-
setTimeout(
this.endShow.bind(
this,
'suc'),
100);
-
}
-
}
成功判定2
标记为雷(插旗)的数量与类总数相同
-
//判断是否成功 -根据插红旗
-
Saolei.prototype.successOrNot2=function(cell){
-
var arr =
this.overArr,cell,count=
0,gridArr =
this.gridArr,item;
-
var count=
0;
-
for(
var i=
0;i<arr.length;i++){
//行
-
for(
var j=
0;j<arr[i].length;j++){
//列
-
cell = arr[i][j];
-
if(cell.state==
1){
//红旗
-
item=gridArr[i][j];
-
if(item.type==
1){
//正好又是雷
-
count++;
-
}
-
}
-
}
-
}
-
if(count==
this.leiSucCount){
//未打开的数量和雷的数量一样,表示成功
-
this.end=
true;
-
clearInterval(
this.timmer);
//清除计时器的定时任务
-
//打开所有的雷
-
this.openAllLei();
-
//显示成功表情(延时)
-
setTimeout(
this.endShow.bind(
this,
'suc'),
100);
-
}
-
}
触雷效果
鼠标点击雷后,会触发雷爆炸的一个动画,这是通过图片的切换来实现的
-
//爆炸效果
-
Saolei.prototype.boom=function(cell){
-
this.end=
true;
-
this.endAnimate=
true;
-
clearInterval(
this.timmer);
//清除计时器的定时任务
-
//开启爆炸的动画
-
this.timmer = setInterval(
this.boomAnimate.bind(
this,cell),
100)
-
}
-
//爆炸动画
-
Saolei.prototype.boomAnimate=function(cell){
-
//切换图片
-
cell.index = (cell.index ||
0)+
1;
-
if(cell.index>
this.boomCount){
-
//结束动画
-
clearInterval(
this.timmer);
-
//因为图片有些不一样,需要修正一下
-
cell.sWidth=
79;
-
cell.sHeight=
79;
-
cell.image=
this.imgObj[
'common'][
18];
-
//打开所有的雷
-
this.openAllLei();
-
//显示失败表情(延时)
-
setTimeout(
this.endShow.bind(
this),
100);
-
this.endAnimate=
false;
-
this.render();
-
return ;
-
}
-
//因为图片有些不一样,需要修正一下
-
cell.sWidth=
61;
-
cell.sHeight=
53;
-
cell.image=
this.imgObj[
'boom'][cell.index];
-
this.render();
-
}
加入鼠标右键事件
此事件是做插旗或者标记为未知等操作的。
-
//右键事件
-
Saolei.prototype.contextMenu=function(e){
-
if(
this.end)
return ;
//结束了已经
-
var e = e||window.event;
-
//取消右键默认事件
-
e.preventDefault && e.preventDefault();
-
-
var pos = _.getOffset(e);
//获取鼠标位置
-
//循环遮罩数组
-
var arr =
this.overArr,cell,cellArr=
this.gridArr;
-
for(
var i=
0;i<arr.length;i++){
//行
-
for(
var j=
0;j<arr[i].length;j++){
//列
-
cell = arr[i][j];
-
if(cell.isPoint(pos) && !cell.
open){
//鼠标捕捉,被打开的同样不捕获
-
//右键切换
-
if(!cell.state){
//如果是没有状态的,则标记为雷,小旗
-
cell.state=
1;
-
cell.image=
this.imgObj[
'common'][
12];
-
this.leiCount--;
-
this.leiCountObj.text=
this.leiCount;
-
//判断如果小旗数量和数据都对上了,也判断为成功
-
this.successOrNot2(cell);
-
}
else
if(cell.state==
1){
//如果状态为雷的,标记为未知,问号
-
cell.state=
2;
-
cell.image=
this.imgObj[
'common'][
13];
-
this.leiCount++;
-
this.leiCountObj.text=
this.leiCount;
-
}
else
if(cell.state==
2){
//如果状态为未知的,则现在原来的
-
cell.state=
0;
-
cell.image=
this.imgObj[
'common'][
10];
-
}
-
}
-
}
-
}
-
this.render();
-
}
最后加入重新开始事件,胜利和失败图片显示就完成了。
看到这里的大佬,动动发财的小手 点赞 + 回复 + 收藏 ,能关注一波就更好了。
源码下载
方式1:完整代码下载、需少量积分
方式2:关注下方公众号,回复 【 扫雷 】 下载代码
★ 更多源码
♥ java学生宿舍管理系统♥
♥ 学生成绩管理系统♥
♥ 基于JavaScript canvas植物大战僵尸(附源码)♥
♥ 老父亲给女儿做的下雪特效,满足女儿看雪的愿望(附源码)♥
转载:https://blog.csdn.net/dkm123456/article/details/116428719