Cribbage: 计算手牌价值
在过去的几个月中,我们一直在构建一个复杂的 Shell 脚本来玩Cribbage游戏的各个元素,在进行过程中演示各种概念和技术。这一切都很好,上个月,该脚本扩展到包括“洗牌”功能和发六张牌的能力,这是一个典型的两人游戏起始手牌。
Cribbage 提示:在三人游戏中,每位玩家获得五张牌,多余的牌直接发到“crib”中——额外的牌在庄家玩完后计算。
我们本月要解决的挑战是根据不同手牌的潜在点数,找出最初六张牌中最好保留的四张牌。最终,会翻开第五张“切牌”,每个人共享(有点类似于德州扑克中的公共牌),但聪明的 Cribbage 玩家会寻求最大化他们持有的四张牌,并在第五张牌增加手牌点数时庆祝,而不是指望切牌来组成手牌。
那么,Cribbage 手牌中什么有价值呢?三张或更多张牌的顺子,相同牌阶的对子或更好,手牌中的所有牌花色相同(并且可能切牌也相同),牌阶组合加起来为 15,以及拥有与切牌花色相同的杰克牌。
相同牌阶的牌——对子或更好——很难计数,因为游戏是基于组合的。因此,5H、5D 价值两分“一对”,但 5H、5D、5S 价值六分,因为它组合成三对:5H+5D、5H+5S 和 5D+5S。四张同阶牌?令人愉悦的 12 分,而且是非常罕见的组合。
关键要知道:在计算 15 点时,A 始终为 1 点,所有 J、Q、K 面牌都为 10 点。因此,A+4+K 是 15 点,就像 9+6 一样,但对于对子来说,它是相同的牌阶,而不仅仅是面牌对子。换句话说,J+K 不是一对。
计数变得棘手的地方是 15 点的组合。例如,一手好牌是 6S、7H、8D、8S,它可以提供 7H+8D 的两分,7H+8S 的两分,6S+7H+8D 的三分,6S+7H+8S 的另三分,以及 8D+8S 对子的最后两分——非常好。如果切牌是另一张 7,或者更好的是另一张 8,你可以看到这手潜在的手牌有很多分!
当然,挑战在于以编程方式完成所有这些。当我们上个月结束程序时,它可以向我们发出六张牌并按升序排列,这很有帮助,尤其是在识别顺子时。
计算所有四张牌的组合我们遇到的第一个问题实际上比与牌值相关的任何事情都早。它是计算六张牌手中所有可能的四张牌组合。这是组合数学——当我大学时为了获得计算机科学学位而上那门课时,我真的很喜欢它。
我们正在研究的实际上是阶乘数学。因此,“六选四”实际上是 6! 减去 2!,通常如图 1 所示,其中 n 是牌的总数,m 是我们要选择的子集。在本例中,它是 6! / 4!(6! - 4!)。

图 1. “六选四”的公式
阶乘数计算为所有递减数字的乘积,因此 4! = 4 x 3 x 2 x 1,而 6! = 6 x 5 x 4 x 3 x 2 x 1。当然,“x 1”部分是毫无意义的,所以我们可以跳过它。4! = 24,6! = 720,这意味着我们有 15 种牌组合需要检查点数。(如果因为是三人或四人 Cribbage 游戏而只发了五张牌,那么我们只需要评估五种组合。)
问题是,我们如何轻松地找出所有这 15 种组合?我的意思是,我可以硬编码所有 15 种组合——毕竟每个子集只有四个数字——但这似乎不如尝试解决更通用的数学方程式有趣。是的,我可以看到自己在继续的过程中跑题了,但当你不赶时间时,这就是编程乐趣的一部分,对吧?
嗯,没关系。让我们假设对于六选四,有这些特定组合,仅此而已:{1,2,3,4}、{1,2,3,5}、{1,2,3,6}、{1,2,4,5}、{1,2,4,6}、{1,2,5,6}、{1,3,4,5}、{1,3,4,6}、{1,3,5,6}、{1,4,5,6}、{2,3,4,5}、{2,3,4,6}、{2,3,5,6}、{2,4,5,6} 和 {3,4,5,6}。
如果我们使用五选四,则组合为 {a,b,c,d}、{a,b,c,e}、{a,b,d,e}、{a,c,d,e} 和 {b,c,d,e}。
对这种情况进行编码的简单方法是使用数组的数组。我们这样做
# six choose four
sixfour[0]="0 1 2 3"; sixfour[1]="0 1 2 4"
sixfour[2]="0 1 2 5"; sixfour[3]="0 1 3 4"
sixfour[4]="0 1 3 5"; sixfour[5]="0 1 4 5"
sixfour[6]="0 2 3 4"; sixfour[7]="0 2 3 5"
sixfour[8]="0 2 4 5"; sixfour[9]="0 3 4 5"
sixfour[10]="1 2 3 4"; sixfour[11]="1 2 3 5"
sixfour[12]="1 2 4 5"; sixfour[13]="1 3 4 5"
sixfour[14]="2 3 4 5"
如果您密切关注我们一直在创建的脚本,您就会知道我们的手牌索引从 0 开始,因此牌值也是 0-5。这很容易修复,只需将预定义数组中的每个值都向下移动一位即可。
现在只需将不同的 sixfour
数组值解释为要选择的四张牌的集合,这就是我的做法
for subhand in {0..14}
do
/bin/echo -n "Subhand ${subhand}:"
for thecard in ${sixfour[$subhand]}
do
showcard ${hand[$thecard]}
/bin/echo -n " $showcardvalue"
done
echo ""
done
echo 语句(请记住 -n
可以防止在输出中附加回车符)对于最终代码并不重要,但对于这个临时解决方案,您将看到它是如何工作的,我只是将其塞到程序的末尾,就在处理、排序和显示完整六张牌手牌的代码之后
Hand: AS, 5C, 7H, 8H, 9D, JS.
Subhand 0: AS 5C 7H 8H
Subhand 1: AS 5C 7H 9D
Subhand 2: AS 5C 7H JS
Subhand 3: AS 5C 8H 9D
Subhand 4: AS 5C 8H JS
Subhand 5: AS 5C 9D JS
Subhand 6: AS 7H 8H 9D
Subhand 7: AS 7H 8H JS
Subhand 8: AS 7H 9D JS
Subhand 9: AS 8H 9D JS
Subhand 10: 5C 7H 8H 9D
Subhand 11: 5C 7H 8H JS
Subhand 12: 5C 7H 9D JS
Subhand 13: 5C 8H 9D JS
Subhand 14: 7H 8H 9D JS
这是很多四张牌的手牌,但至少我们得到了它们。下个月,我们将真正开始编写代码来评估手牌价值。敬请期待!