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

本文介绍了如何使用javascript和canvas创建扫雷小游戏。作者详细阐述了实现思路,包括创建二维数组、设置雷、计算周围雷的数量、创建遮罩、处理鼠标事件等步骤,并提供了源码获取方式。
摘要由CSDN通过智能技术生成

引言:

扫雷是系统自带的经典小游戏,以前上学那会上机的时候就经常玩这个,趁着五一假期的最后一天,用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();
	}

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

 源码获取方式:
订阅我的专栏《【javascript精彩实例】》后,可以查看专栏内所有的文章,并且联系博主免费获取你心仪的源代码,专栏的文章都是上过csdn热榜的,值得信赖,【了解一下我的专栏!

热门专栏推荐:
【1】Java小游戏(俄罗斯方块、飞机大战、植物大战僵尸等)
【2】JavaWeb项目实战(图书管理、在线考试、宿舍管理等)
【3】JavaScript精彩实例(飞机大战、贪吃蛇、验证码等)
【4】Java小白入门200例
【5】从零学Java、趣学Java
【6】Idea从零到精通

评论 40
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程界小明哥

请博主喝瓶水,博主持续输出!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值