我目前正在使用 C# 开发基于回合制的基于网格的策略游戏(类似于魔法门之英雄无敌和奇迹时代)。首先关注战斗部分。
一个格子上有多个单元,轮流进行攻击、射击、施法、移动等。
在 AI 编程上遇到了一些困难。我想要一个可以提前计划的人工智能,所以我认为寻找最佳行动的过程可能是这样的:
- 生成可能的 Action 列表
- 对于每个可能的移动:
然后我在 Minimax 算法中重复这个过程,有点类似于创建国际象棋 AI。
与制作国际象棋 AI 的最大区别在于“棋盘表示”(或状态);这个游戏有很多复杂性,简单地将状态存储为整数数组是不够的。有多个单位,各种领域,储存生命值,攻击类型,他们如何移动和特殊能力等等......。
所以我面临的问题是在“模拟移动”部分。我需要制作一个像 (previous state, move) => state
这样的函数。我通常复制以前的状态并在副本上执行移动。但是我找不到一种有效的方法来复制将执行移动的状态,因为它非常复杂,有很多不同的对象。并且此举不应改变之前的状态。
什么是模拟走法而不让它影响当前棋盘的良好代码结构?
这可能可以通过为每一步都设置一个 Undo() 方法来完成,但是随着逻辑变得复杂,将很难跟踪所有已更改的内容。
我可能正在寻找某种软件设计模式,或者如果有人做过类似的事情,我想知道他们是否遇到过这个问题。
Stack Overflow 上的很多问题都在谈论棋盘游戏 AI,但它似乎总是与具有简单棋盘表示的国际象棋/井字棋有关。
这看起来也是大多数约束规划求解器 处理的问题。一般来说,这些实现了 Command
之间的混合。和 Memento
图案。其工作方式如下:
您需要一个代表游戏状态的类(此处称为State
)。包含例如棋盘状态、可以移动的当前玩家、每个玩家拥有的资源(如果这是游戏的一部分)等的东西。您最好将此类状态设为 ICloneable
的实例。 : 这样就可以克隆状态并对它进行假设处理。
所以:
ICloneable
^
|
+-------------------+
| State |
+-------------------+
|+ Clone() : Object |
+-------------------+
接下来你有一个类IMove
这代表了国家可能采取的行动。 IMove
可以Alter
State
.在大多数情况下,此类方法返回的内容为 IAlterResult
。 : 一个存储一些数据的对象,例如撤消移动。
+------------------------------+
| IMove |
+------------------------------+
|+ Alter(State) : IAlterResult |
+------------------------------+
和
+---------------------+
| IAlterResult |
+---------------------+
|+ Undo(State) : void |
+---------------------+
例如,如果您从棋盘上移除一 block 棋子,IAlterResult
存储那件作品所在的位置,以防万一您撤消移动,IAlterResult
可以将棋子(作为示例)放回棋盘上。
每次你做一个IMove
,你推对应的IAlterResult
在 Stack<IAlterResult>
上.因此,该堆栈会跟踪已完成的移动,并且可以通过迭代弹出 IAlterResult
来跟踪。 s 并执行 Undo
命令带来 State
回到原来的状态。
大多数约束规划求解器交错 State
的副本与 IAlterResults
.因此,堆栈看起来像。
+---------------+
| AlterResult5 |
| AlterResult4 |
| Copy2 |
| AlterResult3 |
| AlterResult2 |
| AlterResult1 |
| Original copy |
+---------------+
如果你想回溯四个状态,你可以先看看是否有可用的副本,只需弹出前两个更改(没有 Undo
ing)然后使用 Copy2
并撤销 AlterResult3
和 AlterResult2
.如果撤消操作需要大量计算结果,这可以获得一些效率。
您还可以将您的移动存储在 IAlterMove
中在这种情况下 - 如果移动无法撤消,或者很难撤消 - 您可以简单地回溯到最后保存的副本并执行堆栈中的所有移动。因此,例如,如果您想撤消最后一步( AlterResult5
)但结果无法撤消,则可以使用 Copy2
并重做存储在 AlterResult4
中的移动.