游戏开发论坛

 找回密码
 立即注册
搜索
查看: 4708|回复: 0

对弈类游戏的人工智能设计(4)——游戏AI的落地

[复制链接]

1万

主题

1万

帖子

3万

积分

论坛元老

Rank: 8Rank: 8

积分
36572
发表于 2016-1-27 14:42:18 | 显示全部楼层 |阅读模式
3081241428.jpg

  GameRes游资网授权发布 文/mumuxinfei

  这篇博文着重谈谈游戏AI落地的问题, 游戏AI不是追求AI的无敌性, 而是应该迎合不同级别的用户水平。 同时游戏本身的用户体验, 是需要游戏开发者, 好好思索和斟酌的。

  案例反思:

  案例一:

  以前写过J2ME版的中国象棋(模拟器性能好于真机的幸福时代)。在模拟器上测试, 搜索深度设置为3,在时间消耗和智能表现达到很好的均衡,基本在2秒之内决策完成。

  后来客户用真机去测试的时候, 反馈没有丝毫响应, 当时想: 糟了,是不是遇到了机器相关的问题? 后来再反馈的时候,说是等了80多秒,才走了一步。

  这件事, 对我个人而言吸取的教训还挺大的, 有些参数不能基于经验来设置,对于不同机器和配置,需合理的选定配置值。

  总而言之: 适配很重要, 不光在不同机器的分辨率上需要, 性能预估也需要。

  案例二:

  有次写完黑白棋, 一开始各种被虐(内心其实很挺开心的)。 在不断的尝试各种路线后,终于找到一种方式击败电脑, 由于电脑采用了静态评估函数, 每次选最优解。 导致电脑没有反馈能力, 一直在犯同一个错误。 这个问题让我(玩家)索然无味。 体验很不好。

  由此可见, 在智能AI中, 需要引入模糊性, 或者说是不确定。

  迭代搜索:

  再解决上述问题之前,让我们先来讲讲迭代搜索的思路和实现方式。

  迭代搜索逐步加深搜索深度, 进行博弈过程。

  1. void negamax_driver(GameState S, int depth, Move best_move) {
  2.   // 负无穷 ~ 正无穷
  3.   (alpha, beta) <= (-INFINITY, INFINITY)
  4.   foreach ( move in candidate list ) {
  5.     S' = makemove(S);
  6.     value = -negamax(S', depth - 1, -beta, -alpha);
  7.     unmakemove(S')
  8.     // 博弈树第一层不存在alpha+beta剪枝, 用于保存最优解
  9.     if ( value > alpha ) {
  10.       alpha = value;
  11.       tmp_best_move = move
  12.     }
  13.   }
  14.     best_move = tmp_best_move
  15. }
复制代码

  函数negamax_driver不同于negamax函数, 它是极大极小搜索的第一层, 其不存在alpha+beta剪枝, 而且用于保存实际最优的解。 因此单独抽取出来。

  1. Move iterative_deepening_search(GameState S) {
  2.   // 定义best_move   
  3.   Move best_move
  4.   // 遍历深度, 从 1 逐步加深
  5.   for ( depth = 1; ; depth++ ) {
  6.     negamax_driver(S, depth, best_move)
  7.     // 判断是否满足退出条件, 一遍为超时判断
  8.     if ( timeout() ) {
  9.       break;
  10.     }  
  11.   }
  12.   return best_move
  13. }
复制代码

  函数iterative_deepening_search则形象描述了迭代搜索的整个过程, 逐步加深搜索深度, 然后调用负极大值搜索。 其中退出条件特别重要。 一般采用超时判断来作为退出条件的检测。

  迭代深搜提供了一个很好的思路, 或许你会问: 迭代搜索不是存在很多的重复计算吗? 其性能会不会很糟糕吗?

  其实不然, 我们简单算一笔账:

  1. 假设F(n)为深度n的性能消耗, m为可候选步数.
  2. 递推归纳公式为:
  3.   F(n+1) = m * F(n)                     (m 远大于2)
  4. 对于单独进行深度为n的性能消耗为:
  5.   F(n) = m^(n-1) * F(1)
  6. 进行1~n的迭代深搜性能代价为:
  7.   S(n) = F(1) + F(2) + ... + F(n) = F(n) * (1 + 1/m + .. + 1/m^(n - 1))
  8. 幂级数的极限:
  9.   S(n) = F(n) * m / (m - 1)             (n 趋向无限大, m >> 2)
  10. 结论:
  11.   F(n) < S(n) < m / (m - 1) * F(n)
复制代码

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

  自动适配:

  对于案例一, 一种解决思路是: 引入一个带超时的搜索接口。 这样对于任何硬件(CPU, 内存)条件, 既能充分利用资源达到最好的效果, 又能保证时间适度的用户体验。

  而我们在阐述迭代搜索的原理过程, 实际上提供了很好的思路去解决这个问题。

  对于带超时的搜索方式, 原本的迭代加深代码框架, 已能完成任务。 但其超时判断有些延后, 我们可以再做修改, 来精确控制超时。

  (1)超时判断添加至函数negamax开头, 即深入到每个搜索节点上

  (2)只要搜索节点判断超时, 就立即跳跃回调用顶层, 并宣告该深度搜索失败

  1. bool demo(S) {
  2.   // 判断超时, 若超时返回 false
  3.   if ( timeout() ) {
  4.     return false;
  5.   }
  6.   // 任务拆分/过程递进
  7.   for (  successor S' in S  ) {
  8.     // 若遇到子调用汇报超时, 则立马返回
  9.     if ( !demo(S') ) {
  10.       return false;
  11.     }
  12.     // 正常业务处理
  13.   }
  14.   // 执行成功, 没有超时
  15.   return true;
  16. }
复制代码

  该代码框架, 演示了如何立即返回调用顶层的技巧。

  (3)带超时的迭代搜索, 选择没有超时的最深高度求解的决策步, 作为最终的决策步。

  模糊化:

  一般的游戏AI并没学习模型, 用于反馈增强。 对于案例二的问题, 模糊化势在必行。

  这边简单谈谈几种可行的方式:

  (1)引入多套评估函数, 每次随机选择一种

  (2)引入抖动, 在设置权重向量时, 可以随机微调个别因素权重值

  (3)在决策过程中, 偶尔按概率选择 次优, 次次优, 甚至其他可行步

  这些都是避免游戏AI固定化的一种思路。

  总结:

  本文讲解了对应案例一, 案例二的合理解决方案。 游戏AI要落地, 需要对算法本身做一些润色工作。

  写在最后:

  如果你觉得这篇文章对你有帮助, 请小小打赏下。 其实我想试试, 看看写博客能否给自己带来一点小小的收益。 无论多少, 都是对楼主一种由衷的肯定。

  相关阅读:

  对弈类游戏的人工智能设计(1):评估函数+博弈树算法

  对弈类游戏的人工智能设计(2):性能优化

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2024-5-20 06:14

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表