飞道的博客

基于javascript扫雷小游戏,以前上学经常玩

322人阅读  评论(0)

引言:

扫雷是系统自带的经典小游戏,以前上学那会上机的时候就经常玩这个,趁着五一假期的最后一天,用canvas编写了这个小游戏,看着还行,不知道会不会有什么我没发现的bug,难度目前是设置成简易的,如果要改难度,代码稍做修改即可。

效果图

实现思路

1.创建9行9列的二维数组。

2.设置雷:随机行数 i 和列数 j,根据随机到 i、j 从二维数组中取出对应的元素,将取到的元素设置一个属性type等于1,表示当前元素已经是雷,并且递增雷计数器,然后递归调用;如果取到的元素已经是雷了,则跳过继续执行,雷计数器达到设定的最大值就跳出递归。

3.计算每个元素周围的雷数量(周围指的是 左上、上、右上、右、右下、下、左下、左 这8个位置),当前位置显示对应的数字(待会内容里面细说)

4.同样根据这个二维数组来创建遮罩的小方块,正好盖住之前创建的图形。

5.点击这个遮罩的小方块则触发揭开,揭开后根据对应的数字或者雷做不同的操作。

代码实现

创建背景及相关元素


  
  1. //绘制背景及相关默认元素
  2. Saolei.prototype.drawBG= function(){
  3. var image,img,sx= 0,sy= 0,sWidth= 141,sHeight= 54,dx= 20,dy= 340,dWidth= 141,dHeight= 54;
  4. //计时
  5. image = this.imgObj[ 'common'][ 15];
  6. img = new _.ImageDraw({ image:image, sx:sx, sy:sy, sWidth:sWidth, sHeight:sHeight, dx:dx, dy:dy , dWidth:dWidth, dHeight:dHeight});
  7. this.renderArr.push(img);
  8. sx= 0,sy= 0,sWidth= 141,sHeight= 52,dx= 180,dy= 340,dWidth= 141,dHeight= 52;
  9. //计雷
  10. image = this.imgObj[ 'common'][ 14];
  11. img = new _.ImageDraw({ image:image, sx:sx, sy:sy, sWidth:sWidth, sHeight:sHeight, dx:dx, dy:dy , dWidth:dWidth, dHeight:dHeight});
  12. this.renderArr.push(img);
  13. //创建一个方形区域
  14. var rect = new _.Rect({
  15. x: 24,
  16. y: 44,
  17. width: 289,
  18. height: 289,
  19. stroke: true
  20. })
  21. this.renderArr.push(rect);
  22. sx= 0,sy= 0,sWidth= 100,sHeight= 40,dx= 120,dy= 2,dWidth= 100,dHeight= 40;
  23. //重新开始按钮
  24. image = this.imgObj[ 'common'][ 21];
  25. img = new _.ImageDraw({ image:image, sx:sx, sy:sy, sWidth:sWidth, sHeight:sHeight, dx:dx, dy:dy , dWidth:dWidth, dHeight:dHeight});
  26. this.renderArr.push(img);
  27. this.reStartObj=img;
  28. }

创建雷和显示对应的图片

1.随机row 和 col,并从二维数组中获取到这个对象;
2.判断他是否是雷,如果是则跳过当前;
3.如果当前不是雷,则标记当前对象为雷对象,并且更改图片;
4.递归,当达到设定的数量时跳出。


  
  1. //创建被遮盖
  2. Saolei.prototype.createUnder=function(){
  3. var image,img,sx= 0,sy= 0,sWidth= 79,sHeight= 79,dx= 0,dy= 0,dWidth= 32,dHeight= 32;
  4. var rows = this.rows; //行
  5. var cols = this.cols; //列
  6. image = this.imgObj[ 'common'][ 9];
  7. //二维网格数组
  8. var gridArr=[];
  9. var arr = this.gridArr,cell;
  10. for( var i= 0;i<rows;i++){ //行
  11. dy = 45+i*dHeight;
  12. gridArr[i]=[];
  13. for( var j= 0;j<cols;j++){ //列
  14. dx = 25+j*dWidth;
  15. img = new _.ImageDraw({image:image,sx:sx,sy:sy,sWidth:sWidth,sHeight:sHeight, dx:dx, dy:dy ,dWidth:dWidth,dHeight:dHeight});
  16. img.type= 0;
  17. this.renderArr.push(img);
  18. gridArr[i][j]=img;
  19. }
  20. }
  21. this.gridArr=gridArr;
  22. //创建雷
  23. this.createLei();
  24. }
  25. //创建雷
  26. Saolei.prototype.createLei=function(){
  27. //当达到设定的数量时跳出
  28. if( this.leiMaxCount<= 0) {
  29. return ;
  30. }
  31. var arr = this.gridArr;
  32. /*
  33. 1.随机row 和 col,并从二维数组中获取到这个对象
  34. 2.判断他是否是雷,如果是则跳过当前
  35. 3.如果当前不是雷,则标记当前对象为雷对象,并且更改图片
  36. 4.递归,当达到设定的数量时跳出
  37. */
  38. var row = _.getRandom( 0, this.rows);
  39. var col = _.getRandom( 0, this.cols);
  40. var cell = arr[row][col];
  41. if(cell.type== 0){
  42. //标记为雷
  43. cell.type= 1;
  44. cell.image = this.imgObj[ 'common'][ 18];
  45. this.leiMaxCount--;
  46. console.log(row,col);
  47. }
  48. //递归
  49. this.createLei();
  50. }

计算周围雷的数量并显示

1.循环之前定义的二维数组

2.如果当前元素的下标是(i,j),则左上为(i-1,j-1),上为(i-1,j ),右上为(i-1,j+1),以此类推,如下图所示:

3.分别取出这些元素,并判断他们是不是雷,如果是则计数累加,最后将计数对应到相应的图片,然后显示出来。


  
  1. //计算周边雷的数量并更改对象的相关参数
  2. Saolei.prototype.computedLei=function(){
  3. var arr = this.gridArr,cell;
  4. for( var i= 0;i<arr.length;i++){ //行
  5. for( var j= 0;j<arr[i].length;j++){ //列
  6. cell = arr[i][j];
  7. if(cell.type== 1){ //当前是雷则直接跳过
  8. continue;
  9. }
  10. var count= 0;
  11. //左上
  12. var ci = i- 1,cj = j- 1,ccell;
  13. if(ci>= 0 && cj>= 0){
  14. ccell = arr[ci][cj];
  15. if(ccell.type== 1){
  16. count++;
  17. }
  18. }
  19. //上
  20. ci = i- 1,cj = j,ccell;
  21. if(ci>= 0 && cj>= 0){
  22. ccell = arr[ci][cj];
  23. if(ccell.type== 1){
  24. count++;
  25. }
  26. }
  27. //右上
  28. ci = i- 1,cj = j+ 1,ccell;
  29. if(ci>= 0 && cj< this.cols){
  30. ccell = arr[ci][cj];
  31. if(ccell.type== 1){
  32. count++;
  33. }
  34. }
  35. //右
  36. ci = i,cj = j+ 1,ccell;
  37. if(cj< this.cols){
  38. ccell = arr[ci][cj];
  39. if(ccell.type== 1){
  40. count++;
  41. }
  42. }
  43. //右下
  44. ci = i+ 1,cj = j+ 1,ccell;
  45. if(ci< this.rows && cj< this.cols){
  46. ccell = arr[ci][cj];
  47. if(ccell.type== 1){
  48. count++;
  49. }
  50. }
  51. //下
  52. ci = i+ 1,cj = j,ccell;
  53. if(ci< this.rows){
  54. ccell = arr[ci][cj];
  55. if(ccell.type== 1){
  56. count++;
  57. }
  58. }
  59. //左下
  60. ci = i+ 1,cj = j- 1,ccell;
  61. if(ci< this.rows && cj >= 0){
  62. ccell = arr[ci][cj];
  63. if(ccell.type== 1){
  64. count++;
  65. }
  66. }
  67. //左
  68. ci = i,cj = j- 1,ccell;
  69. if(cj >= 0){
  70. ccell = arr[ci][cj];
  71. if(ccell.type== 1){
  72. count++;
  73. }
  74. }
  75. //设定周围雷的数量
  76. cell.count=count;
  77. if(count== 0){ //因为0那张图片下标用的9
  78. count= 9;
  79. }
  80. //更换图片
  81. cell.image = this.imgObj[ 'common'][count];
  82. }
  83. }
  84. }

