Cribbage: 整理手牌
我们一直在编写游戏 Cribbage 的代码,上次,我创建了所需的代码,从“牌堆”中挑选六张随机子集,并以吸引人的格式显示它们——就像这样
$ sh cribbage.sh
Card 0: 7C
Card 1: 5H
Card 2: 9H
Card 3: 10S
Card 4: 5D
Card 5: AS
本文议程上的主要任务是在发牌后整理牌。这意味着我们将需要按等级对牌进行排序,同时忽略花色,然后将它们放回“手牌”数组中。有没有简单的方法可以做到这一点?实际上,我们将使用 sort
函数。
我们可以通过使用命令行来原型化这一点,看看我们得到什么结果
$ sh cribbage.sh | sort -n
Card 0: 4S
Card 1: 7C
Card 2: 9S
Card 3: JC
Card 4: 7H
Card 5: 8C
怎么回事?哦!您能看到问题,对吧?通过告诉 sort
按数字顺序排列事物,它可以正确地忽略“Card”,但随后会看到牌的序数值,并基于此进行排序,而不是基于实际的牌值本身。
即使我们解决了这个问题,我们仍然存在面牌会在数值牌之前排序的问题,这不是我们想要的。实际上,我们希望 A 在排序时低于 2,而 J、Q 和 K 在排序时高于 10。
如果您想让 A “高”,当然,最简单的方法是更改显示例程:1 = 2,2 = 3,12 = K,13 = A。噗。一切都按 A 高排序。但这并不是 Cribbage 的计分方式。
为了完成 Cribbage 等级排序,我们需要更改输出以输出两个值:等级和总牌值。它看起来会很丑陋,但这只是一个临时的结果。
这是我调整代码以显示这些值的方式
showcard()
{
# given a card value of 0..51 show the suit and rank
suit=$(( $1 / 13 ))
rank=$(( ( $1 % 13 ) + 1 ))
case $rank in
1) orank="A" ;;
11) orank="J" ;;
12) orank="Q" ;;
13) orank="K" ;;
*) orank=$rank ;;
esac
showcardvalue=$orank${suits[$suit]}
}
如果您将其与我们上个月构建的版本进行比较,主要区别在于,我们使用新的变量 orank
来存储更正后的值,而不是计算牌的等级,然后用“A”、“J”、“Q”或“K”覆盖它。为什么?因为现在在脚本的主体部分,我们也可以根据需要访问牌的 $rank
showcard ${hand[$card]}
echo "$rank ${hand[$card]}"
对于选择的每张牌,脚本都有等级后跟牌的数值的临时输出,没有花哨的显示(即使我们仍然为了简单起见而使用 showcard 函数)。结果
$ sh cribbage.sh
13 38
6 31
8 33
10 35
5 30
12 24
丑陋?绝对的。但是现在我们可以对其进行排序并获得有用的结果,即使它们看起来还不太像
$ sh cribbage.sh | sort -n
1 26
2 14
2 40
3 2
7 45
10 22
它看起来仍然令人困惑,但您可以看到它是按等级顺序排列的。
那么,既然我们知道如何排序,我们如何将其放回“手牌”数组中呢?由于变量作用域问题,这实际上相当棘手,您将会看到。
然而,在我们去那里之前,我编写了一个新的“showhand”函数,它借助 /bin/echo 在单行上显示手牌中的所有牌,以便在没有尾随换行符的情况下进行回显
showhand()
{
# show our hand neatly
/bin/echo -n "Hand: "
for card in {0..4}
do
showcard ${hand[$card]}
/bin/echo -n "$showcardvalue, "
done
showcard ${hand[5]}
echo "$showcardvalue."
}
有了这个,我们的主代码开始看起来漂亮而简洁
dealhand;
showhand; # for testing sorthand only
sorthand;
showhand;
为了调试目的,我将在按等级排序之前和之后显示手牌。当然,最终,第一个“showhand”将被删除。
现在,让我们回到整理我们手牌中的牌所需的代码(据我所知,许多 iOS Cribbage 游戏似乎都忽略了这一功能)。
我对编写“sorthand”的第一次尝试利用了 Bourne shell 中一个非常巧妙的功能,该功能允许您使用管道将一个循环的输出连接到另一个循环的输入。例如
for card in {0..5}
do
showcard ${hand[$card]}
echo "$rank ${hand[$card]}"
done | sort -n | while read rank value
do
hand[$index]=$value
index=$(( $index + 1 ))
done
问题是 shell 的管道实现将第二个循环推送到子 shell 中,而没有任何简单的方法可以将更改后的值返回到父 shell。结果:在最后一个 done
语句之后的行之后,所有新值都丢失了。
太糟糕了,因为它绝对更优雅。但话又说回来,这不是关于优雅,而是关于功能,对吧?
这是我实际解决它的方法,通过使用临时文件来存储中间结果。当然,这远没有那么优雅
sorthand()
{
# hand is dealt, now sort it by card rank...
index=0
tempfile="/tmp/.deleteme"
for card in {0..5}
do
showcard ${hand[$card]}
echo "$rank ${hand[$card]}"
done | sort -n > $tempfile
while read rank value
do
hand[$index]=$value
index=$(( $index + 1 ))
done < $tempfile
rm -f $tempfile
}
请注意,要将临时文件的输入作为 while
循环的输入,我只需在循环的末尾重定向循环的 stdin:done < $tempfile
。
让我们通过发几手牌来测试它,然后在发牌后立即显示它们,然后在用 sorthand 函数重新排列它们之后显示它们
$ sh cribbage.sh
Hand: 9H, 6D, KC, AH, 9S, JH.
Hand: AH, 6D, 9S, 9H, JH, KC.
$ sh cribbage.sh
Hand: 4D, QS, AC, 9H, 10C, JS.
Hand: AC, 4D, 9H, 10C, JS, QS.
$ sh cribbage.sh
Hand: 9H, 10C, 7C, 7H, 5H, AS.
Hand: AS, 5H, 7C, 7H, 9H, 10C.
看起来它完全按照我们希望的方式工作。耶-哈!
是的,毫无疑问,有更有效的方法来编写这段代码,您可以完全合理地询问 shell 脚本是否是此类项目的最佳开发环境,但是,说真的,放松点。让我们享受这个项目,而不是因为标点符号而折磨自己!
就此而言,让我们结束本月的专栏,并开始思考下个月开始我们将面临的更艰巨的挑战:如何评估手牌的价值,以便我们可以推荐应该保留六张发牌中的哪四张牌以优化 Cribbage 手牌。
您正在边走边学 Cribbage,对吧?您肯定会在下一期中需要它。