这篇文章主要介绍“如何利用js+canvas实现扫雷游戏”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“如何利用js+canvas实现扫雷游戏”文章能帮助大家解决问题。
代码如下
<body>
胜利条件,找出所有地雷并标记
<form action="javaScript:createContent()">
<div id="message" >地雷数量必须小于地图大小xy的平方</div>
<br />
地图大小xy :<input id="xyNum" type="number" required="true" name="points" min="1" max="50" />
booNum:<input id="booNum" type="number" required="true" name="points" min="1" max="2500"/>
<input type="submit" value="OK" : />
<br /> 1. 输入宽度 <br />2. 输入地雷数(地雷数小于宽*宽) <br /> 3. 单击确定 <br />
鼠标右键:<br />
第一次:标记您的猜测<br />
第二次: 取消标签<br />
</form>
<div id= 'game'>
</div>
<script src="./js/MarkObs.js"></script>
<script src="./js/Isboo.js"></script>
<script src="./js/lei.js"></script>
<script>
let xy = document.getElementById('xyNum');
let boo = document.getElementById('booNum');
let meg = document.getElementById("message");
let div = document.getElementById('game');
//获取输入的宽高和地雷数
createContent = function (){
// console.log(xy.value);
// console.log(boo.value);
let xyNum = xy.value;
let booNum = boo.value;
// console.log(Math.pow(xyNum,2));
//判断输入是否合法
if(Math.pow(xyNum,2)<boo.value){
meg.style.display = 'block';
}
else {//绘制地图
div.innerHTML = '';//清除上次div里的地图
let game = new Game('game',xyNum,booNum);
}
}
</script>
</body>
lei.js
/* 一个自定义原型数组方法 可以放到html里
二维数组查找
arr:要找数组第一第二项 找到返回下标,没有返回-1
PS:只要this数组和arr数组第一第二项的值相等,即为找到。
*/
Array.prototype.myindexOf = function(arr){
for(let i=0;i<this.length;i++){
if((this[i][0] == arr[0]) &&(this[i][1]==arr[1])){
return i;
}
}
return -1;
}
/*
初始化地雷图
id:传入绘制地图的容器id
xyNum:长||宽的格子数(地图固定正方形)
booNum:地雷数
*/
class Game {
constructor(id,xyNum,booNum){
this.xyNum = xyNum;
this.booNum = booNum;
this.id = id;
this.booArrs = [];//保存雷的位置
this.boox = -1;//地雷在x轴第几个块
this.booy = -1;//地雷在x轴第几个块
this.numArrs = [];//保存提醒数字的位置以及数字
this.num = 0;//保存找到的提醒数字的个数
this.markArrs = [];//保存标记位置的数组
//单个块的宽高
this.divw = 20;
// 初始化画布
this.initCanvas(xyNum);
// 初始化地雷位置(地雷用-1代替,图片绘制麻烦)
this.initBooxy(xyNum,booNum);
// 初始化遮挡物
this.initObs(xyNum);
//判断是否胜利
this.win();
}
/*初始化画布(包括网格)
xyNum:传入需要绘制的宽格子数
*/
initCanvas(xyNum){
this.canvas = document.createElement('canvas');
this.ctx = this.canvas.getContext('2d');
//1为border
this.canvas.width = (this.divw+1)*xyNum;
this.canvas.height = (this.divw+1)*xyNum;
// 绘制网格坐标
// 获取canvas的宽高;
let w = this.canvas.width;
let h = this.canvas.height;
// 绘制水平线
for (let i = 1; i < h / 21; i++) {
this.ctx.beginPath();
this.ctx.lineTo(0, 21 * i) //起点
this.ctx.lineTo(w, 21 * i); //重点
this.ctx.stroke();
}
// h绘制垂直线
for (let i = 1; i < w / 21; i++) {
this.ctx.beginPath();
this.ctx.lineTo(21 * i,0) //起点
this.ctx.lineTo(21 * i,h); //重点
this.ctx.stroke();
}
// ctx.stroke();
// 放入容器
this.div = document.getElementById(this.id);
this.div.appendChild(this.canvas);
// 绑定点击事件!!!
this.canvas.addEventListener('mousedown',this.mouseDown.bind(this))//!!!!注意需要更改this指向,用bind
// 清除鼠标右键的默认事件 “contextmenu“
this.canvas.addEventListener("contextmenu",function(event){
event.preventDefault()
})
}
/*初始化地雷(包括提醒数字)
xyNum:传入地图的宽的格子数
booNum:传入地雷数
*/
initBooxy (xyNum,booNum){
// 随机地雷位置 并保存起来
for(let i=0;i<booNum;i++){
// x,y为地雷所在格子坐标,从0开始
this.boox = parseInt(Math.random()*xyNum);
this.booy = parseInt(Math.random()*xyNum);
//避免雷的位置重复
while(this.booArrs.myindexOf([this.boox,this.booy])!=-1){
this.boox = parseInt(Math.random()*xyNum);
this.booy = parseInt(Math.random()*xyNum);
}
this.booArrs.push([this.boox,this.booy])//!!!保存地雷的位置
console.log(i,'x:'+this.boox,'y:'+this.booy);
//绘制地雷
this.ctx.beginPath();//不清楚可不可以删
this.ctx.rect(this.boox*21,this.booy*21,20,20);
this.ctx.fillStyle = 'red';
this.ctx.fill();
}
// 绘制地雷位置周围提醒数字
// 这里的逻辑可以优化,不提前绘制数字,在点击清除障碍物后再判断绘制。
/*
想法一:在每个雷周围添加数字1,如果在多个雷交集处累加
想法二:所有块依次判断周围是否有雷,有几个雷,就fillText()多少
想法三:(一二结合)先找每个雷,该雷周围的8个块依次 判断周围有几个雷
*/
// 这里为法二
for(let i=0;i<xyNum;i++){
for(let j=0;j<xyNum;j++){
let num = 0;//提醒数字 ,每次重置为0
if(this.booArrs.myindexOf([i-1,j-1]) !=-1){
num++;
}
if(this.booArrs.myindexOf([i-1,j]) !=-1){
num++;
}
if(this.booArrs.myindexOf([i-1,j+1]) !=-1){
num++;
}
if(this.booArrs.myindexOf([i,j-1]) !=-1){
num++;
}
if(this.booArrs.myindexOf([i,j+1]) !=-1){
num++;
}
if(this.booArrs.myindexOf([i+1,j-1]) !=-1){
num++;
}
if(this.booArrs.myindexOf([i+1,j]) !=-1){
num++;
}
if(this.booArrs.myindexOf([i+1,j+1]) !=-1){
num++;
}
//绘制提醒数字
if(num!=0 && (this.booArrs.myindexOf([i,j]) ==-1 )){//(this.booArrs.myindexOf([i,j]) ==-1)地雷不标注提示数字若。要标注需要+1(本身)
this.ctx.font = '18px fasdg'
this.ctx.fillStyle = '#000'
this.ctx.fillText(num,i*(this.divw+1)+2,(j+1)*(this.divw+1)-2);//加1和j+1为测试结果,-+2是为了文本在格子里居中//y为文本中线坐标
this.numArrs.push([i,j,num]);//i,j为提醒数字的块坐标,num为装数组里的值(myindexOf来判断)
}
// this.NUM = num;
}
}
}
/*初始化遮挡物
xyNum:传入地图的宽的格子数
*/
initObs(xyNum){
for(let i=0;i<xyNum;i++){
for(let j=0;j<xyNum;j++){
this.ctx.beginPath();
this.ctx.rect(i*21,j*21,20,20);
// this.ctx.fillStyle = 'rgb(155,25,205,0.7)';//设置障碍物透明度可以方便查看雷的位置
this.ctx.fillStyle = 'rgb(155,25,205,1)';//正常游戏时透明度为'1‘
this.ctx.fill();
}
}
}
/*点击事件在initCanvas中绑定*/
mouseDown(){
//这里使用preventDefault,默认事件被没有消除,是因为触发鼠标右键的默认事件的事件类型不是mousedown,是contextmenu
// event.preventDefault(); //ie9以下不兼容
this.clix = Math.floor(event.layerX/( this.divw+1));//this.divw为20是块的宽
this.cliy = Math.floor(event.layerY/( this.divw+1));
// 鼠标左键
if(event.button==0){
this.clearObs(this.clix,this.cliy);
}
// 鼠标右键
else if(event.button==2){
this.markObs(this.clix,this.cliy);
}
}
/*扫雷*/ //这里的代码可以封装一下 为了方便此处没有封装
clearObs(x,y){
// console.log(x,y);点击坐标
this.ctx.clearRect(x*21,y*21,20,20);//清除指定块
// 点击到标记,点击到提醒数字,点击到地雷,点击到空白,
if(this.markArrs.myindexOf([x,y])!=-1){ //点击到标记,重新覆盖
this.ctx.rect(x*21,y*21,20,20);
this.ctx.fillStyle = 'rgb(155,25,205,1)';
this.ctx.fill();
this.ctx.beginPath();
this.ctx.fillStyle = 'red';
this.ctx.fillText('?',x*(this.divw+1)+2,(y+1)*(this.divw+1)-2);
this.ctx.fill();
}
else if(this.numArrs.myindexOf([x,y])!=-1){//点击到提醒数字
let index = this.numArrs.myindexOf([x,y]);//下标
let num = this.numArrs[index][2];//提醒数字
this.ctx.fillText(num,x*(this.divw+1)+2,(y+1)*(this.divw+1)-2);//加1和j+1为测试结果,-+2是为了文本在格子里居中//y为文本中线坐标
this.num++;
}
else if(this.booArrs.myindexOf([x,y])!=-1){//,点击到地雷,全部绘制
console.log(this.booArrs.myindexOf([x,y]));
//绘制全图
// 绘制提醒数字
for(let i=0;i<this.xyNum;i++){
for(let j=0;j<this.xyNum;j++){
let num = 0;//提醒数字 ,每次重置为0
// if(booArrs.indexof([i-1,j-1]) != -1){//数组是对象这样永远-1
this.ctx.clearRect(i*21,j*21,20,20);
if(this.booArrs.myindexOf([i-1,j-1]) !=-1){
num++;
}
if(this.booArrs.myindexOf([i-1,j]) !=-1){
num++;
}
if(this.booArrs.myindexOf([i-1,j+1]) !=-1){
num++;
}
if(this.booArrs.myindexOf([i,j-1]) !=-1){
num++;
}
if(this.booArrs.myindexOf([i,j+1]) !=-1){
num++;
}
if(this.booArrs.myindexOf([i+1,j-1]) !=-1){
num++;
}
if(this.booArrs.myindexOf([i+1,j]) !=-1){
num++;
}
if(this.booArrs.myindexOf([i+1,j+1]) !=-1){
num++;
}
//绘制提醒数字
if(num!=0 && (this.booArrs.myindexOf([i,j]) ==-1 )){//(this.booArrs.myindexOf([i,j]) ==-1)地雷不标注提示数字若要标注需要+1(本身)
this.ctx.font = '18px fasdg'
this.ctx.fillStyle = '#000'
this.ctx.fillText(num,i*(this.divw+1)+2,(j+1)*(this.divw+1)-2);//加1和j+1为测试结果,-+2是为了文本在格子里居中//y为文本中线坐标
this.numArrs.push([i,j,num]);//i,j为提醒数字的块坐标,num为装数组里的值(myindexOf来判断)
}
// this.NUM = num;
}
}
// 绘制地雷
for(let i=0;i<this.booArrs.length;i++){
this.ctx.fillStyle = 'red';
this.ctx.rect(this.booArrs[i][0]*21,this.booArrs[i][1]*21,20,20);
this.ctx.fill();
}
this.ctx.clearRect((this.xyNum-1)*21,(this.xyNum-1)*21,20,20);//每次最后一个都会变红,不知道原因,此处专门删除。
alert('你惊动了雷雷');
}
else {
this.isboo(this.ctx,x,y,this.booArrs,this.numArrs,this.markArrs,this.xyNum);
}
}
win (){//标记数组==地雷数组
this.tim = setInterval(()=>{
if(this.booArrs.length ==this.markArrs.length){
for(let i=0;i<this.booNum;i++){
if( true == this.booArrs.some(()=>{
return this.markArrs.myindexOf(this.booArrs[i])!=-1;
})){
this.booNum--;
}
if(this.booNum==0){
clearInterval(this.tim);
alert('you are win');
}
}
}
},10)
}
isboo(ctx,x,y,booArrs,numArrs,markArrs,xyNum){
new Isboo(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
}
/*标记
*/
markObs(x,y){
console.log(x,y);
new MarkObs(this.ctx,x,y,this.booArrs,this.divw,this.markArrs);
}
}
isboo.js
Array.prototype.myindexOf = function(arr){
for(let i=0;i<this.length;i++){
if((this[i][0] == arr[0]) &&(this[i][1]==arr[1])){
return i;
}
}
return -1;
}
/*
这里解决点击到空白格子时,把周围的空白格一起显示。此处的逻辑可以再优化.
ctx:布局
x,点击位置
y,点击位置
booArrs:炸弹的位置数组
numArrs:提示数的位置
markArrs:标记的位置
*/
class Isboo {
constructor(ctx,x,y,booArrs,numArrs,markArrs,xyNum){
this.x = x;
this.y = y;
// 判断有没有提醒数字
this.isbool(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
this.isboor(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
this.isboot(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
this.isboob(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
}
isbool(ctx,x,y,booArrs,numArrs,markArrs,xyNum){
if((numArrs.myindexOf([x,y])==-1)&&(x<xyNum)&&(markArrs.myindexOf([x,y])==-1)){
ctx.clearRect(x*21,y*21,20,20);
x+=1;
this.isbool(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
// this.isboor(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
this.isboot(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
this.isboob(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
}else {
return ;
}
}
isboor(ctx,x,y,booArrs,numArrs,markArrs,xyNum){
if((numArrs.myindexOf([x,y])==-1)&&(x>=0)&&(markArrs.myindexOf([x,y])==-1)){
ctx.clearRect(x*21,y*21,20,20);
x-=1;
// this.isbool(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
this.isboor(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
this.isboot(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
this.isboob(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
}else {
return ;
}
}
isboot(ctx,x,y,booArrs,numArrs,markArrs,xyNum){
if((numArrs.myindexOf([x,y])==-1)&&(y<xyNum)&&(markArrs.myindexOf([x,y])==-1)){
ctx.clearRect(x*21,y*21,20,20);
y+=1;
// this.isbool(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
// this.isboor(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
this.isboot(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
// this.isboob(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
}else {
return ;
}
}
isboob(ctx,x,y,booArrs,numArrs,markArrs,xyNum){
if((numArrs.myindexOf([x,y])==-1)&&(y>=0)&&(markArrs.myindexOf([x,y])==-1)){
ctx.clearRect(x*21,y*21,20,20);
y-=1;
// this.isbool(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
// this.isboor(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
// this.isboot(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
this.isboob(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
}else {
return ;
}
}
}
MarkObs.js
Array.prototype.myindexOf = function(arr){
for(let i=0;i<this.length;i++){
if((this[i][0] == arr[0]) &&(this[i][1]==arr[1])){
return i;
}
}
return -1;
}
/*
ctx:布局
x,点击位置
y,点击位置
booArrs:炸弹的位置数组
divw:各自宽度
markarrs:标记数组
*/
class MarkObs{
constructor(ctx,x,y,booArrs,divw,markarrs){
this.markObs(ctx,x,y,booArrs,divw,markarrs);
}
markObs(ctx,x,y,booArrs,divw,markarrs){
if(markarrs.myindexOf([x,y])==-1){//如果标记数组里没有该地址,则标记,并添加进数组
ctx.beginPath();
ctx.fillStyle = 'red';
ctx.fillText('?',x*(divw+1)+2,(y+1)*(divw+1)-2);
markarrs.push([x,y]);
}else {//如果标记数组里有该地址,则取消标记,并从数组中删除
ctx.clearRect(x*(divw+1),y*(divw+1),divw,divw);
ctx.beginPath();
ctx.rect(x*21,y*21,20,20);
ctx.fillStyle = 'rgb(155,25,205,1)';
ctx.fill();
markarrs.splice((markarrs.myindexOf([x,y])),1);
}
}
}
页面效果
初始化障碍物设置了透明度时
正常游戏时
这里点击右键标记后忘了把填充颜色设置回来。所以后面变红。
关于“如何利用js+canvas实现扫雷游戏”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识,可以关注亿速云行业资讯频道,小编每天都会为大家更新不同的知识点。
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。