创建遮罩


  
  1. //创建遮盖
  2. Saolei.prototype.createOver=function(){
  3. var image,img,sx= 0,sy= 0,sWidth= 79,sHeight= 79,dx= 0,dy= 0,dWidth= 32,dHeight= 32;
  4. image = this.imgObj[ 'common'][ 10];
  5. var arr = this.gridArr;
  6. for(var i= 0;i<arr.length;i++){ //行
  7. this.overArr[i]=[];
  8. for(var j= 0;j<arr[i].length;j++){ //列
  9. dy = 45+i*dHeight;
  10. dx = 25+j*dWidth;
  11. img = new _.ImageDraw({ image:image, sx:sx, sy:sy, sWidth:sWidth, sHeight:sHeight, dx:dx, dy:dy , dWidth:dWidth, dHeight:dHeight});
  12. img.i=i,img.j=j;
  13. this.renderArr.push(img);
  14. this.overArr[i][j]=img;
  15. }
  16. }
  17. }

创建计时和计数器


  
  1. //创建计数和计时器
  2. Saolei.prototype.createCount=function(){
  3. //计时器
  4. var x= 115,y= 382,content= 0;
  5. var text = new _.Text({
  6. x:x,
  7. y:y,
  8. text:content,
  9. font: '26px ans-serif',
  10. textAlign: 'center',
  11. fill: true,
  12. fillStyle: 'white'
  13. });
  14. this.renderArr.push(text);
  15. this.timeCountObj=text;
  16. x= 222,y= 382,content= this.leiCount;
  17. var text = new _.Text({
  18. x:x,
  19. y:y,
  20. text:content,
  21. font: '26px ans-serif',
  22. textAlign: 'center',
  23. fill: true,
  24. fillStyle: 'white'
  25. });
  26. this.renderArr.push(text);
  27. this.leiCountObj=text;
  28. }

加入鼠标移动事件


  
  1. //鼠标移动事件
  2. Saolei.prototype.mouseMove=function(e){
  3. if( this.endAnimate) return ;
  4. var pos = _.getOffset(e); //获取鼠标位置
  5. var isCatch= false;
  6. if( this.reStartObj.isPoint(pos)){
  7. this.el.style.cursor = 'pointer'; //改为手状形态
  8. } else{
  9. this.el.style.cursor = ''; //改为普通形态
  10. }
  11. if( this.end) return ; //结束了已经
  12. if(!isCatch){
  13. //循环遮罩数组
  14. var arr = this.overArr,cell;
  15. for( var i= 0;i<arr.length;i++){ //行
  16. for( var j= 0;j<arr[i].length;j++){ //列
  17. cell = arr[i][j];
  18. if(cell.isPoint(pos)&& !cell. open){ //鼠标捕捉,被打开的同样不捕获
  19. if(!cell.state){ //打上标记的不做处理
  20. cell.image= this.imgObj[ 'common'][ 11];
  21. }
  22. } else{
  23. if(!cell.state){ //打上标记的不做处理
  24. cell.image= this.imgObj[ 'common'][ 10];
  25. }
  26. }
  27. }
  28. }
  29. this.render();
  30. }
  31. }

