使用minimax搜索具有不完全信息的纸牌游戏

时间:2012-09-30 23:46:01

标签: artificial-intelligence minimax

我想使用minimax搜索(使用alpha-beta修剪),或者更确切地说是使用negamax搜索来使计算机程序玩纸牌游戏。

纸牌游戏实际上由4名玩家组成。因此,为了能够使用minimax等,我将游戏简化为“我”以对抗“其他人”。在每次“移动”之后,您可以客观地从游戏本身读取当前状态的评估。当所有4名玩家都已经放置了这张牌时,最高赢得了所有牌 - 并且这些牌值很重要。

由于你不知道其他3名玩家之间的牌分布是如何确切的,我认为你必须使用不属于你的牌来模拟所有可能的分布(“世界”)。你有12张牌,其他3名玩家共有36张牌。

所以我的方法是这个算法,其中player是1到3之间的数字,表示程序可能需要找到移动的三个计算机玩家。并且-player代表对手,即所有其他三名球员在一起。

private Card computerPickCard(GameState state, ArrayList<Card> cards) {
    int bestScore = Integer.MIN_VALUE;
    Card bestMove = null;
    int nCards = cards.size();
    for (int i = 0; i < nCards; i++) {
        if (state.moveIsLegal(cards.get(i))) { // if you are allowed to place this card
            int score;
            GameState futureState = state.testMove(cards.get(i)); // a move is the placing of a card (which returns a new game state)
            score = negamaxSearch(-state.getPlayersTurn(), futureState, 1, Integer.MIN_VALUE, Integer.MAX_VALUE);
            if (score > bestScore) {
                bestScore = score;
                bestMove = cards.get(i);
            }
        }
    }
    // now bestMove is the card to place
}

