我目前正在开发一个解决方案,一个基于技巧的纸牌游戏称为Skat在一个完美的信息情况。虽然大多数人可能不知道这个游戏,但请容忍我,我的问题是一般性的。
SKAT简介:
基本上,每个玩家轮流玩一张牌,每三张牌就形成一个戏法。每张卡都有特定的价值。一个玩家所获得的分数是将其所赢得的每一张牌的价值相加的结果。我漏掉了一些对我的问题不重要的事情,例如谁对谁,我什么时候赢一个把戏。
我们应该记住的是,有一个连续的分数,谁在调查某个位置时打了什么(->它的历史)与该分数有关。
我用Java编写了一个alpha-beta算法,它看起来工作得很好,但是速度太慢了最有希望的第一个改进是使用换位表。我读到在搜索skat游戏树的时候,你会遇到很多已经被调查过的位置。
这就是我的问题所在:如果我找到一个之前已经调查过的位置,那么导致这个位置的移动是不同的。因此,一般来说,分数(和α或β)也会有所不同。
这就引出了我的问题:如果我知道同一个职位的价值,但又有不同的历史,我如何确定这个职位的价值?
换言之:如何将子树从其路径分离到根,以便将其应用到新路径?
我的第一个冲动是这是不可能的,因为alpha或beta可能受到其他路径的影响,这些路径可能不适用于当前位置,但是……
似乎已经有了解决办法
…我好像不明白在Sebastion Kupferschmid关于Skat解算器的硕士论文中,我发现了这段代码(可能是C-ish/伪代码?)以下内容:

def ab_tt(p, alpha, beta):
    if p isa Leaf:
        return 0

    if hash.lookup(p, val, flag):
        if flag == VALID:
            return val
        elif flag == LBOUND:
            alpha = max(alpha, val)
        elif flag == UBOUND:
            beta = min(beta, val)
        if alpha >= beta:
            return val

    if p isa MAX_Node:
        res = alpha
    else:
        res = beta

    for q in succ(p):
        if p isa MAX_Node:
            succVal = t(q) + ab_tt(q, res - t(q), beta - t(q))
            res = max(res, succVal)
            if res >= beta:
                hash.add(p, res, LBOUND)
                return res
        elif p isa MIN_Node:
            succVal = t(q) + ab_tt(q, alpha - t(q), res - t(q))
            res = min(res, succVal)
            if res <= alpha:
                hash.add(p, res, UBOUND)
                return res
    hash.add(p, res, VALID)
    return res

这应该是非常不言而喻的。succ(p)是返回当前位置的所有可能移动的函数t(q)是我认为的各自位置的连续得分(到目前为止由宣布者获得的分数)。
既然我不喜欢在不理解的情况下抄袭,这应该是对任何想帮助我的人的帮助。当然,我已经对这段代码做了一些思考,但我不能只考虑一件事:在再次调用函数之前,先从alpha/beta中减去当前的分数[例如ab_tt(q, res - t(q), beta - t(q))],这似乎是某种脱钩但是如果我们把位置的值存储在换位表中,而不在这里做同样的减法,到底有什么好处呢?如果我们发现了一个先前调查过的位置,为什么我们可以返回它的值(如果它是VALID)或者使用alpha或beta的绑定值?在我看来,从换位表中存储和检索值并不能解释这些位置的特定历史还是会呢?
文献:
在skat游戏中,几乎没有英文资料可以处理人工智能,但我找到了这个:A Skat Player Based on Monte Carlo Simulation by Kupferschmid, Helmert遗憾的是,整篇论文,特别是换位表的阐述是相当紧凑的。
编辑:
为了让每个人都能更好地想象,在玩完所有的牌之前,通过Skat游戏,分数是如何发展的,这里有一个example游戏过程显示在下表中,每行一个技巧每一个技巧后的实际得分在它的左边,其中+x是说唱者的得分(-y是防守队的得分,这与α-β无关)。如我所说,一个技巧的赢家(庄家或防守队)会将这个技巧中每张牌的价值加到他们的得分上。
卡值为:
Rank    J   A   10  K   Q   9   8   7
Value   2   11  10  4   3   0   0   0

最佳答案

我解决了这个问题。根据我问题中引用的建议,每次递归调用都要做奇怪的减法运算,只有在换位表中存储一个位置时,我才会从得到的alpha beta值中减去运行分数:
对于精确的值(位置尚未修剪):

transpo.put(hash, new int[] { TT_VALID, bestVal - node.getScore()});

如果节点导致beta中断:
transpo.put(hash, new int[] { TT_LBOUND, bestVal - node.getScore()});

如果节点导致alpha截断:
transpo.put(hash, new int[] { TT_UBOUND, bestVal - node.getScore()});

哪里:
transpo是一个HashMap<Long, int[]>
hash是表示该位置的long
bestVal是精确值或导致截断的值
TT_VALIDTT_LBOUNDTT_UBOUND是简单的常量,用于描述换位表条目的类型
然而,这本身并不起作用在gamedev.net上发布了同样的问题之后,一位名叫阿尔瓦罗的用户给了我一个决定性的提示:
当存储精确的分数(TT_VALID)时,我应该只存储位置,这样可以提高alpha。

关于algorithm - 如何在换位表中说明头寸历史,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/21688552/

10-12 23:55