js版九宮格拼圖與啟發式搜尋(A*演算法)

pangyongsheng發表於2018-03-30

  九宮格拼圖遊戲大家都很熟悉,這裡給大家如介紹何應用狀態空間搜尋的方式求解拼圖的最佳路徑和一個遊戲dome及自動求解方法;

本文分web版遊戲的實現和啟發式搜尋演算法兩部分;

先看dome,直接滑鼠點選要移動的方塊開始遊戲,點選 提示 開始最佳路徑搜尋(啟發式)直到最後一步;

(如果提示無解,則表示沒有找到最佳路點選重置重新試一次,可通過console檢視全部搜尋的每一步節點狀態,或在js/main.js中打斷點看每一步結果,詳細內容見下文)


一、遊戲的實現方法

  首先我們考慮如何用資料表示拼圖遊戲的狀態,即將拼圖遊戲檢視與資料繫結;

  如下圖所示:

    

  (1)以左上角為原點,建立座標系,藍色數字表示位置序號,

     則該位置div(拼圖塊)的left和right(向左和向下的偏移距離)等於為其左上角綠點的座標(x,y),即:

      left = x * 小方塊邊長

      right = y* 小方塊邊長

  (2)這樣的話,我們就可以用一個長度為9陣列表示當前拼圖的狀態空間

     如 [2,0,1,5,4,6,7,8,3] 可表示一號方塊在2號位置

二號方塊在0號位置...
如下圖所示:

自此我們就實現了檢視與資料的對應關係,把拼圖問題轉化成為一個陣列排列組合問題;

  (3)對於任意號位置a的座標c我們可通過建立一個如下二維陣列來獲取, 

      var place= [
        [0, 0],[1, 0],[2, 0],

        [0, 1],[1, 1],[2, 1],   

        [0, 2],[1, 2],[2, 2]

                      ]

    位置序號與座標則有如下關係

      c=place[a]

    由以上可知獲取a座標方法

    

   初始化每一個小方塊位置方法(block為全部小方塊dom,這裡借用陣列方法forEach遍歷div)

    

   (4)對於任意兩個座標的距離我們可以表示為

      d=| x1 - x2 | + | y1-y2 |

     程式碼如下

    

  (5)那麼我們可以求得當前狀態和目標狀態的全部距離為,(每個小方塊距離目標的距離求和),f(x)第x方塊距離目標位置的距離

    

    程式碼如下

    

  (6)如何判斷點選的方塊是否能移動,首先我們將最後一個方塊隱藏如果點選的方塊距離最後(8號)方塊距離為1則表示可以移動,及兩個狀態可以轉化,

     這個方法也可以看做兩個狀態的陣列能否相互轉化,可作為後面啟發式搜尋判斷節點擴充套件的方法;

    

    以上程式碼為每個方塊新增點選事件

    至此遊戲的基本實現方式介紹完畢,詳細看程式碼

二、啟發式搜尋

  啟發式搜尋就是在狀態空間中的搜尋對每一個搜尋的位置進行評估,得到最好的位置,再從這個位置進行搜尋直到目標。這樣可以省略大量無謂的搜尋路徑,提高了效率。在啟發式搜尋中,對位置的估價是十分重要的。採用了不同的估價可以有不同的效果

它把到達節點的耗散g(n)和從該節點到目標節點的消耗h(n)結合起來對節點進行評價:f(n)=g(n)+h(n)

  簡單的說就是擴充套件當前狀態節點的所有可能下一步節點,通過一個方式來估算那個節點最快能到到目標,不斷重複知道實現達到目標狀態;

  我們這裡的估計方法為 當前狀態的全部距離+走的步數;

  

  搜尋過程可能描述如下:

(1)把初始節點S0放入Open表中,f(S0)=g(S0)+h(S0);

(2)如果Open表為空,則問題無解,失敗退出;

(3)把Open表的第一個節點取出放入Closed表,並記該節點為n;

(4)考察節點n是否為目標節點。若是,則找到了問題的解,成功退出;

(5)若節點n不可擴充套件,則轉到第(2)步;

(6)擴充套件節點n,生成子節點ni(i=1,2,……),計算每一個子節點的估價值f(ni) (i=1,2,……),併為每一個子節點設定指向父節點的指標,然後將這些子節點放入Open表中;

(7)根據各節點的估價函式值,對Open表中的全部節點按從小到大的順序重新進行排序;

(8)轉第(2)步。

  程式碼太長 截圖不夠,詳細還是看程式碼吧:O(∩_∩)O

  js版九宮格拼圖與啟發式搜尋(A*演算法)


  

  我是把一步的搜尋結果直接展示在檢視中的,所以closed表中沒有保留節點狀態,單通過console.log輸出,大家可以點選F12在除錯模式下檢視全部節點;

  若希望檢視每一步檢視狀態,則可以在searchA方法的while迴圈中打斷點檢視效果;

  網上找個圖說明一下搜尋的方法:容易明白

  


 其實這裡還是有個兩問題需注意:

(1)並不是每次都能找到目標狀態 ,因為我的陣列打亂是完全隨機的,而尋路的限制是只能兩個相鄰節點互換位置;

(2)在每次擴充套件節點的時候需考慮這個節點是否已經出現在closed表中(即尋路的過程中出現過改節點),這樣會導致尋路的過程在幾個節點中轉圈,陷入死迴圈; 但也不能完全限制closed的表中的資料不能重複出現,這樣會降低尋路成功的可能性;這裡我取了一個比較合理的值256,即在最近的256次中沒有出現過改節點;


相關文章