九宮格拼圖游戲大家都很熟悉,這里給大家如介紹何應用狀態空間搜索的方式求解拼圖的最佳路徑和一個游戲dome及自動求解方法;
本文分web版游戲的實現和啟發式搜索算法兩部分;
先看dome,直接鼠標點擊要移動的方塊開始游戲,點擊 提示 開始最佳路徑搜索(啟發式)直到最后一步;
(如果提示無解,則表示沒有找到最佳路點擊重置重新試一次,可通過console查看全部搜索的每一步節點狀態,或在js/main.js中打斷點看每一步結果,詳細內容見下文)
項目地址:https://github.com/pangyongsheng/puzzle
dome演示:http://pangyongsheng.github.io/puzzle/
一、游戲的實現方法
首先我們考慮如何用數據表示拼圖游戲的狀態,即將拼圖游戲視圖與數據綁定;
如下圖所示:
(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
我是把一步的搜索結果直接展示在視圖中的,所以closed表中沒有保留節點狀態,單通過console.log輸出,大家可以點擊F12在調試模式下查看全部節點;
若希望查看每一步視圖狀態,則可以在searchA方法的while循環中打斷點查看效果;
網上找個圖說明一下搜索的方法:容易明白
其實這里還是有個兩問題需注意:
(1)並不是每次都能找到目標狀態 ,因為我的數組打亂是完全隨機的,而尋路的限制是只能兩個相鄰節點互換位置;
(2)在每次擴展節點的時候需考慮這個節點是否已經出現在closed表中(即尋路的過程中出現過改節點),這樣會導致尋路的過程在幾個節點中轉圈,陷入死循環; 但也不能完全限制closed的表中的數據不能重復出現,這樣會降低尋路成功的可能性;這里我取了一個比較合理的值256,即在最近的256次中沒有出現過改節點;