我用數據結構花了一晚上給女友寫了個h5走迷宮小遊戲

@(文章目錄)html

先看效果圖(在線電腦嘗試地址http://biggsai.com/maze.html):
在這裏插入圖片描述前端

原由

在這裏插入圖片描述
又到深夜了,我按照以往在公衆號寫着數據結構!這佔用了我大量的時間!個人超越妹妹嚴重缺少陪伴而 怨氣滿滿!
在這裏插入圖片描述
超越妹妹時常埋怨,認爲數據結構這麼抽象難懂的東西沒啥做用,常會問道:每天寫這玩意,有啥做用。而我答道:能幹事情多了,好比寫個小遊戲啥的!
在這裏插入圖片描述
當我碼完字準備睡覺時:寫很差別睡覺!
在這裏插入圖片描述java

分析

若是用數據結構與算法造出東西來呢?算法

  • 什麼東西簡單容易呢?我百度一下,我靠,這個鳥遊戲原來很差搞啊,得接觸一堆不熟悉的東西,搞不來搞不來。

有了(靈光一閃),寫個猜數字遊戲,問他加減乘除等於幾。canvas

  • 超越妹妹又不是小孩子,糊弄不過去。

通過一番折騰,終於在半夜12點肯定寫迷宮小遊戲了。大概弄清楚其中的幾個步驟。數組

大概是數據結構

  • 畫線—>畫迷宮(擦線)—>方塊移動、移動約束(不出界不穿牆)—>完成遊戲

畫線(棋盤)

對於html+js(canvas)畫的東西,以前學過javaswing應該有點映像。在html中有個canvas 的畫布,能夠在上面畫一些東西和聲明一些監聽(鍵盤監聽)。dom

對於迷宮來講,那些線條是沒有屬性的,只有位置x,y,你操做這個畫布時候,可能和咱們習慣的面相對象思惟不同。因此,在你設計的線或者點的時候,記得那個點、線在什麼位置,在後續劃線仍是擦線仍是移動的時候根據這個位置進行操做。函數

<!DOCTYPE html>
<html>
  <head>
    <title>MyHtml.html</title>  
  </head> 
  <body>
  <canvas id="mycanvas" width="600px" height="600px"></canvas>
    
  </body>
  <script type="text/javascript">

var aa=14;
    var chess = document.getElementById("mycanvas");
    var context = chess.getContext('2d');

    //  var context2 = chess.getContext('2d');
    //      context.strokeStyle = 'yellow';
    var tree = [];//存放是否聯通
    var isling=[];//判斷是否相連
    for(var i=0;i<aa;i++){
        tree[i]=[];
        for(var j=0;j<aa;j++){
            tree[i][j]=-1;//初始值爲0
        }
    }  for(var i=0;i<aa*aa;i++){
        isling[i]=[];
        for(var j=0;j<aa*aa;j++){
            isling[i][j]=-1;//初始值爲0
        }
    }
    
    function drawChessBoard(){//繪畫
        for(var i=0;i<aa+1;i++){
            context.strokeStyle='gray';//可選區域
            context.moveTo(15+i*30,15);//垂直方向畫15根線,相距30px;
            context.lineTo(15+i*30,15+30*aa);
            context.stroke();
            context.moveTo(15,15+i*30);//水平方向畫15根線,相距30px;棋盤爲14*14;
            context.lineTo(15+30*aa,15+i*30);
            context.stroke();
        }
    }
    drawChessBoard();//繪製棋盤
   
    //      var mymap=new Array(36);
    //      for(var i=0;i<36;i++)
    //     {mymap[i]=-1;}


  </script>
</html>

實現效果
在這裏插入圖片描述

畫迷宮

隨機迷宮怎麼生成?怎麼搞?一臉懵逼。

  • 由於咱們想要迷宮,那麼就須要這個迷宮出口和入口有連通路徑,你可能壓根不知道迷宮改怎麼生成,用的什麼算法。小聲BB:並查集(不相交集合)

迷宮和不相交集合有什麼聯繫呢?(規則)

  • 以前筆者在前面數據結構與算法系列中曾經介紹過並查集(不相交集合),它的主要功能是森林的合併,不聯通的經過並查集可以快速將兩個森林合併,而且可以快速查詢兩個節點是否在同一個森林中!

咱們的隨機迷宮:在每一個方格都不聯通的狀況下,是一個棋盤方格,這也是它的初始狀態。而這個節點能夠跟鄰居可能相連,也可能不相連。咱們能夠經過並查集實現。

具體思路爲:(主要理解並查集)

  • 1:定義好不想交集合的基本類和方法(search,union等)
    2:數組初始化,每個數組元素都是一個集合,值爲-1
    3:隨機查找一個格子(一維數據要轉換成二維,有點麻煩),在隨機找一面牆(也就是找這個格子的上下左右),還要判斷找的格子出沒出界。
    具體在格子中找個隨機數m——>隨機數m在二維中的位置[m/長,m%長]——>這個二維的上下左右隨機找一個位置p[m/長+1,m%長][m/長-1,m%長][m/長,m%長+1][m/長,m%長-1]——>判斷是否越界
    4:判斷兩個格子(一維數組編號)是否在一個集合(並查集查找)。若是在,則從新找,若是不在,那麼把牆挖去
    5:把牆挖去有點繁瑣,須要考慮奇偶判斷它那種牆(上下仍是左右,還要考慮位置),而後擦掉。(根據數組轉換成真實距離)。具體爲找一個節點,根據位置關係找到一維數組的號位用並查集判斷是否在一個集合中。
    6:最終獲得一個完整的迷宮。直到第一個(1,1)和(n,n)聯通中止。雖然採用隨機數找牆,可是效果並非特別差。其中要搞清一維二維數組的關係。一維是真實數據,並查集操做。二維是位置。要搞懂轉化!

注意:避免混淆,搞清數組的地址和邏輯矩陣位置。數組從0開始的,邏輯上你本身判斷。別搞混淆!
在這裏插入圖片描述
主要邏輯爲:

while(search(0)!=search(aa*aa-1))//主要思路
    {
        var num = parseInt(Math.random() * aa*aa );//產生一個小於196的隨機數
        var neihbour=getnei(num);
        if(search(num)==search(neihbour)){continue;}
        else//不在一個上
        {
           isling[num][neihbour]=1;isling[neihbour][num]=1;
            drawline(num,neihbour);//劃線
            union(num,neihbour);
         
        }
    }

那麼在前面的代碼爲

<!DOCTYPE html>
<html>
  <head>
    <title>MyHtml.html</title>  
  </head> 
  <body>
  <canvas id="mycanvas" width="600px" height="600px"></canvas>
    
  </body>
  <script type="text/javascript">
//自行添加上面代碼
    //      var mymap=new Array(36);
    //      for(var i=0;i<36;i++)
    //     {mymap[i]=-1;}
    function getnei(a)//得到鄰居號  random
    {
        var x=parseInt(a/aa);//要精確成整數
        var y=a%aa;
        var mynei=new Array();//儲存鄰居
        if(x-1>=0){mynei.push((x-1)*aa+y);}//上節點
        if(x+1<14){mynei.push((x+1)*aa+y);}//下節點
        if(y+1<14){mynei.push(x*aa+y+1);}//有節點
        if(y-1>=0){mynei.push(x*aa+y-1);}//下節點
        var ran=parseInt(Math.random() * mynei.length );
        return mynei[ran];

    }
    function search(a)//找到根節點
    {
        if(tree[parseInt(a/aa)][a%aa]>0)//說明是子節點
        {
            return search(tree[parseInt(a/aa)][a%aa]);//不能壓縮路徑路徑壓縮
        }
        else
            return a;
    }
    function value(a)//找到樹的大小
    {
        if(tree[parseInt(a/aa)][a%aa]>0)//說明是子節點
        {
            return tree[parseInt(a/aa)][a%aa]=value(tree[parseInt(a/aa)][a%aa]);//不能路徑壓縮
        }
        else
            return -tree[parseInt(a/aa)][a%aa];
    }
    function union(a,b)//合併
    {
        var a1=search(a);//a根
        var b1=search(b);//b根
        if(a1==b1){}
        else
        {
            if(tree[parseInt(a1/aa)][a1%aa]<tree[parseInt(b1/aa)][b1%aa])//這個是負數(),爲了簡單減小計算,不在調用value函數
            {
                tree[parseInt(a1/aa)][a1%aa]+=tree[parseInt(b1/aa)][b1%aa];//個數相加  注意是負數相加
                tree[parseInt(b1/aa)][b1%aa]=a1;       //b樹成爲a樹的子樹,b的根b1直接指向a;
            }
            else
            {
                tree[parseInt(b1/aa)][b1%aa]+=tree[parseInt(a1/aa)][a1%aa];
                tree[parseInt(a1/aa)][a1%aa]=b1;//a所在樹成爲b所在樹的子樹
            }
        }
    }

    function drawline(a,b)//劃線,要判斷是上下仍是左右
    {

        var x1=parseInt(a/aa);
        var y1=a%aa;
        var x2=parseInt(b/aa);
        var y2=b%aa;        
        var x3=(x1+x2)/2;
        var y3=(y1+y2)/2;
        if(x1-x2==1||x1-x2==-1)//左右方向的點  須要上下劃線
        {
            //alert(x1);
            //  context.beginPath();
            context.strokeStyle = 'white';
            //    context.moveTo(30+x3*30,y3*30+15);//
            //   context.lineTo(30+x3*30,y3*30+45);
            context.clearRect(29+x3*30, y3*30+16,2,28);
            //    context.stroke();
        }
        else
        {
            //   context.beginPath();
            context.strokeStyle = 'white';
            //  context.moveTo(x3*30+15,30+y3*30);//
            //    context.lineTo(45+x3*30,30+y3*30);
            context.clearRect(x3*30+16, 29+y3*30,28,2);
            //      context.stroke();
        }
    }
     
    while(search(0)!=search(aa*aa-1))//主要思路
    {
        var num = parseInt(Math.random() * aa*aa );//產生一個小於196的隨機數
        var neihbour=getnei(num);
        if(search(num)==search(neihbour)){continue;}
        else//不在一個上
        {
           isling[num][neihbour]=1;isling[neihbour][num]=1;
            drawline(num,neihbour);//劃線
            union(num,neihbour);
         
        }
    }
  </script>
</html>

實現效果:
在這裏插入圖片描述
在這裏插入圖片描述

方塊移動

這部分我採用的方法不是動態真的移動,而是一格一格的跳躍。也就是當走到下一個格子將當前格子的方塊擦掉,在移動的那個格子中再畫一個方塊。選擇方塊是由於方塊更方便擦除,能夠根據像素大小精準擦除。

另外,再移動中要注意不能穿牆、越界。那麼怎麼判斷呢?很好辦,咱們再前面會判斷兩個格子是否聯通,若是不連通咱們將把這個牆拆開。再拆的時候把這個牆的時候記錄這兩點拆牆可走便可(數組)

另外,事件的監聽上下左右查一查就能夠獲得,添加按鈕對一些事件監聽,這些不是最主要的。

爲了豐富遊戲可玩性,將方法封裝,能夠設置關卡(只需改變迷宮大小)。這樣就能夠實現通關了。另外,若是寫成動態存庫那就更好了。
在這裏插入圖片描述

結語

在線嘗試地址,代碼直接查看網頁源代碼便可!

筆者前端能力和算法能力有限,寫的可能不是特別好,還請見諒!固然,筆者歡迎和一塊兒熱愛學習的人共同進步、學習!歡迎關注筆者公衆號:bigsai,若是感受不錯,歡迎關注、點贊!蟹蟹!

相關文章
相關標籤/搜索