使用极小极大搜索处理具有不完全信息的纸牌游戏。

11

我想使用极小化极大搜索(带有alpha-beta剪枝)或者更确切地说是negamax搜索,来让计算机程序玩一种纸牌游戏。

这个纸牌游戏实际上包括4个玩家。因此为了能够使用极小化极大等算法,我将游戏简化为“我”对抗“其他人”。每次“移动”后,您可以客观地从游戏本身读取当前状态的评估。当4名玩家都已放置纸牌后,最高分数将赢得所有纸牌的总数,而且纸牌的价值也要计算在内。

由于不知道其他3个玩家手中的牌具体分布情况,我认为必须模拟所有可能的牌分布(“世界”),其中包括非你所拥有的牌。你有12张牌,而其他三个玩家总共有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算法是一个好的解决方案(也许)。您知道这个算法吗?您能帮我实现它吗?
2个回答

13
我希望澄清一些被接受的答案没有详细解释的细节。
在许多纸牌游戏中,您可以对可能拥有的未知卡牌进行抽样,而不是生成所有卡牌。在进行这种抽样时,您可以考虑短套和根据游戏情况持有某些卡牌的概率来加权每个可能手的可能性(每个手是我们将单独解决的可能世界)。然后,使用完全信息搜索解决每个手。所有这些可能世界上的最佳移动通常是最佳移动 - 有一些警告。
在像扑克这样的游戏中,这种方法效果并不好 - 游戏全部关于隐藏信息。您必须精确平衡您的行动以保持与您手中的信息相关的隐私。
但是,在像基于花色的纸牌游戏中,这种方法效果相当不错 - 特别是因为新信息正在不断揭示。真正优秀的玩家已经很清楚每个人都拥有什么了。因此,基于这些想法,相当强大的Skat和Bridge程序已经出现了。
如果您能够完全解决底层问题,那是最好的,但如果您不能,则可以使用极小化或UCT在每个世界中选择最佳移动。还有混合算法(ISMCTS)尝试将此过程混合在一起。请注意这里的声明。简单的抽样方法更容易编码 - 您应该尝试更简单的方法,然后再尝试更复杂的方法。
以下是关于不完美信息采样方法成功应用的一些研究论文:
- 理解完美信息蒙特卡罗采样在博弈树搜索中的成功(该论文分析了采样方法何时可能奏效。) - 改进基于技巧的纸牌游戏中状态评估、推断和搜索(该论文描述了在Skat中使用采样的方法。) - 计算复杂的桥牌游戏中的不完美信息(该论文描述了在Bridge中采样的方法。) - 信息集蒙特卡罗树搜索(该论文将采样和UCT/Monte Carlo Tree Search合并,以避免第一篇参考文献中存在的问题。)
传统的基于规则的方法的问题在于,它们无法利用超出创建初始规则所需的计算资源。此外,基于规则的方法将受到您可以编写的规则强度的限制。搜索导向的方法可以利用组合搜索的能力,产生比程序作者更强大的游戏策略。

6
你实现的极小化搜索(Minimax search)在存在如此多不确定性的游戏中是错误的方法。因为你不知道其他玩家手中的牌分布,所以你的搜索将花费指数级的时间来探索在实际牌分布下不能发生的游戏。
我认为更好的方法是针对当你几乎没有或没有其他玩家手牌信息时制定出好的游戏规则,例如:
1. 如果你在回合中首先出牌,请出你最低的一张牌,因为你很少有机会赢得该回合。 2. 如果你在回合中最后出牌,请放出能赢得该回合的最低一张牌。如果你无法赢得该回合,则放出你最低的一张牌。
初始时,你的程序可以不必经过搜索,而只需按照这些规则进行游戏,并假设其他所有玩家也会使用这些启发式方法。随着程序观察到每个回合中的第一个和最后一个玩家打出的牌,它可以建立起所有玩家手牌的信息表。例如,9号牌本应赢得这一回合,但是3号玩家却没有出这张牌,因此他不能拥有9或更高级别的牌。当收集到每个玩家手牌信息时,搜索空间最终将被限制到一个极小化搜索可能会产生关于下一步出牌的有用信息的程度。

1
关于在游戏结束时的极小化问题。此时,您知道您需要x个技巧才能获胜。任何您无法(不应该)获胜的世界都可以忽略。因为如果那个世界是正确的,那么你已经输了。如果您基于导致获胜的世界来计算概率(本质上使用一种愿望式思维),那么您可能会进一步剪枝搜索。 - Cruncher

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接