加入鼠标点击事件


  
  1. //鼠标点击事件
  2. Saolei.prototype.mouseClick=function(e){
  3. if( this.endAnimate) return ; //结束动画的时候不许点击
  4. var pos = _.getOffset(e); //获取鼠标位置
  5. if( this.reStartObj.isPoint(pos)){ //重新开始被点击
  6. this.restart();
  7. return ;
  8. }
  9. if( this.end) return ; //结束了已经
  10. //循环遮罩数组
  11. var arr = this.overArr,cell,cellArr= this.gridArr;
  12. for( var i= 0;i<arr.length;i++){ //行
  13. for( var j= 0;j<arr[i].length;j++){ //列
  14. cell = arr[i][j];
  15. if(cell.isPoint(pos) && !cell. open){ //鼠标捕捉,被打开的同样不捕获
  16. if(! this.start){
  17. doStart.call( this);
  18. }
  19. //移出当前对象
  20. cell. open= true; //被打开
  21. this.clearAssign( this.renderArr,cell);
  22. //获取对应的格子对象
  23. var item = cellArr[i][j];
  24. if(item.type== 1){ //如果是雷,则显示爆炸动画并提示失败
  25. this.boom(item);
  26. } else{ //如不是雷
  27. if(item.count== 0){ //判断周围雷数量,如果是0则打开周围,并依次递归
  28. this.openOver(cell.i,cell.j);
  29. }
  30. //判断是否达到胜利的条件
  31. this.successOrNot();
  32. }
  33. }
  34. }
  35. }
  36. this.render();
  37. function doStart(){
  38. this.start= true;
  39. this.timmer = setInterval(function(){
  40. this.timmerCount++;
  41. this.timeCountObj.text= this.timmerCount;
  42. this.render();
  43. }.bind( this), 1000);
  44. }
  45. }

成功判定1

未打开的数量与雷的数量相同


  
  1. //判断是否成功
  2. Saolei.prototype.successOrNot=function(cell){
  3. var arr = this.overArr,cell,count= 0;
  4. for( var i= 0;i<arr.length;i++){ //行
  5. for( var j= 0;j<arr[i].length;j++){ //列
  6. cell = arr[i][j];
  7. if(!cell. open){
  8. count++;
  9. }
  10. }
  11. }
  12. if(count== this.leiSucCount){ //未打开的数量和雷的数量一样,表示成功
  13. this.end= true;
  14. clearInterval( this.timmer); //清除计时器的定时任务
  15. //打开所有的雷
  16. this.openAllLei();
  17. //显示成功表情(延时)
  18. setTimeout( this.endShow.bind( this, 'suc'), 100);
  19. }
  20. }

成功判定2

标记为雷(插旗)的数量与类总数相同


  
  1. //判断是否成功 -根据插红旗
  2. Saolei.prototype.successOrNot2=function(cell){
  3. var arr = this.overArr,cell,count= 0,gridArr = this.gridArr,item;
  4. var count= 0;
  5. for( var i= 0;i<arr.length;i++){ //行
  6. for( var j= 0;j<arr[i].length;j++){ //列
  7. cell = arr[i][j];
  8. if(cell.state== 1){ //红旗
  9. item=gridArr[i][j];
  10. if(item.type== 1){ //正好又是雷
  11. count++;
  12. }
  13. }
  14. }
  15. }
  16. if(count== this.leiSucCount){ //未打开的数量和雷的数量一样,表示成功
  17. this.end= true;
  18. clearInterval( this.timmer); //清除计时器的定时任务
  19. //打开所有的雷
  20. this.openAllLei();
  21. //显示成功表情(延时)
  22. setTimeout( this.endShow.bind( this, 'suc'), 100);
  23. }
  24. }

触雷效果

