1. 程式人生 > >象棋AI算法(一)

象棋AI算法(一)

估值 流行 估計 其余 最小 做到 gravity 目的 註意

最近想做一個象棋遊戲,但是AI把我難住了。這是這幾天的成果:

象棋程序通過使用“搜索”函數來尋找著法。搜索函數獲得棋局信息,然後尋找對於程序一方來說最好的著法。

一,最小-最大搜索Minimax Search


首先
:最小與最大是相對的,且只針對一方,AI中即為有利於AI
象棋AI中的最小最大搜索: 簡單來講就是該AI走了,窮舉這個過程中對於AI來說的最佳(最大)走法對於我來說最差(最小)的走法。
而這個走法就是我們所要找的AI的最佳走法。


這個過程就跟你與別人下象棋時猜測對方走法然後下棋一樣,只不過,電腦可以多想幾步,這裏的步數就是下面的搜索深度


舉個例子:假設搜索深度為4. 那麽AI走一步(他認為最佳,記為步數1,搜索深度4)時,會先考慮如果他走這一步1,那我肯定會走相對於這一步來講
最差的一步2(搜索深度3),然後ai再假設出根據步數2來講的最佳步數3(搜索深度2),繼續考慮我根據步數3走的最差步數4(搜索深度1)
接下,搜索深度為0,給出此時的局面評價函數
技術分享



他們之間彼此遞歸的調用,所以說這種搜索思路是相對的


這裏只是淺顯的講下最小最大搜索的原理,還不能實現具體的AI功能






下面是實現代碼


int Max(int depth) {
 int best = -INFINITY;
 if (depth <= 0) {
  return Evaluate();
 }
 GenerateLegalMoves(); //產生所有合理走法
 while (MovesLeft()) {
  MakeNextMove(); //走這一步時
  val = Min(depth - 1); //接受一個相對最小值
  UnmakeMove();
  if (val > best) {
   best = val;
  }
 }
 return best; //返回一個相對最大的評價(AI 認為的最佳著法)
}
 
int Min(int depth) {
 int best = INFINITY; // 註意這裏不同於“最大”算法
 if (depth <= 0) {
  return Evaluate();
 }
 GenerateLegalMoves();
 while (MovesLeft()) {
  MakeNextMove();
  val = Max(depth - 1); //接受一個相對最大值
  UnmakeMove();
  if (val < best) {  // 註意這裏不同於“最大”算法
   best = val;
  }
 }
 return best; //返回一個相對最小的評價 (對方,人認為的最差走法)
}
 
  上面的代碼可以這樣調用:
 
val = MinMax(5);
 
  這樣可以返回當前局面的評價,它是向前看5步的結果。 相關解釋看註釋就好




上面這種算法代碼長,而且只是利於一方來推測(即對一方來說最佳最差走法),下面將介紹一種優化過的算法



--------------------------------------------------------------------------華麗麗的分割線
二,負值最大函數 Negamax Search


int NegaMax(int depth) {
 int best = -INFINITY;
 if (depth <= 0) {
  return Evaluate();
 }
 GenerateLegalMoves();
 while (MovesLeft()) {
  MakeNextMove();
  val = -NegaMax(depth - 1); // 註意這裏有個負號。
  UnmakeMove();
  if (val > best) { //始終最優值
   best = val;
  }
 }
 return best;
}


從這個函數可以看出,這個函數求得始終是當前節點的最優值(即始終求對當前節點的最佳走法),只是當變換節點(即由AI變換到人時),對函數結果取負值,變成人的最佳著法對於AI的評價,這樣就省去了求min函數的步驟,減少了代碼量



———————————————————————————————————————華麗麗的分割線


三,Alpha-Beta搜索



最小最大運行時要檢查整個博弈樹,然後盡可能選擇最好的線路,但因為分枝因子太大導致效率非常低,無法做到很深的搜索。
Alpha-Beta搜索好處在於裁剪了不必要的分枝因子


舉個例子
(口袋的例子):
比如你的死敵面前有很多口袋,他和你打賭賭輸了,因此他必須從中給你一樣東西,而挑選規則卻非常奇怪:
  每個口袋裏有幾件物品,你能取其中的一件,你來挑這件物品所在的口袋,而他來挑這個口袋裏的物品。你要趕緊挑出口袋並離開,因為你不願意一直做在那裏翻口袋而讓你的死敵盯著你。
  假設你一次只能找一只口袋,在找口袋時一次只能從裏面摸出一樣東西。


分析:你很容易將最小最大原理運用到這個問題上——你挑出最好的口袋,你死敵從裏面挑出最差的物品。所以你的目標是——
挑出在諸多最糟糕物品中最好的物品所在的口袋


