Cribbage: 整理手牌

作者:Dave Taylor

我们一直在编写游戏 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,对吧?您肯定会在下一期中需要它。

Dave Taylor 在 UNIX 和 Linux 系统上破解 shell 脚本已经很长时间了。他是 Learning Unix for Mac OS XWicked Cool Shell Scripts 的作者。您可以在 Twitter 上找到他,账号为 @DaveTaylor,您可以通过他的技术问答网站联系他:Ask Dave Taylor

加载 Disqus 评论