3

对于我的 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 以获得某种形式的结果。

4

0 回答 0