对于我的 AI 课程中的一个项目,我们正在巧妙地为游戏制作 AI,但在创建游戏状态树时遇到了麻烦。我们的树由节点表示,其中包含 GameStates,它们本身包含玩家列表(以及所有相应的数据,例如他们的分数、他们手中的牌和他们的玩家编号),游戏的当前状态板,还有一袋剩余的瓷砖可供选择。根节点是当前状态,它通过 Edge 对象连接到子节点,其中包含父节点和子节点,以及将父节点引导到子节点的 Action。该动作包含放置在板上的图块、图块包含的颜色以及将放置图块的六边形网格上的坐标。
我们的问题是,在创建新的游戏状态时,我们正在更新状态中包含的数据,而不是创建游戏状态的副本。通常,我们只会为根的每个子节点制作游戏状态的深层副本,等等。
这就是事情变得棘手的地方。我们的六边形网格库来自 Java 库Hexameter,它是一个六边形网格,其中包含可以包含卫星数据的六边形对象,这是我们用来表示我们放置的瓷砖/颜色的东西。问题在于,由于 satelliteData 指的是原始对象,并且实际上并不包含对象,因此我们无法深度克隆我们的 GameState(据我所知)。
由于我们的最后期限在不到一周的时间内很快就要到了,而且过去几天我们一直在努力解决我们的问题,所以我们最终求助于 StackOverFlow 寻求帮助。看看我们的 GameState 结构:(请注意,下面的所有代码都被删除了 getter、setter 和不相关的方法)
public class GameState {
private Player[] players;
private Board currentBoard;
private Bag currentBag;
private Player gamingPlayer;
public GameState() {
players = new Player[2];
currentBoard = new Board();
currentBag = new Bag(Pieces.createBagPieces());
players[0] = new Player(1, currentBag.pickSix(), true);
for (Tile t: players[0].getHand().getPieces()){
players[0].addToVisibleTiles(t);
}
players[1] = new Player(2, currentBag.pickSix(), true);
for (Tile t: players[1].getHand().getPieces()){
players[1].addToVisibleTiles(t);
}
gamingPlayer = players[0];
}
private GameState(Player[] players, Board currentBoard, Bag currentBag,
Player gamingPlayer) {
this.players = players;
this.currentBoard = currentBoard;
this.currentBag = currentBag;
this.gamingPlayer = gamingPlayer;
}
}
看看我们的两个构造函数——我们的第一个实例化初始游戏状态,第二个接受我们想要在更新游戏时传递的参数。问题是,我们意识到
this.players = players;
this.currentBoard = currentBoard;
this.currentBag = currentBag;
this.gamingPlayer = gamingPlayer;
更改原始状态,而不是新状态。
我们最初考虑做的是在第二个构造函数中,传递我们想要更改的状态,然后为新状态创建新变量并根据前一个状态的值实例化它们。问题是,我们现在没有任何方法可以访问这些。
我们研究了在游戏树中为我们的子节点使用享元,它代表了我们基于动作所做的更改:
public class Action {
private Hexagon h1;
private Hexagon h2;
private Tile tile;
public Action(Hexagon h1, Hexagon h2, Tile t){
this.h1 = h1;
this.h2 = h2;
this.tile = t;
}
public Action(){
this.h1 = null;
this.h2 = null;
this.tile = null;
}
}
我们以这样的方式应用动作来创建下一个 GameState:
public GameState applyAction(Action a){
HexagonActor first = null;
GameState nextState;
if (a.getH1().getSatelliteData().isPresent()){
// create a link for the actor and hex of the next hex from current
Link hexLink = (Link) a.getH1().getSatelliteData().get();
HexagonActor currentHexActor = hexLink.getActor();
currentHexActor.setHexColor(a.getTileColors()[0]);
first = currentHexActor;
Player.updateScore(Player.scoreGain(currentHexActor, currentBoard.getGrid(), currentHexActor), gamingPlayer);
}
if (a.getH2().getSatelliteData().isPresent()){
// create a link for the actor and hex of the next hex from current
Link hexLink = (Link) a.getH2().getSatelliteData().get();
HexagonActor currentHexActor = hexLink.getActor();
currentHexActor.setHexColor(a.getTileColors()[1]);
if (first != null){
Player.updateScore(Player.scoreGain(currentHexActor, currentBoard.getGrid(), first), gamingPlayer);
}
}
gamingPlayer.getHand().removeFromHand(a.getTile());
Tile newTile = currentBag.pickTile();
gamingPlayer.getHand().pickFromBag(newTile);
gamingPlayer.addToVisibleTiles(newTile);
nextState = new GameState(players, currentBoard, currentBag, changeGamingPlayer());
return nextState;
}
如您所见,这并不能正确“克隆”游戏状态。在对 StackOverFlow 进行深入研究后,我们提出了 3 个不错的选择:
- 通过手动重构我们的代码以具有多个构造函数、getter、setter 和用于创建每个类的副本的方法来进行深度克隆。
- 使用 Serializable 接口,但是我们从未使用过它,并且由于截止日期,不愿意跳进去尝试解决它。
- 使用享元模式,尽管它似乎是我们最好的选择(并且显然实现起来很复杂),但我们也从未使用过它。
我在问是否有人有任何“快速修复”,我们可以使用它来获得某种形式的“工作”树,我们可以在(MCTS、A*、ExpectiMax、Greedy 等)上运行基本 AI 以获得某种形式的结果。