炸弹人的极致


11

我正在开发Bomberman游戏的克隆,并且正在尝试不同类型的AI。首先,我使用A *在状态空间中进行搜索,现在我想使用Minimax算法尝试其他方法。我的问题是我发现假定的每位minimax选手都在交替。但是在Bomberman中,每个玩家都同时做出一些动作。我认为我可以为一个游戏滴答生成所有可能的状态,但是在有四个玩家和5个基本动作(4个动作和炸弹放置)的情况下,它在游戏树的第一级给出5 ^ 4个状态。该值将随着下一个级别的增长呈指数增长。我想念什么吗?有什么方法可以实现它,还是应该使用完全不同的算法?谢谢你的建议


1
尽管这个话题有点离题,但我想对AI进行的一件事是为AI使用目标或个性。可能是诸如积蓄力量,不进取,寻求报复,仓促之类的事情。通过这样的目标,您可以大致判断出应该向哪个方向前进,并且只有在炸弹将进度推进到目标时才投下炸弹(如果距离您正在寻找的玩家或您想摧毁的街区很近)。
本杰明·

2
是的,您缺少了一些东西,但是您不会感谢我指出这些问题,因为它们会使情况变得更糟。没有5个基本动作。一些方块有5个“移动”(4个方向并保持静止);其他人有3人(因为他们在两个方向上都被封锁);平均而言,它是4。但是您可以在跑步时投下炸弹,因此平均而言,分支系数为8。具有高速启动功能的人可以进行更多移动,从而有效地提高其分支系数。
彼得·泰勒

我使用蒙特卡洛树搜索为您的问题提供了答案。
SDwarfs

在与Bomberman一样多的选择中,Minimax根本没有用。在进行足够的动作以判断是否明智之前,您将耗尽搜索能力。
罗伦·佩希特尔

Answers:


8

像轰炸机之类的实时战略游戏在AI方面遇到困难。您希望它是智能的,但同时又不能完美。

如果AI是完美的,您的玩家会感到沮丧。因为它们总是丢失,或者您每秒获得0.3帧。

如果不够智能,您的播放器将变得无聊。

我的建议是具有两个AI功能,一个确定AI的去向,另一个确定何时最好放下炸弹。您可以使用运动预测之类的方法来确定敌人是否正朝某个地点移动,如果炸弹被扔到当前位置,这将是危险的。

根据难度,您可以修改这些功能以提高或降低难度。


2
时间,挫折和无聊不是问题。我正在Bomberman中撰写有关不同AI方法的学士论文,并对其进行比较。因此,如果它是完美的,那就更好了。我现在正停留在那个极小值上
Billda

1
您将在minimax算法中遇到的问题是处理时间。您需要跟踪所有敌人的动作,并确定他们的游戏风格和反击游戏风格。看来您已经意识到了这一点,但这对于实时游戏而言可能是一项艰巨的任务,而又不会降低游戏的速度。您将需要实时确定自己的动作,而不是构建游戏树,也许是构建一种机器学习算法,使其发挥得越好就越好?
UnderscoreZero

4

您已经注意到,炸弹人太复杂了,无法模拟为回合制游戏。推断任何可能的自己的决定加上其他所有玩家的所有可能的决定都无法解决。

相反,您应该使用更具策略性的方法。

您应该问自己:人类玩家在扮演轰炸机时如何做出决定?通常,玩家应遵循四个基本优先级:

  1. 避免炸弹爆炸区域
  2. 放置炸弹,使其他人无法避开爆炸区域
  3. 收集能量
  4. 放置炸弹炸毁岩石

可以通过创建“危险图”来实现第一要务。放置炸弹时,所有炸弹覆盖的瓷砖都应标记为“危险”。炸弹爆炸得越早(牢记连锁反应!),危险等级就越高。只要AI注意到它处于高危险区域,它就应该移开。(无论出于何种原因)绘制路径时,应避免具有高危险等级的字段(可以通过人为地增加路径成本来实现)。

可以进一步增强危险地图的计算能力,以保护AI免受愚蠢的决定(例如进入另一个玩家附近很难逃脱的区域)。

这应该已经创建了一个合理的防御性AI。那么进攻呢?

当AI意识到目前它相当安全时,它应该计划进攻性机动:它应该考虑如何通过放置炸弹本身来增加其他玩家周围的危险地图。在选择放置炸弹的位置时,它应首选靠近的位置,这样它就不必移动那么远。当生成的危险图不允许合理的逃生路线时,它也应忽略炸弹的位置。


我玩游戏的有限经验是,您通常必须放置多枚炸弹才能杀死有能力的对手-策略需要考虑到这一点。我已经按照您的策略对付AI,除非您陷入困境,否则它们无法有效地杀死您。
罗伦·佩希特尔

4

我认为我可以为一个游戏滴答生成所有可能的状态,但是在有四个玩家和5个基本动作(4个动作和炸弹放置)的情况下,它在游戏树的第一级给出5 ^ 4个状态。

正确!您需要为每个游戏滴答搜索所有5 ^ 4(甚至6 ^ 4,因为您可以在4个方向上行走,停止并“放炸弹”?)动作。但是,当玩家已经决定移动时,要花一些时间才能执行移动(例如10个游戏滴答声)。在此期间,可能性减少。

该值将随着下一个级别的增长呈指数增长。我想念什么吗?有什么方法可以实现它,还是应该使用完全不同的算法?

您可以使用哈希表仅计算一次相同的游戏状态“子树”。想象玩家A上下走动,而所有其他玩家“等待”,则您最终处于相同的游戏状态。与“左右”或“左右”相同。同样,“先左上移”和“先左上移”也会导致相同的状态。使用哈希表,您可以针对已评估的游戏状态“重用”计算出的分数。这大大降低了生长速度。从数学上讲,它减小了指数增长函数的基础。为了弄清楚它在多大程度上降低了复杂性,让我们看一下如果一个玩家只是向上/向下/向左/向右/停止移动,则与地图上可到达的位置(=不同的游戏状态)相比,仅一个玩家可能的移动。

深度1:5次移动,5个不同状态,此递归的5个其他状态

深度2:25个移动,13个不同的状态,此递归的8个其他状态

深度3:6125次移动,25个不同的状态,此递归的12个其他状态

为了使之可视化,请自己回答:一招,二招,三招可以到达地图上的哪些字段。答案是:与起始位置的最大距离为1、2或3的所有字段。

使用HashTable时,您只需评估每个可到达的游戏状态(在我们的示例中为25,深度3)。而如果没有HashTable,则需要对其进行多次评估,这意味着在深度级别3上需要进行6125次评估,而不是25次评估。最好:一旦计算了HashTable条目,便可以在以后的时间步骤中重复使用它...

您还可以使用不值得进行更深入搜索的增量加深和alpha-beta修剪“剪切”子树。对于国际象棋,这将搜索到的节点数减少到大约1%。可以在以下视频中找到有关alpha-beta修剪的简短介绍:http : //www.teachingtree.co/cs/watch? concept_name=Alpha-beta+Pruning

http://chessprogramming.wikispaces.com/Search是一个进行进一步研究的好起点。该页面与国际象棋有关,但是搜索和优化算法完全相同。

另一个(但很复杂)的AI算法-更适合于游戏-是“时间差异学习”。

问候

斯特凡

PS:如果您减少了可能的游戏状态数量(例如,地图尺寸非常小,每位玩家只有一颗炸弹,没有别的),那么就有机会预先计算所有游戏状态的评估值。

- 编辑 -

