上周接了一个小单,根据一个用户的需求用C++做了个小游戏------Goofspiel。游戏逻辑很简单,两个用户A,B和一个奖励方C,用户方每一轮出一张牌比点数,大的就赢得奖励牌。一共13轮,结束后,谁手中奖励牌之和大,谁就胜利。注意,用户牌和奖励牌的大小都是1到13。 有想体验的可以去试试别人写的网页版:coppercod.games/game/gops/
图1. Goofspiel game 网页版
一个不存在最优解的纯策略游戏
首先,这是一个不存在最优解的纯策略游戏。因为它是双方博弈性质的,不同对手有不同的出牌偏好,这也决定了你的出牌倾向。换言之,不同对手出牌的概率分布是变化的。
常见的游戏策略
这类游戏的策略非常多,我们列举几个如下:
- 随机策略:随机从手中取出一张牌出
- 确定性策略:假设奖励牌为prize_card,那么我们出一张确定的牌;即下次奖励牌为prize_card,我们扔出那张确定的牌
- 加x策略:奖励牌为prize_card,那么我们出的牌就是(prize_card+x)%13
- 拿中值牌策略:采用+3的方式,侧重于4,5,6,7,8,9,10这7张中值牌。对于非目标牌,可以随机性出牌
策略的评估
不同策略有不同的应对方法,下面列举一些策略的应对方法:
- 对于随机策略,即出牌服从[1,13]的均匀分布。我们只需要出与奖励牌一样的牌,就可以获得最后的点数期望最大。
- 对于确定性策略,如果你知道对手采用+x策略,我们只需要采用+(x+1)策略。
我们的策略
考虑到高价值奖励牌往往并不容易获取,我们对拿中值牌策略进行了改进。它可以自适应的预测对手出牌大小通过考虑轮次、奖励牌情况、对手历 史出牌和我们的手牌。这个策略针对下面三类牌会做出不同的反应:
- 较低价值牌: 对于1到3的奖励牌x,如果是1,我们出手上最小的纸牌;否则我们倾向于选择一个[x, x+2]的随机卡牌。
- 中等价值牌 (Prize cards 4-10): 对于4到10的奖励牌x,对局一开始,我们会选择+3的策略。然后,我们会动态学习对手拿牌的gap (gap= 出牌-奖励牌),从而针对性的拿牌。
- 高价值牌 (Prize cards 11-13): 对于11到13的奖励牌x,我们根据对局状况来选择小牌、随机牌或者大牌。具体来讲,如果我们手上大牌比对手大牌大,则我们拿下该牌;如果我们手上大牌和对手大牌一样大,我们随机选择中等价值牌或者低价值牌;如果我们手上大牌没有对手大牌大,我们将随机选择小牌或者第三个四分位牌。 同时,为了限制对手学习能力,一旦我们得分超过45分,我们将随机选择手牌。
设计相关算法
- 随机出牌检测算法:我们的算法会分析对手历史出牌,从而判断对手是否是随机出牌。具体来讲,如果对手是随机出牌,当轮数足够大时,对于任意奖励牌x,对手牌将遵循区间在1到13的均匀分布。由于大数定律可知,样本均值收敛于数学期望7。我们从而可以判断对手是否是随机算法。
- 确定性出牌检测算法:对于一类确定性+x的算法,我们提出了针对性的检测算法。通过检测(对手出牌-奖励牌)%13是否为常数,我们可以确定对手是否是确定性出牌。
- 自适应出牌算法:该算法会帮助我们的策略拿下更多的中等价值牌。具体来看,一开始我们会以+3的方式冷启动,如果对手与我们竞争中等价值牌,我们会记录对手出牌与奖励牌的gap(gap=对手出牌-奖励牌)。下次面对中等价值牌时,我们会通过 gap+1方式出更有竞争力的牌。
- 动态丢牌算法:该算法会帮助我们的策略合理的丢弃高价值牌。具体来看,对于一张高价值牌(11,12 或 13),如果我们手中的大牌没有对手大,我们会随机选择丢弃小牌或者中等价值牌(防止对手不出大牌)。如果我们和对手手中的大牌一样大,对于比赛初,我们会随机丢弃中等价值牌或最小牌;对于比赛后期,我们会出最大牌(后期弃牌会非常被动)。
运行截图
下面,我们对上述算法的实现结果进行展示:
图2.游戏主界面
图3. 自适应出牌
图4. 确定性拿牌与动态丢牌
图5. 游戏结果展示
代码
具体代码可去我的github查看,github.com/guchengzhon...