假設口袋內物品請況

我們從第一個口袋開始,看每一件物品,並對口袋作出評價。比方說口袋裏有一只花生黃油三明治和一輛新汽車的鑰匙。你知道三明治更糟,因此如果你挑了這只口袋就會得到三明治。
事實上只要我們假設對手也會跟我們一樣正確評價物品,那麽口袋裏的汽車鑰匙就是無關緊要的了。
現在你開始翻第二個口袋,這次你采取的方案就和最小-最大方案不同了。你每次看一件物品,並跟你能得到的最好的那件物品(三明治)去比較。只要物品比三明治更好,那麽你就按照最小-最大方案來辦——
去找最糟的,或許最糟的要比三明治更好,那麽你就可以挑這個口袋,它比裝有三明治的那個口袋好。
  比方這個口袋裏的第一件物品是一張20美元的鈔票,它比三明治好。如果包裏其他東西都沒比這個更糟了,那麽如果你選了這個口袋,它就是對手必須給你的物品,這個口袋就成了你的選擇。
這個口袋裏的下一件物品是六合裝的流行唱片。你認為它比三明治好,但比20美元差,那麽這個口袋仍舊可以選擇。再下一件物品是一條爛魚,這回比三明治差了。於是你就說“不謝了”,把口袋放回去,不再考慮它了。
  無論口袋裏還有什麽東西,或許還有另一輛汽車的鑰匙,也沒有用了,因為你會得到那條爛魚。或許還有比爛魚更糟的東西(那麽你看著辦吧)。無論如何爛魚已經夠糟的了,而你知道挑那個有三明治的口袋肯定會更好。


物品情況如圖所示

技術分享
排序後如圖所示
技術分享
節點2中最小值200,節點3中150<200.而節點1下第一個子節點只有170,小於200,而第二個子節點比170還要小,所以就不用再拿他跟200比較,剪裁,節點4類似,第一個子節點50,後面的就都不用再看了。這裏只有alpha剪枝。

對於一個MIN節點(第二層),若能估計出其倒推值的上確界Beta(170和50),並且這個Beta值不大於MIN的父節點(MAX節點)的估計倒推值的下確界Alpha(200),即Alpha≥Beta,則就不必再擴展該MIN節點的其余子節點(畫x的子節點)了,因為這些節點的估值對MIN父節點的倒推值已無任何影響了,這一過程稱為Alpha剪枝。

技術分享



當然還有beta剪枝:

對於一個MAX節點,若能估計出其倒推值的下確界Alpha,並且這個Alpha值不小於MAX的父節點(MIN節點)的估計倒推值的上確界Beta,即Alpha≥Beta,則就不必再擴展該MAX節點的其余子節點了,因為這些節點的估值對MAX父節點的倒推值已無任何影響了。這一過程稱為Beta剪枝。

技術分享


一個MAX節點的Alpha值等於其後繼節點當前最大的最終倒推值,一個MIN節點的Beta值等於其後繼節點當前最小的最終倒推值

算法
搜索中傳遞兩個值,第一個值是Alpha,即搜索到的最好值,體現在if (val > alpha) {alpha = val;}
第二個值是beta,即對於對手來說最壞的值,如果某個著法的結果大於或等於Beta,那麽整個結點就作廢了

體現在:if (val >= beta) {return beta;}
   
  

代碼:
int AlphaBeta(int depth,int alpha,int beta) {
 if (depth == 0) {
  return Evaluate();
 }
 GenerateLegalMoves();
 while (MovesLeft()) {
  MakeNextMove();
  val = -AlphaBeta(depth - 1,-beta, -alpha); //Alpha和Beta是不斷互換的。當函數遞歸時,Alpha和Beta不但取負 // 數而且位置交換了
  UnmakeMove();
  if (val >= beta) {
   return beta;
  }
  if (val > alpha) {
   alpha = val;
  }
 }
 return alpha;
}
把醒目的部分去掉,剩下的就是最小-最大函數。
這個函數需要傳遞的參數有:需要搜索的深度,負無窮大即Alpha,以及正無窮大即Beta:

可能的弱點:

這個算法嚴重依賴於著法的尋找順序。如果你總是先去搜索最壞的著法,那麽Beta截斷就不會發生,因此該算法就如同最小-最大一樣,效率非常低。該算法最終會找遍整個博弈樹,就像最小-最大算法一樣。 

所以,生成全部著法後,排序很重要~ ~ ~

結語:

算法原理方面就到此為止了,比較淺顯,大神勿噴~

象棋AI算法(一)