對弈類游戲的人工智能(4)--游戲AI的落地


 

前言:
  對弈類游戲的智能算法, 網上資料頗多, 大同小異. 然而書上得來終覺淺, 絕知此事要躬行. 結合了自己的工程實踐, 簡單匯總整理下. 一方面是對當年的經典<<PC游戲編程(人機博弈)>>表達敬意, 另一方面, 也想對自己當年的游戲編程人生做下回顧.
  承接上三篇博文:
  (1). 評估函數+博弈樹算法
  (2). 學習算法
  (3). 博弈樹優化
  這篇博文着重談談游戲AI落地的問題, 游戲AI不是追求AI的無敵性, 而是應該迎合不同級別的用戶水平. 同時游戲本身的用戶體驗, 是需要游戲開發者, 好好思索和斟酌的.

案例反思:
  (1). 案例一:
  以前寫過J2ME版的中國象棋(模擬器性能好於真機的幸福時代).在模擬器上測試, 搜索深度設置為3,在時間消耗和智能表現達到很好的均衡,基本在2秒之內決策完成.
  后來客戶用真機去測試的時候, 反饋沒有絲毫響應, 當時想: 糟了,是不是遇到了機器相關的問題? 后來再反饋的時候,說是等了80多秒,才走了一步.
  這件事, 對我個人而言吸取的教訓還挺大的, 有些參數不能基於經驗來設置,對於不同機器和配置,需合理的選定配置值.
  總而言之: 適配很重要, 不光在不同機器的分辨率上需要, 性能預估也需要.
  (2). 案例二:
  有次寫完黑白棋, 一開始各種被虐(內心其實很挺開心的). 在不斷的嘗試各種路線后,終於找到一種方式擊敗電腦, 由於電腦采用了靜態評估函數, 每次選最優解. 導致電腦沒有反饋能力, 一直在犯同一個錯誤. 這個問題讓我(玩家)索然無味. 體驗很不好.
  由此可見, 在智能AI中, 需要引入模糊性, 或者說是不確定.

迭代搜索:
  再解決上述問題之前, 讓我們先來講講迭代搜索的思路和實現方式.
  迭代搜索逐步加深搜索深度, 進行博弈過程.

void negamax_driver(GameState S, int depth, Move best_move) {
  // 負無窮 ~ 正無窮
  (alpha, beta) <= (-INFINITY, INFINITY)
  foreach ( move in candidate list ) {
    S' = makemove(S);
    value = -negamax(S', depth - 1, -beta, -alpha);
    unmakemove(S')
    // 博弈樹第一層不存在alpha+beta剪枝, 用於保存最優解
    if ( value > alpha ) {  
      alpha = value;
      tmp_best_move = move
    }  
  }
    best_move = tmp_best_move
}

  函數negamax_driver不同於negamax函數, 它是極大極小搜索的第一層, 其不存在alpha+beta剪枝, 而且用於保存實際最優的解. 因此單獨抽取出來.

Move iterative_deepening_search(GameState S) {
  // 定義best_move	
  Move best_move
  // 遍歷深度, 從 1 逐步加深
  for ( depth = 1; ; depth++ ) {
    negamax_driver(S, depth, best_move)
    // 判斷是否滿足退出條件, 一遍為超時判斷
    if ( timeout() ) {
      break;
    }	
   }
   return best_move
}

  函數iterative_deepening_search則形象描述了迭代搜索的整個過程, 逐步加深搜索深度, 然后調用負極大值搜索. 其中退出條件特別重要. 一般采用超時判斷來作為退出條件的檢測.
  迭代深搜提供了一個很好的思路, 或許你會問: 迭代搜索不是存在很多的重復計算嗎? 其性能會不會很糟糕嗎?
  其實不然, 我們簡單算一筆賬:

假設F(n)為深度n的性能消耗, m為可候選步數. 
遞推歸納公式為:
  F(n+1) = m * F(n)                     (m 遠大於2)
對於單獨進行深度為n的性能消耗為:
  F(n) = m^(n-1) * F(1)
進行1~n的迭代深搜性能代價為:  
  S(n) = F(1) + F(2) + ... + F(n) = F(n) * (1 + 1/m + .. + 1/m^(n - 1))
冪級數的極限:
  S(n) = F(n) * m / (m - 1)				(n 趨向無限大, m >> 2)
結論: 
  F(n) < S(n) < m / (m - 1) * F(n)                     

  由此我們可得: 迭代深搜和一般深搜相比, 其性能多消耗的那部分, 基本可忽略.

自動適配:
  對於案例一, 一種解決思路是: 引入一個帶超時的搜索接口. 這樣對於任何硬件(CPU, 內存)條件, 既能充分利用資源達到最好的效果, 又能保證時間適度的用戶體驗.
  而我們在闡述迭代搜索的原理過程, 實際上提供了很好的思路去解決這個問題.
  對於帶超時的搜索方式, 原本的迭代加深代碼框架, 已能完成任務. 但其超時判斷有些延后, 我們可以再做修改, 來精確控制超時.
  (1). 超時判斷添加至函數negamax開頭, 即深入到每個搜索節點
  (2). 只要搜索節點判斷超時, 就立即跳躍回調用頂層, 並宣告該深度搜索失敗

bool demo(S) {
  // 判斷超時, 若超時返回 false
  if ( timeout() ) {
    return false;
  }
  // 任務拆分/過程遞進
  for (  successor S' in S  ) {
    // 若遇到子調用匯報超時, 則立馬返回
    if ( !demo(S') ) {
      return false;
    }
    // 正常業務處理
  }
  // 執行成功, 沒有超時
  return true;
}

  該代碼框架, 演示了如何立即返回調用頂層的技巧.
  (3). 帶超時的迭代搜索, 選擇沒有超時的最深高度求解的決策步, 作為最終的決策步.

模糊化:
  一般的游戲AI並沒學習模型, 用於反饋增強. 對於案例二的問題, 模糊化勢在必行.
  這邊簡單談談幾種可行的方式:
  (1). 引入多套評估函數, 每次隨機選擇一種
  (2). 引入抖動, 在設置權重向量時, 可以隨機微調個別因素權重值
  (3). 在決策過程中, 偶爾按概率選擇 次優, 次次優, 甚至其他可行步
  這些都是避免游戲AI固定化的一種思路.

總結:
  本文講解了對應案例一, 案例二的合理解決方案. 游戲AI要落地, 需要對算法本身做一些潤色工作.

寫在最后:
  
如果你覺得這篇文章對你有幫助, 請小小打賞下. 其實我想試試, 看看寫博客能否給自己帶來一點小小的收益. 無論多少, 都是對樓主一種由衷的肯定.

   


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM