Shell 脚本实践 - 调用所有函数,以及一些数学运算!
如果您一直在关注我的专栏,您就会知道我们正在构建一个 二十一点 游戏作为 shell 脚本。为什么?因为大多数 shell 脚本都太枯燥乏味了,不打瞌睡都看不下去,所以在游戏的背景下考虑 shell 脚本编程的所有细微之处听起来更有趣!
我的上一篇专栏文章讨论了如何洗一副牌,在一个简单的 52 个值的数组(1-52)中描述。洗牌问题有一些有趣的细微之处。让我们从那里开始。然后我们将看看如何将任意的 1-52 值转换为一副牌中熟悉的点数和花色。
如果您从 UNIX 诞生之初就开始编写 shell 脚本,您可能没有意识到现代 shell 现在支持函数和过程,就像“真正”的编程语言一样。对于您计划多次执行的代码块,这是不二之选。
这是作为 shell 函数编写的洗牌代码
function shuffleDeck { count=1 while [ $count -le 52 ] do pickCard newdeck[$count]=$picked count=$(( $count + 1 )) done }
这会构建一个名为 newdeck 的数组,它实际上是洗过的牌(在上一篇专栏文章中,我们展示了 deck,它是一个按线性顺序排列的牌的数组),并且它使用了一些基本的 shell 数学运算和 $(( )) 表示法,以避免生成子 shell 来为 count 变量的每次递增调用 expr。
我说过 shell 脚本是健壮的编程环境,这可能有点夸张,真的。眼尖的读者会注意到 pickCard 函数通过设置一个全局变量 picked 返回其值,这并不是真正的最佳编程策略。但它确实有效,实用主义是任何好的软件开发方法的重要组成部分,不是吗?
完整的 pickCard 函数是使其工作的关键,但它太长了,无法在此处包含,因此请从 LJ FTP 站点获取它以供阅读(ftp://ftp.linuxjournal.com/pub/lj/listings/8774.tgz)。
有了 shuffleDeck 函数和 initializeDeck 函数,如此处所示
function initializeDeck { card=1 while [ $card -le 52 ] do deck[$card]=$card card=$(( $card + 1 )) done }
很容易完成洗牌的基本操作,并为玩家和庄家各发两张牌
initializeDeck shuffleDeck echo "** Player's hand: ${newdeck[1]}, ${newdeck[3]}" echo "** Dealer's hand: ${newdeck[2]}, ${newdeck[4]}"
让我们运行它,看看我们得到什么样的结果
$ ./blackjack.sh ** Player's hand: 22, 49 ** Dealer's hand: 11, 8 $ ./blackjack.sh ** Player's hand: 19, 32 ** Dealer's hand: 49, 10 $ ./blackjack.sh ** Player's hand: 44, 23 ** Dealer's hand: 46, 11
将牌显示为 1-52 的数值并不是最友好的,因此让我们将注意力转向以传统扑克牌熟悉的点数和花色来显示牌值。
一副牌由 52 张牌组成,均匀分成四种花色,每种花色 13 张牌。花色的顺序并不重要(至少在 二十一点 中是这样),但点数很重要。事实上,游戏的目标是获得总点数为 21 点,且不超过此点数。
牌的点数是数值牌值除以 13 的余数。用数学术语来说,这称为模数,可以这样计算
点数 = 牌值 % 13
为了将其放入适当的 shell 表示法中,我们将再次使用 $(( )) 快捷方式,最终得到
rank=$(( $card % 13 ))
获取花色也应该很简单;它是除法的另一半。换句话说,如果牌值是 17,那么 17/13 = 1,这意味着它是花色 #1,而 17%13 = 4。由于我们希望花色的范围在 1-4 之间,而不是 0-3 之间,因此我们需要在等式中加一。此外,每种花色的第 13 张牌与之前的 12 张牌的花色相同,因此我们还必须在进行除法之前减一(如果我们只计算 13/13,则第 13 张牌将是花色 #1,但如果我们为它计算 12/13,我们将正确地将其识别为花色 #0 的一部分)。
这非常令人困惑,所以这里是等式
suite="$(( ( ( $card - 1) / 13 ) + 1))"
更清楚了,对吧?说真的,您可以实验性地验证它是否正确工作。重要的边缘情况是 value=1、value=12、value=13 和 value=14。如果您能将这些情况弄对,那么您就可以处理牌组中的所有值了。
一旦我们确定了牌的点数和花色,我们只需要做一些花哨的步法将数字变成文字
case $suite in 1 ) suite="Hearts" ;; 2 ) suite="Clubs" ;; 3 ) suite="Spades" ;; 4 ) suite="Diamonds" ;; * ) echo "Bad suite value: $suite"; exit 1 esac
和
case $rank in 0 ) rank="King" ;; 1 ) rank="Ace" ;; 11) rank="Jack" ;; 12) rank="Queen" ;; esac
将它们放在一个名为 showCard 的函数中(它返回“$cardname”作为计算的点数和花色),我们现在可以稍微清理一下
initializeDeck shuffleDeck echo -n "** Player's hand: " showCard ${newdeck[1]} ; echo -n "$cardname, " showCard ${newdeck[3]} ; echo "$cardname" echo -n "** Dealer's hand: " showCard ${newdeck[2]} ; echo -n "$cardname, " showCard ${newdeck[4]} ; echo "$cardname"
现在我们可以开始看到游戏组合在一起了!考虑一下
$ ./blackjack.sh ** Player's hand: 8 of Clubs, 3 of Diamonds ** Dealer's hand: King of Spades, 3 of Spades $ ./blackjack.sh ** Player's hand: 2 of Spades, 4 of Spades ** Dealer's hand: 10 of Spades, 4 of Hearts
本月就到此为止吧,因为已经有很多代码需要深入研究了。我邀请您访问 LJ FTP 站点获取到目前为止的所有源代码,这样您也可以自己尝试这个脚本。
下个月,我们将开始研究游戏逻辑本身,但现在,拉斯维加斯正在召唤一个大型贸易展,嗯,我可以将其作为 Linux Journal 的研究注销,不是吗?
Dave Taylor 是一位拥有 26 年 UNIX 经验的资深人士,The Elm Mail System 的创建者,也是畅销书 超酷 Shell 脚本 和 24 小时自学 Unix 等 16 本技术书籍的作者。他的主要网站是 www.intuitive.com。