前端基础练习项目——网页版扫雷
掃雷,學習資料:渡一教育實現掃雷視頻,基本上所有都是原生js的,一個小bug用jquery解決,如果解決了bug再改,用了jq的地方會標注即寫上js寫法,大致實現效果如下
完整代碼github地址:https://github.com/yuriaFan/mine-clearance
?
HTML部分,因為是學習+練習就只寫了很普通的樣式
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><link rel="stylesheet" href="css/index.css"><script src="js/jquery.js"></script><title>Document</title> </head> <body><div id="content"><!-- 頂端按鈕 --><div id="level"><button class="active">初級</button><button>中級</button><button>高級</button><button class="big">重新開始</button></div><!-- 棋盤 --><div id="gameBox"><!-- js動態添加的 --></div><!-- 底部文本 --><div id="bottomText"><div class="text">剩余雷數:<span class="mineNum"></sapn></div></div></div><script src="js/index.js"></script> </body> </html>三個部分,上部按鈕,中部掃雷界面(‘棋盤’),下部剩余雷數text,中部‘棋盤’是用添加上去的
CSS部分,前面部分是對基礎界面的樣式修改,后面td.one-td.eight部分是給實現掃雷點擊后出現的數字改顏色用的
注意 td的border-color,是用來實現‘棋盤’上宛如磚塊一樣的立體效果,設置上方和左側為白色,其他地方為想要的顏色即可,
#content{height: 900px;margin: 0 auto;width: 800px; } #level{height: 25px;width: 280px;margin: 0 auto; } #level button{height: 25px;line-height: 25px;width: 60px;text-align: center;background: rgb(73, 139, 141);;color: white;padding: 0px;margin: 0px;border: 0px;border-radius: 5px;outline: none;cursor: pointer; } #level button.active{background: #0bb9b6; } #level button.big{width: 80px; } #gameBox{padding-top: 20px; } #bottomText{height: 20px;width: 200px;margin: 0 auto;line-height: 20px; } /* .mineNum{height: 20px;width: 50px;} */ table{border-spacing: 1px;background-color: #929196;margin: 0 auto; } td{height: 20px;width: 20px;background-color: #ccc;padding: 0;border: 1px solid;border-color: #fff #a1a1a1 #a1a1a1 #fff;text-align: center;line-height: 20px;font-weight: bold; } .mine{background:#c9c9c9 url(../images/mine.png) no-repeat center;background-size: cover; } .flag{background:#ccc url(../images/flag.png) no-repeat center;background-size: cover; } td.zero{background-color: #d9d9d9;border-color: #d9d9d9; } td.one{color: red;background-color: #d9d9d9;border-color: #d9d9d9; } td.two{color: green;background-color: #d9d9d9;border-color: #d9d9d9; } td.three{color: yellow;background-color: #d9d9d9;border-color: #d9d9d9; } td.four{color: blue;background-color: #d9d9d9;border-color: #d9d9d9; } td.five{color: blueviolet;background-color: #d9d9d9;border-color: #d9d9d9; } td.six{color: brown;background-color: #d9d9d9;border-color: #d9d9d9; } td.seven{color: cadetblue;background-color: #d9d9d9;border-color: #d9d9d9; } td.eight{color: coral;background-color: #d9d9d9;border-color: #d9d9d9; }JS部分!
首先使用的是面向對象思想,在部分地方會對寫好的函數進行調用,函數是寫在原型上的。
第一步先寫了構造函數,寫上了后期所需要用到的各種變量,用在后期生成‘棋盤’的時候。
//面向對象思想 function Mine(tr,td,mineNum){this.tr=tr;//行數this.td=td;//列數this.mineNum=mineNum;//雷的總數this.squares=[];//存儲所有方塊的信息,二維數組,以行列的信息存儲this.tds=[];//存儲所有單元格的DOM對象(二維數組)this.surplusMine=mineNum;//剩余雷的數量this.allRight=false;//玩家標注的小紅旗是否全是雷,判斷是否游戲成功this.parent=document.getElementById('gameBox');}然后是createDom(),生成‘棋盤’,用的是table,td,tr,但是ul和li也是能夠實現的
生成基礎‘棋盤’后,寫格子的點擊事件調用了play函數,后面會講到這個函數
Mine.prototype.createDom=function(){var This=this;var table=document.createElement('table');for(var i=0;i<this.tr;i++){var domTr=document.createElement('tr');//通過構造函數傳入的行數來生成trthis.tds[i]=[];for(var j=0;j<this.td;j++){//列var domTd=document.createElement('td');//同上domTd.pos=[i,j];//存儲對應的位置,行與列(后面會用到坐標,坐標和行與列的關系是x y恰好相反)domTd.onmousedown=function(){//當對應的格子被點擊時This.play(event,this);//This 實例對象 this 被點擊的Td}this.tds[i][j]=domTd;domTr.appendChild(domTd);}table.appendChild(domTr);}this.parent.innerHTML="";this.parent.appendChild(table); }然后init函數
其中使用了一個random函數,用于隨機生成雷的位置,用td*tr得到大于需要雷的數量的有序數,用排序+random得到隨機數,通過mineNum的大小來確定隨機數的數量,最后返回一個數組。
Mine.prototype.randomNum=function(){var square=new Array(this.td*this.tr);for(var i=0;i<square.length;i++){square[i]=i;}square.sort(function(){return 0.5-Math.random()});return square.slice(0,this.mineNum); } Mine.prototype.init=function(){var rn=this.randomNum();//雷的位置var n=0;//作為訪問隨機數字的位置的索引,即rn的索引for(var i=0;i<this.tr;i++){this.squares[i]=[];for(var j=0;j<this.td;j++){n++;//取方塊在數組里的數組用行與列的方式去取,取方塊周圍對應的數字用坐標if(rn.indexOf(n)!=-1){this.squares[i][j]={type:'mine',x:j,y:i};}else{this.squares[i][j]={type:'number',x:j,y:i,value:0};}}}this.update();this.createDom();this.parent.oncontextmenu=function(){return false;}// this.mineNumDom=document.getElementsByClassName('mineNum');// this.mineNumDom.innerHTML=this.surplusMine;$('.mineNum').html(this.surplusMine);//出bug,用原生無法顯示,未解決,原生實現應該就是上面兩行,但是也不知道哪里出的問題}上方init函數
定義一個n用于一會比較用,兩個for循環用rn.indexOf(n)來判斷rn即隨機數有哪些,若存在隨機數數組中,則未squares里對應的坐標位賦值type:'mine',否則則為number。
update()晚點再說,大致就是刷新。
this.parent.oncontextmenu是用于取消鼠標右鍵的點擊事件,為一會給右鍵點擊添加旗子🚩做準備。最后一行用了jQuery,是往最底下的span里放對應的雷數。
然后是getAround()函數,由于在坐標上,一個雷的左上角是x-1,右下角是x+1,y同理,故兩個for循環,調用這個函數可獲得在某個雷附近的 除掉‘棋盤’四個角以外導致的誤差值+自身+九宮格除掉自身以外八個格子中是雷的格子。
//找某個格子周圍的八個格子 Mine.prototype.getAround=function(square){var x=square.x;var y=square.y;var result=[];//把找到的格子(雷以外的)的坐標返回【二維數組】//x坐標的值從x-1到x+1,y類似//坐標循環九宮格(坐標)for(var i=x-1;i<=x+1;i++){for(var j=y-1;j<=y+1;j++){//排除整個棋盤四個角的誤差值+自身+八個格子中有雷if(i<0||j<0||i>this.td-1||j>this.tr-1||(i==x&&j==y)||this.squares[j][i].type=='mine'){continue;}result.push([j,i]);//返回行與列的形式}}return result; }然后在update中調用了getAround
//更新所有數字 Mine.prototype.update=function(){for(var i=0;i<this.tr;i++){for(var j=0;j<this.td;j++){//更新的只有雷周圍的數字if(this.squares[i][j].type=='number'){continue;}var num=this.getAround(this.squares[i][j]);//獲取雷周圍的數組for(var k=0;k<num.length;k++){this.squares[num[k][0]][num[k][1]].value+=1;}}} }先用了兩個for循環,將不是雷的位置排除出去(跳出循環),然后再調用getAround函數,得到對應的這個雷旁邊的坐標的數組,由于是二維數組,用num[k][0]來獲取x坐標,y坐標同理,然后令對應位置上的value加1
然后是最開始的createDom里面調用過的play函數,由于這個函數比較長,將其分成兩部分來說明(還是連在一起的)
play()第一部分
Mine.prototype.play=function(ev,obj){var This=this;if(ev.which==1 && obj.className!='flag'){//且在立了小紅旗之后不能再點擊左鍵,只能右鍵取消了之后才可再次點擊左鍵var curSquare=this.squares[obj.pos[0]][obj.pos[1]];var cl=['zero','one','two','three','four','five','six','seven','eight'];if(curSquare.type=='number'){obj.innerHTML=curSquare.value;obj.className=cl[curSquare.value];//處理零的情況if(curSquare.value==0){obj.innerHTML='';function getAllZero(square){var around=This.getAround(square);for(var i=0;i<around.length;i++){var x=around[i][0];//行var y=around[i][1];//列This.tds[x][y].className=cl[This.squares[x][y].value];//遞歸 1.顯示自身 2.找四周 然后重復if(This.squares[x][y].value==0){//給遍歷過的格子加屬性降低資源浪費if(!This.tds[x][y].check){This.tds[x][y].check=true;getAllZero(This.squares[x][y]);}}else{//某個格子為中心查找到的值不為零,則顯示This.tds[x][y].innerHTML=This.squares[x][y].value;}}}getAllZero(curSquare);}}else{this.gameOver(obj);}}最開始先將函數作用域里的this提出來
第一個if,兩個判斷:1.鼠標左鍵被點擊 2.被點擊的這個格子沒有小紅旗(這個屬性可有可無,根據個人需要來加,加上了就是在有小紅旗的時候必須先右鍵取消掉小紅旗才能再次左鍵點擊有效)
寫個cl給添加css屬性用
在判斷這個格子是數字不是雷后,就將它的文本設置為value值,加上對應的css屬性,倘若是零的話,用一個函數來處理,getAllZero()中先獲取值為零的格子附近的八個格子,將對應的css屬性加上后,判斷value值是不是零,若是用遞歸,原因:查找周圍的規律如下
找到了則顯示自身,然后繼續找四周還有沒有,然后找到了然后顯示自身,然后繼續找...一直重復,直到找到的格子value不是零,才將它的innerHTML值顯示出來
其中有一個小問題就是,對于x這個格子來說,x-1周圍的格子里有它,x+1的格子里也有它,就會造成重復查詢,資源浪費,所以就給查詢過的格子加屬性,降低資源浪費。
然后調用getAllZero()
最后else點擊到的如果不是數字(即雷)就游戲結束調用gameOver函數(一會說)
?
play()第二部分
if(ev.which==3){//如果右擊的是數字,忽略if(obj.className&&obj.className!='flag'){return;}obj.className=obj.className=='flag'?'':'flag';//切換小旗if(this.squares[obj.pos[0]][obj.pos[1]].type=='mine'){this.allRight=true;}else{this.allRight=false;}//小旗的添加和減少if(obj.className=='flag'){$('.mineNum').html(--this.surplusMine);}else{$('.mineNum').html(++this.surplusMine);}if(this.surplusMine==0){if(this.allRight){alert('win');}else{alert('lose');this.gameOver;}}} }這一部分處理的是鼠標右鍵時的情況,如果右擊的時已經左擊點出來了的數字,就忽略掉。
然后用三目運算符,對不是數字的格子進行有無小旗的切換。
在最開始構造函數的地方定義了一個allRight屬性,然后右鍵處理的地方,若是插上小旗,且所有在規定數量以內插上小旗的地方type都是mine則allRight為true,否則為false,用來在游戲最后判斷小旗是否推斷出了全部雷的地方是準確的。
然后if-else控制小旗數量在span里的顯示和surplusMine屬性
最后用surplusMine來判斷是否游戲即將結束,全對彈出win,否則調用gameOver函數。
//定義游戲失敗 Mine.prototype.gameOver=function(clickTd){//1.顯示所有的雷//2.取消所有格子點擊事件//3.給點中的格子標紅for(var i=0;i<this.tr;i++){for(var j=0;j<this.td;j++){if(this.squares[i][j].type=='mine'){this.tds[i][j].className='mine';}this.tds[i][j].onmousedown=null;}}if(clickTd){clickTd.style.backgroundColor="white";}alert('lose'); }在判斷游戲是結束時需做上方代碼注釋里的三件事。
?
最后就是處理調節游戲難度,動態生成不同的‘棋盤’
//button功能實現 var btns=document.querySelectorAll('#level button'); var mine=null;//存儲生成的實例 var ln=0;//處理當前選中的狀態 var arr=[[9,9,10],[16,16,40],[28,28,99]];//行數列數雷數的二維數組 for(let i=0;i<btns.length-1;i++){btns[i].onclick=function(){btns[ln].className="";this.className="active";mine=new Mine(...arr[i]);//擴展運算符... 直接放arr[i]取不出來。要寫成arr[i][0],arr[i][1]mine.init();ln=i;} } btns[0].onclick();//初始化 btns[3].onclick=function(){mine.init(); }將構造函數需要的數據寫成二維數組,在點擊對應按鈕時生成‘棋盤’,并取消掉上一個‘棋盤’的顯示(classname)
兩個注意點:1.? ...是拓展運算符
2. for循環里用的是let不是var,為了ln=i是onclick里的i
令最開始打開的時候不是空的,寫上代碼點擊第一個按鈕
然后最后的onclick是處理重新開始按鈕的
?
大致就是這樣,仍有許多細節需要修改,若其中存在問題,還請大佬多多指教
總結
以上是生活随笔為你收集整理的前端基础练习项目——网页版扫雷的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: STM32系统定时器SysTick(只能
- 下一篇: 如何简单快速调试高大上的谷歌浏览器