您好,登錄后才能下訂單哦!
這篇文章主要介紹“如何利用js+canvas實(shí)現(xiàn)掃雷游戲”的相關(guān)知識(shí),小編通過實(shí)際案例向大家展示操作過程,操作方法簡單快捷,實(shí)用性強(qiáng),希望這篇“如何利用js+canvas實(shí)現(xiàn)掃雷游戲”文章能幫助大家解決問題。
代碼如下
<body> 勝利條件,找出所有地雷并標(biāo)記 <form action="javaScript:createContent()"> <div id="message" >地雷數(shù)量必須小于地圖大小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. 輸入地雷數(shù)(地雷數(shù)小于寬*寬) <br /> 3. 單擊確定 <br /> 鼠標(biāo)右鍵:<br /> 第一次:標(biāo)記您的猜測<br /> 第二次: 取消標(biāo)簽<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'); //獲取輸入的寬高和地雷數(shù) 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
/* 一個(gè)自定義原型數(shù)組方法 可以放到html里 二維數(shù)組查找 arr:要找數(shù)組第一第二項(xiàng) 找到返回下標(biāo),沒有返回-1 PS:只要this數(shù)組和arr數(shù)組第一第二項(xiàng)的值相等,即為找到。 */ 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:長||寬的格子數(shù)(地圖固定正方形) booNum:地雷數(shù) */ class Game { constructor(id,xyNum,booNum){ this.xyNum = xyNum; this.booNum = booNum; this.id = id; this.booArrs = [];//保存雷的位置 this.boox = -1;//地雷在x軸第幾個(gè)塊 this.booy = -1;//地雷在x軸第幾個(gè)塊 this.numArrs = [];//保存提醒數(shù)字的位置以及數(shù)字 this.num = 0;//保存找到的提醒數(shù)字的個(gè)數(shù) this.markArrs = [];//保存標(biāo)記位置的數(shù)組 //單個(gè)塊的寬高 this.divw = 20; // 初始化畫布 this.initCanvas(xyNum); // 初始化地雷位置(地雷用-1代替,圖片繪制麻煩) this.initBooxy(xyNum,booNum); // 初始化遮擋物 this.initObs(xyNum); //判斷是否勝利 this.win(); } /*初始化畫布(包括網(wǎng)格) xyNum:傳入需要繪制的寬格子數(shù) */ 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; // 繪制網(wǎng)格坐標(biāo) // 獲取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) //起點(diǎn) this.ctx.lineTo(w, 21 * i); //重點(diǎn) this.ctx.stroke(); } // h繪制垂直線 for (let i = 1; i < w / 21; i++) { this.ctx.beginPath(); this.ctx.lineTo(21 * i,0) //起點(diǎn) this.ctx.lineTo(21 * i,h); //重點(diǎn) this.ctx.stroke(); } // ctx.stroke(); // 放入容器 this.div = document.getElementById(this.id); this.div.appendChild(this.canvas); // 綁定點(diǎn)擊事件!!! this.canvas.addEventListener('mousedown',this.mouseDown.bind(this))//!!!!注意需要更改this指向,用bind // 清除鼠標(biāo)右鍵的默認(rèn)事件 “contextmenu“ this.canvas.addEventListener("contextmenu",function(event){ event.preventDefault() }) } /*初始化地雷(包括提醒數(shù)字) xyNum:傳入地圖的寬的格子數(shù) booNum:傳入地雷數(shù) */ initBooxy (xyNum,booNum){ // 隨機(jī)地雷位置 并保存起來 for(let i=0;i<booNum;i++){ // x,y為地雷所在格子坐標(biāo),從0開始 this.boox = parseInt(Math.random()*xyNum); this.booy = parseInt(Math.random()*xyNum); //避免雷的位置重復(fù) 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(); } // 繪制地雷位置周圍提醒數(shù)字 // 這里的邏輯可以優(yōu)化,不提前繪制數(shù)字,在點(diǎn)擊清除障礙物后再判斷繪制。 /* 想法一:在每個(gè)雷周圍添加數(shù)字1,如果在多個(gè)雷交集處累加 想法二:所有塊依次判斷周圍是否有雷,有幾個(gè)雷,就fillText()多少 想法三:(一二結(jié)合)先找每個(gè)雷,該雷周圍的8個(gè)塊依次 判斷周圍有幾個(gè)雷 */ // 這里為法二 for(let i=0;i<xyNum;i++){ for(let j=0;j<xyNum;j++){ let num = 0;//提醒數(shù)字 ,每次重置為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++; } //繪制提醒數(shù)字 if(num!=0 && (this.booArrs.myindexOf([i,j]) ==-1 )){//(this.booArrs.myindexOf([i,j]) ==-1)地雷不標(biāo)注提示數(shù)字若。要標(biāo)注需要+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為測試結(jié)果,-+2是為了文本在格子里居中//y為文本中線坐標(biāo) this.numArrs.push([i,j,num]);//i,j為提醒數(shù)字的塊坐標(biāo),num為裝數(shù)組里的值(myindexOf來判斷) } // this.NUM = num; } } } /*初始化遮擋物 xyNum:傳入地圖的寬的格子數(shù) */ 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)';//設(shè)置障礙物透明度可以方便查看雷的位置 this.ctx.fillStyle = 'rgb(155,25,205,1)';//正常游戲時(shí)透明度為'1‘ this.ctx.fill(); } } } /*點(diǎn)擊事件在initCanvas中綁定*/ mouseDown(){ //這里使用preventDefault,默認(rèn)事件被沒有消除,是因?yàn)橛|發(fā)鼠標(biāo)右鍵的默認(rèn)事件的事件類型不是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)); // 鼠標(biāo)左鍵 if(event.button==0){ this.clearObs(this.clix,this.cliy); } // 鼠標(biāo)右鍵 else if(event.button==2){ this.markObs(this.clix,this.cliy); } } /*掃雷*/ //這里的代碼可以封裝一下 為了方便此處沒有封裝 clearObs(x,y){ // console.log(x,y);點(diǎn)擊坐標(biāo) this.ctx.clearRect(x*21,y*21,20,20);//清除指定塊 // 點(diǎn)擊到標(biāo)記,點(diǎn)擊到提醒數(shù)字,點(diǎn)擊到地雷,點(diǎn)擊到空白, if(this.markArrs.myindexOf([x,y])!=-1){ //點(diǎn)擊到標(biāo)記,重新覆蓋 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){//點(diǎn)擊到提醒數(shù)字 let index = this.numArrs.myindexOf([x,y]);//下標(biāo) let num = this.numArrs[index][2];//提醒數(shù)字 this.ctx.fillText(num,x*(this.divw+1)+2,(y+1)*(this.divw+1)-2);//加1和j+1為測試結(jié)果,-+2是為了文本在格子里居中//y為文本中線坐標(biāo) this.num++; } else if(this.booArrs.myindexOf([x,y])!=-1){//,點(diǎn)擊到地雷,全部繪制 console.log(this.booArrs.myindexOf([x,y])); //繪制全圖 // 繪制提醒數(shù)字 for(let i=0;i<this.xyNum;i++){ for(let j=0;j<this.xyNum;j++){ let num = 0;//提醒數(shù)字 ,每次重置為0 // if(booArrs.indexof([i-1,j-1]) != -1){//數(shù)組是對象這樣永遠(yuǎn)-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++; } //繪制提醒數(shù)字 if(num!=0 && (this.booArrs.myindexOf([i,j]) ==-1 )){//(this.booArrs.myindexOf([i,j]) ==-1)地雷不標(biāo)注提示數(shù)字若要標(biāo)注需要+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為測試結(jié)果,-+2是為了文本在格子里居中//y為文本中線坐標(biāo) this.numArrs.push([i,j,num]);//i,j為提醒數(shù)字的塊坐標(biāo),num為裝數(shù)組里的值(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);//每次最后一個(gè)都會(huì)變紅,不知道原因,此處專門刪除。 alert('你驚動(dòng)了雷雷'); } else { this.isboo(this.ctx,x,y,this.booArrs,this.numArrs,this.markArrs,this.xyNum); } } win (){//標(biāo)記數(shù)組==地雷數(shù)組 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); } /*標(biāo)記 */ 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; } /* 這里解決點(diǎn)擊到空白格子時(shí),把周圍的空白格一起顯示。此處的邏輯可以再優(yōu)化. ctx:布局 x,點(diǎn)擊位置 y,點(diǎn)擊位置 booArrs:炸彈的位置數(shù)組 numArrs:提示數(shù)的位置 markArrs:標(biāo)記的位置 */ class Isboo { constructor(ctx,x,y,booArrs,numArrs,markArrs,xyNum){ this.x = x; this.y = y; // 判斷有沒有提醒數(shù)字 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,點(diǎn)擊位置 y,點(diǎn)擊位置 booArrs:炸彈的位置數(shù)組 divw:各自寬度 markarrs:標(biāo)記數(shù)組 */ 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){//如果標(biāo)記數(shù)組里沒有該地址,則標(biāo)記,并添加進(jìn)數(shù)組 ctx.beginPath(); ctx.fillStyle = 'red'; ctx.fillText('?',x*(divw+1)+2,(y+1)*(divw+1)-2); markarrs.push([x,y]); }else {//如果標(biāo)記數(shù)組里有該地址,則取消標(biāo)記,并從數(shù)組中刪除 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); } } }
頁面效果
初始化障礙物設(shè)置了透明度時(shí)
正常游戲時(shí)
這里點(diǎn)擊右鍵標(biāo)記后忘了把填充顏色設(shè)置回來。所以后面變紅。
關(guān)于“如何利用js+canvas實(shí)現(xiàn)掃雷游戲”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí),可以關(guān)注億速云行業(yè)資訊頻道,小編每天都會(huì)為大家更新不同的知識(shí)點(diǎn)。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。