我正在开发一个解决方案,用于在完美的信息情境中使用名为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
)或使用α或β的绑定值?我看到它的方式,从转置表中存储和检索值都不会考虑这些位置的特定历史。或者会吗?
文献:
在skat游戏中几乎没有关于人工智能的英语资源,但我找到了这个:A Skat Player Based on Monte Carlo Simulation by Kupferschmid, Helmert。不幸的是,整篇论文,尤其是转置表的详细说明,都是 compact 。
所以每个人都可以更好地想象得分如何在Skat游戏中发展直到所有牌都被播放,这里是example。游戏过程显示在下表中,每行一个技巧。每个技巧后的实际得分位于左侧,其中 + X 是申报者的得分( -Y 是防御团队的得分,与alpha beta无关)。正如我所说,一个技巧(宣告者或辩护团队)的获胜者将这个技巧中的每张牌的价值加到他们的分数上。
卡片值为:
Rank J A 10 K Q 9 8 7
Value 2 11 10 4 3 0 0 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_VALID
,TT_LBOUND
和TT_UBOUND
是简单常量,描述转置表条目的类型然而,这本身并不起作用。在gamedev.net上发布相同问题后,名为Álvaro的用户向我提供了决定提示:
存储精确分数(TT_VALID
)时,我应该只存储改进alpha 的位置。