鼠标点击雷后,会触发雷爆炸的一个动画,这是通过图片的切换来实现的


  
  1. //爆炸效果
  2. Saolei.prototype.boom=function(cell){
  3. this.end= true;
  4. this.endAnimate= true;
  5. clearInterval( this.timmer); //清除计时器的定时任务
  6. //开启爆炸的动画
  7. this.timmer = setInterval( this.boomAnimate.bind( this,cell), 100)
  8. }
  9. //爆炸动画
  10. Saolei.prototype.boomAnimate=function(cell){
  11. //切换图片
  12. cell.index = (cell.index || 0)+ 1;
  13. if(cell.index> this.boomCount){
  14. //结束动画
  15. clearInterval( this.timmer);
  16. //因为图片有些不一样,需要修正一下
  17. cell.sWidth= 79;
  18. cell.sHeight= 79;
  19. cell.image= this.imgObj[ 'common'][ 18];
  20. //打开所有的雷
  21. this.openAllLei();
  22. //显示失败表情(延时)
  23. setTimeout( this.endShow.bind( this), 100);
  24. this.endAnimate= false;
  25. this.render();
  26. return ;
  27. }
  28. //因为图片有些不一样,需要修正一下
  29. cell.sWidth= 61;
  30. cell.sHeight= 53;
  31. cell.image= this.imgObj[ 'boom'][cell.index];
  32. this.render();
  33. }

加入鼠标右键事件

此事件是做插旗或者标记为未知等操作的。


  
  1. //右键事件
  2. Saolei.prototype.contextMenu=function(e){
  3. if( this.end) return ; //结束了已经
  4. var e = e||window.event;
  5. //取消右键默认事件
  6. e.preventDefault && e.preventDefault();
  7. var pos = _.getOffset(e); //获取鼠标位置
  8. //循环遮罩数组
  9. var arr = this.overArr,cell,cellArr= this.gridArr;
  10. for( var i= 0;i<arr.length;i++){ //行
  11. for( var j= 0;j<arr[i].length;j++){ //列
  12. cell = arr[i][j];
  13. if(cell.isPoint(pos) && !cell. open){ //鼠标捕捉,被打开的同样不捕获
  14. //右键切换
  15. if(!cell.state){ //如果是没有状态的,则标记为雷,小旗
  16. cell.state= 1;
  17. cell.image= this.imgObj[ 'common'][ 12];
  18. this.leiCount--;
  19. this.leiCountObj.text= this.leiCount;
  20. //判断如果小旗数量和数据都对上了,也判断为成功
  21. this.successOrNot2(cell);
  22. } else if(cell.state== 1){ //如果状态为雷的,标记为未知,问号
  23. cell.state= 2;
  24. cell.image= this.imgObj[ 'common'][ 13];
  25. this.leiCount++;
  26. this.leiCountObj.text= this.leiCount;
  27. } else if(cell.state== 2){ //如果状态为未知的,则现在原来的
  28. cell.state= 0;
  29. cell.image= this.imgObj[ 'common'][ 10];
  30. }
  31. }
  32. }
  33. }
  34. this.render();
  35. }

最后加入重新开始事件,胜利和失败图片显示就完成了。

看到这里的大佬,动动发财的小手 点赞 + 回复 + 收藏 ,能关注一波就更好了。

源码下载

方式1:完整代码下载、需少量积分

方式2:关注下方公众号,回复 【 扫雷 】 下载代码

 更多源码

♥ java学生宿舍管理系统

♥ 学生成绩管理系统

♥ 基于JavaScript canvas植物大战僵尸(附源码)

♥ 老父亲给女儿做的下雪特效,满足女儿看雪的愿望(附源码)

♥ 基于canvas的九宫格抽奖特效(附源码)

♥ 抖音很火的华为太空人表盘(附源码)

♥ 基于JavaScript页面动态验证码(附源码)

♥ 基于JavaScript的俄罗斯方块小游戏(附源码)

♥ 基于JavaScript的贪吃蛇游戏(附源码)

 


转载:https://blog.csdn.net/dkm123456/article/details/116428719
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场