您还可以使用minimax计算的离线计算结果来训练神经元网络。或者您可以使用它们来评估/比较手动实施的策略。例如,您可以实施一些建议的“个性”和一些启发式检测,以找出在哪种情况下哪种策略是好的。因此,您应该“分类”情况(例如游戏状态)。这也可以由神经网络来处理:训练神经网络以预测在当前情况下哪种手动编码策略发挥最佳效果并执行该策略。对于真实游戏,这将产生非常好的实时决策。这比通过其他方式可以实现的低深度限制搜索要好得多,因为脱机计算花费多长时间(在游戏开始之前)并不重要。

-编辑#2-

如果仅每1秒重新计算一次最佳移动,则还可以尝试执行更多更高级别的计划。那是什么意思 您知道在一秒钟内可以执行多少步。因此,您可以列出一个可到达的位置列表(例如,如果在1秒内进行3次移动,则您将有25个可到达的位置)。然后,您可以像这样计划:转到“位置x并放置炸弹”。正如其他人建议的那样,您可以创建一个“危险”地图,该地图用于路由算法(如何定位到x?应该选择哪个路径[在大多数情况下可能会有一些变化])。与庞大的HashTable相比,这减少了内存消耗,但产生的最佳结果却更少。但是由于它使用较少的内存,因此可能会由于缓存效果(更好地使用L1 / L2内存缓存)而更快。

另外:您可以进行预搜索,其中仅包含一个玩家的移动,以找出导致丢失的变化。因此,将所有其他玩家从游戏中移除。。。存储每个玩家可以选择的组合而不会丢失。如果只有松散的举动,请寻找玩家最长存活时间的举动组合。要存储/处理这种树形结构,您应该使用带有索引指针的数组,如下所示:

class Gamestate {
  int value;
  int bestmove;
  int moves[5];
};

#define MAX 1000000
Gamestate[MAX] tree;

int rootindex = 0;
int nextfree = 1;

每个状态都有一个评估“值”,并在移动时(0 =停止,1 =向上,2 =右,3 =向下,4 =左)链接到下一个游戏状态,方法是将数组索引存储在move [0]中的“ tree”中]移至[4]。要递归构建树,可能看起来像这样:

const int dx[5] = { 0,  0, 1, 0, -1 };
const int dy[5] = { 0, -1, 0, 1,  0 };

int search(int x, int y, int current_state, int depth_left) {
  // TODO: simulate bombs here...
  if (died) return RESULT_DEAD;

  if (depth_left == 0) {
    return estimate_result();
  }

  int bestresult = RESULT_DEAD;

  for(int m=0; m<5; ++m) {
    int nx = x + dx[m];
    int ny = y + dy[m];
    if (m == 0 || is_map_free(nx,ny)) {
      int newstateindex = nextfree;
      tree[current_state].move[m] = newstateindex ;
      ++nextfree;

      if (newstateindex >= MAX) { 
        // ERROR-MESSAGE!!!
      }

      do_move(m, &undodata);
      int result = search(nx, ny, newstateindex, depth_left-1);
      undo_move(undodata);

      if (result == RESULT_DEAD) {
        tree[current_state].move[m] = -1; // cut subtree...
      }

      if (result > bestresult) {
        bestresult = result;
        tree[current_state].bestmove = m;
      }
    }
  }

  return bestresult;
}

这种树结构要快得多,因为动态分配内存确实非常慢!但是,存储搜索树还是很慢的……因此,这更多的是启发。


0

它是否有助于想象,每个人都确实需要转弯?

从技术上讲,它们实际上是在底层系统中执行的,但是由于事物是交错和重叠的,因此它们似乎在同时运行。

还请记住,不必在帧动画后运行AI 。许多成功的休闲游戏大约每秒钟运行一次AI算法,向AI控制的角色提供有关他们应该去哪里或应该做什么的信息,然后该信息用于控制AI角色在其他框架上。


我不是在动画的每一帧中都在计算AI,而是每一秒钟。我的环境每秒钟都会收集所有参与者的动作,并向他们发送新的更新状态。
Billda
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.