差不多是我一年前做的項目了,今天翻回來再回顧回顧,總結下。
1. 項目流程
UI界面部分
利用Java的圖形界面工具swing和awt來繪制棋盤的框架,繪制了15X15的網格。然后在畫布上增加監聽器來監聽鼠標點擊的部分,然后在鄰近的網格交點處繪制棋子,這樣就實現了下棋的效果。
邏輯處理部分
19X19的棋盤,就用19X19的二維數組來存儲棋盤的信息。每當玩家新增加一個棋子,后台部分就會遍歷一遍數組,對每一個槽進行8個方向深度為5的DFS搜索,判斷是否有五子相連的情況,這樣就實現了輸贏的判斷。
人機對戰原理
簡而言之就是統計棋型累計評分,例如4個我方或對方的棋子相連,那么權重就是一個非常大的數,用來表示這個位置非常重要;如果是一個3個棋子相連也賦予一個對應權值。我把這種棋局用字符串表示,權重用整型數表示,以鍵值對的方式存儲在一個HashMap中,那么這個哈希表就是電腦的判斷棋局的依據。接着在電腦出手的時候,就遍歷一遍二維數組,同樣是對每一個槽進行8個方向的DFS搜索,之后查詢哈希表累計該槽的權值。遍歷完之后,權值最大的位置就是電腦下棋的位置。
人機對戰進階
極大值極小值搜索
初始版本的AI只能根據當前的局勢判斷下一步,而極大極小值算法可以自己先多走幾步,預判未來的局勢再決定當前的局勢。過程是這樣的:
先設定好一個搜索樹層數 Depth
,即要模擬 Depth
步;因為每一方都想贏,所以每一步的模擬都是模擬當前角色的最優下法,那么從AI方計算的視角來看,AI下棋的時候應該要使得全局的權值最大,對方下棋的時候就應要使得全局的權值最小;其實雙方的最優下法邏輯是一樣的,只不過從我方來看對方要_加個負號_,也可以說是我要使我的分數盡量大,對方要使我的分數盡量小。最后,綜合模擬的若干步累計權值,取其中權值最大的下法下棋。
但是這個算法的開銷很大,因為假設棋盤有N個空位,那么這棵搜索樹的第一層就有N個節點,第二層就有N-1個節點……這樣下去時間復雜度和空間復雜度都是指數級的增長,所以為了優化我們需要剪枝來舍去不必要的搜索。
剪枝
這個算法就是給搜索樹上的每一個節點維護一個下界為上界為
的窗口。這個窗口從父親到兒子直接傳遞,從兒子回溯到父親就要進行相應變化,因為搜索樹結果信息是自底向上傳遞的,所以更新也是自底向上更新。如果當前為對方節點,那么對方會盡量使可取值小,故下拉上界
;若當前使我方,則會盡量使可取值大,故上推下界
。
如果發現那么就可以剪枝了,為什么呢?首先一個前提是,當前的每一步都是由AI來模擬對於每一個下棋這的最優走法。假設當前為MAX方,那么由某個子節點的信息MAX方更新了
發現
,說明當前MAX節點可獲得一個值大於由對方約束的最大值,那么對方是肯定不會走這里的,所以本節點之后的其他子節點就沒有繼續搜索的必要了;同理,假設當前為MIN方,那么由某個子節點的信息MIN方更新了
,若發現
,說明當前的MIN節點可以獲得一個值小於對方約束的最小值,那么對方是肯定不會走這里的,所以本節點的其他子節點可以剪掉。
項目中遇到的問題
2. 知識點涉及
HashMap
哈希表本質上就是一個數組,只不過是通過哈希函數映射某個關鍵碼來獲取數組的下標。正是利用了數組空間連續查詢高效,所以哈希函數如果設計得好,能夠在的復雜度下實現查找。
但是容易出現沖突,就是不同的關鍵碼通過哈希函數映射出同一個值,這個時候就需要解決沖突的策略。大體上分為兩種:
DFS
本質上就是狀態的轉移。它從某一個初始狀態開始,一直轉移到狀態無法轉移為止。如果把搜索的過程看作一棵樹,那么信息的傳遞就會是自下而上或者是自上而下,然后對應的最終的完整信息狀態就會出現在根或者是葉子。對於我這個五子棋,信息的傳遞就是自上而下的,也即最終某個方向上棋局的狀態是搜索到葉子處獲得。
3. 亮點
4. 可改進之處
- 剪枝后的效率還是不太高,3層的延時在2s左右
-
5. 我的收獲