private int negamaxSearch(int player, GameState state, int depthLeft, int alpha, int beta) {
    ArrayList<Card> cards;
    if (player >= 1 && player <= 3) {
        cards = state.getCards(player);
    }
    else {
        if (player == -1) {
            cards = state.getCards(0);
            cards.addAll(state.getCards(2));
            cards.addAll(state.getCards(3));
        }
        else if (player == -2) {
            cards = state.getCards(0);
            cards.addAll(state.getCards(1));
            cards.addAll(state.getCards(3));
        }
        else {
            cards = state.getCards(0);
            cards.addAll(state.getCards(1));
            cards.addAll(state.getCards(2));
        }
    }
    if (depthLeft <= 0 || state.isEnd()) { // end of recursion as the game is finished or max depth is reached
        if (player >= 1 && player <= 3) {
            return state.getCurrentPoints(player); // player's points as a positive value (for self)
        }
        else {
            return -state.getCurrentPoints(-player); // player's points as a negative value (for others)
        }
    }
    else {
        int score;
        int nCards = cards.size();
        if (player > 0) { // make one move (it's player's turn)
            for (int i = 0; i < nCards; i++) {
                GameState futureState = state.testMove(cards.get(i));
                if (futureState != null) { // wenn Zug gültig ist
                    score = negamaxSuche(-player, futureState, depthLeft-1, -beta, -alpha);
                    if (score >= beta) {
                        return score;
                    }
                    if (score > alpha) {
                        alpha = score; // alpha acts like max
                    }
                }
            }
            return alpha;
        }
        else { // make three moves (it's the others' turn)
            for (int i = 0; i < nCards; i++) {
                GameState futureState = state.testMove(cards.get(i));
                if (futureState != null) { // if move is valid
                    for (int k = 0; k < nCards; k++) {
                        if (k != i) {
                            GameState futureStateLevel2 = futureState.testMove(cards.get(k));
                            if (futureStateLevel2 != null) { // if move is valid
                                for (int m = 0; m < nCards; m++) {
                                    if (m != i && m != k) {
                                        GameState futureStateLevel3 = futureStateLevel2.testMove(cards.get(m));
                                        if (futureStateLevel3 != null) { // if move is valid
                                            score = negamaxSuche(-player, futureStateLevel3, depthLeft-1, -beta, -alpha);
                                            if (score >= beta) {
                                                return score;
                                            }
                                            if (score > alpha) {
                                                alpha = score; // alpha acts like max
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
            return alpha;
        }
    }
}

这似乎工作正常,但是对于深度为1(depthLeft=1),程序已经需要平均计算50,000次移动(放置的牌)。当然,这太过分了!

所以我的问题是:

  1. 实施是否正确?你能模拟这样的游戏吗?关于不完善的信息,特别是?
  2. 如何在速度和工作负荷方面改进算法?
  3. 例如,我可以将可能移动的集合减少到50%的随机设置以提高速度,同时保持良好的效果吗?
  4. 我发现UCT algorithm是一个很好的解决方案(也许)。你知道这个算法吗?你能帮我实现吗?

2 个答案:

答案 0 :(得分:8)

我想澄清所接受的答案并未真正涉及的细节。

在许多纸牌游戏中,您可以对对手可能拥有的未知卡进行采样,而不是生成所有卡。到目前为止,你可以考虑一些信息,比如短套装和持有某些牌的可能性,这样做是为了加权每个可能手牌的可能性(每一手牌都是我们独立解决的可能世界)。然后,您使用完美的信息搜索解决每只手。对所有这些世界的最好的举动往往是最好的举措 - 有一些警告。

在像扑克这样的游戏中,这种方式运作得很好 - 游戏完全是关于隐藏的信息。您必须精确平衡您的行为,以保持手的信息隐藏。

但是,在基于技巧的纸牌游戏等游戏中,这种效果非常好 - 特别是因为新信息一直在被曝光。无论如何,真正优秀的球员都知道每个人都拥有什么。因此,相当强大的Skat和Bridge计划基于这些想法。

如果你可以完全解决潜在的世界,那就是最好的,但是如果你不能,你可以使用minimax或UCT来选择每个世界中最好的动作。还有混合算法(ISMCTS)试图将这个过程混合在一起。这里的说法要小心。简单的采样方法更容易编码 - 您应该在更复杂的方法之前尝试更简单的方法。

以下是一些研究论文,可以提供有关何时对不完全信息的抽样方法运作良好的更多信息:

Understanding the Success of Perfect Information Monte Carlo Sampling in Game Tree Search(本文分析了抽样方法何时可行。)

Improving State Evaluation, Inference, and Search in Trick-Based Card Games(本文描述了在Skat中使用抽样)

Imperfect information in a computationally challenging game(本文描述了Bridge中的采样)

Information Set Monte Carlo Tree Search(本文合并了抽样和UCT /蒙特卡洛树搜索以避免第一次参考中的问题。)

在接受的答案中,基于规则的方法存在的问题是,他们无法利用超出创建初始规则所需的计算资源。此外,基于规则的方法将受到您可以编写的规则的力量的限制。基于搜索的方法可以利用组合搜索的力量来产生比程序作者更强大的游戏。

答案 1 :(得分:5)

Minimax搜索,因为你已经实现了它是错误的方法,因为游戏存在很多不确定性。由于你不知道其他玩家之间的牌分布,你的搜索将花费指数的时间来探索根据牌的实际分配不可能发生的游戏。

我认为一个更好的方法是,当你很少或没有关于其他球员手牌的信息时,首先要制定好的比赛规则。比如:

  1. 如果你在一轮比赛中先发挥,那就打出你最低的牌,因为你几乎没有机会赢得这一轮。
  2. 如果你在一轮比赛中打得最后,可以打出最低牌,赢得比赛。如果你不能赢得这一轮,那么就打最低的牌。
  3. 让你的程序最初不打扰搜索,只是按照这些规则玩并让它假设所有其他玩家也将使用这些启发式。因为程序会观察到第一个和最后一个每轮玩家可以建立一个关于每个玩家可能拥有的牌的信息表。例如。本轮将获得9分,但是3号球员没有参赛,所以他不能拥有9或者更高的牌。当收集有关每个玩家手牌的信息时,搜索空间最终将被限制在可能的游戏的极小极大搜索可以产生关于下一张牌的有用信息的点。