Work the Shell - 识别 Blackjack

作者:Dave Taylor

上个月,我们最终解决了 A 牌可以算作 1 点或 11 点的问题,通过认识到我称之为“懒惰编码”的价值——尝试解决具体情况,而不是创建优雅而复杂的通用解决方案。此后,我与一些程序员交谈,发现许多人不同意我的理念。事实上,一位伙计基本上说我完全错了,特别是如果代码要发布(正如本专栏显然要做的),并且我应该始终尽最大努力为遇到的任何问题提供堪称典范的优雅解决方案。

但是,我反驳说,如果这样做会使程序长度增加两倍呢?或者四倍?他茫然地看着我,问道:“什么时候程序的长度在您尝试解决问题或模拟特定行为时变得重要了?” 这是一个公平的问题,但我不得不认为这就是我们所有代码膨胀的根源——程序都无法做到简单、高效和精简,并且所有程序都做其他所有事情,包括可以发送电子邮件的文字处理器、可以进行基本页面布局的电子邮件程序、可以将 ASCII 转换为 HTML 的文本编辑器、以及似乎包含几乎所有功能(包括 KitchenSink.app)的网络浏览器。

而且,不要让我开始谈论 Emacs 与 vi 的哲学,好吗? 我们就说,一个可以替换你的操作系统内核的编辑器对于绝大多数用户来说可能有点过度实现了。

然而,所有这些复杂性的真正代价不是代码的长度。 毕竟,现在几千兆字节的磁盘空间非常便宜,甚至 RAM 似乎也不比一顿像样的饭菜贵多少。 代价在于复杂性本身——在于大型、复杂的系统比简单系统更难使用、更难调试并且更难做到万无一失。 您只需看看微软在 2010 年春季之前努力发布其新 Windows 操作系统 Vista 时面临的挑战就知道了。

那么,这一切与 shell 脚本编程有什么关系呢? 我认为,实际上,这个讨论对于所有编程任务都至关重要,并且如果您无法在 shell 脚本的世界中找到一个简单且可管理的解决方案,那么可能是时候转向更复杂的开发环境了。 在本专栏的早期,我开玩笑说最终要用 shell 脚本重写流行的游戏 Doom,但当然,即使我们可能能够将方枘凿入 shell 脚本的圆孔,也会被复杂性所扼杀。

回到拉斯维加斯

让我们回到我们的 Blackjack 游戏,看看我们在复杂性、长度和功能方面的情况。 我怀疑我们将在下一专栏结束前完成这个项目,因为这实际上是我们可以作为脚本创建的所有机制; 游戏本身注定永远是一个原始的命令行界面,因为,嗯,它是一个 shell 脚本。 我们可以使用 Tk 或其他图形工具包来分层一个界面吗? 当然,但是,您真的希望底层的代码库是一个 shell 脚本吗?

我们的代码现在正在进入非常长的 shell 脚本的类别,重达 177 行。 这实际上超出了我通常对脚本的最大行数 150 行的限制,所以我们肯定需要在它变得太长之前完成它。

我们将在本专栏中处理的缺失部分是测试庄家 Blackjack 和玩家 Blackjack 的情况。 如果您拿到一张 A 牌和一张十分牌(10 点牌或花牌),您就有一个 Blackjack。 如果庄家有 Blackjack,则游戏结束,玩家输。 如果玩家在发牌时拿到 Blackjack,则游戏结束,庄家输(无法再拿牌,因为涉及两张以上牌的 21 点牌不能平玩家的 Blackjack)。 如果您遇到玩家和庄家都有 Blackjack 的特殊情况,则为“平局”,就像玩家和庄家手中的点数相同的其他任何情况一样。

我们需要修改代码以测试 Blackjack 的关键位置是在发牌的地方,我们不妨在添加额外的测试之前先发两手牌。 这是发牌代码

player[1]=${newdeck[1]}
player[2]=${newdeck[3]}
nextplayercard=3        # player starts with two cards

dealer[1]=${newdeck[2]}
dealer[2]=${newdeck[4]}
nextdealercard=3        # dealer also has two cards

为了快速查看任何一手牌是否是 Blackjack,我们将使用 handValue 函数

handValue ${newdeck[1]} ${newdeck[3]}

回想一下,由于您无法在函数中返回值,handValue 只是将全局变量 $handvalue 设置为手牌的数值。 这意味着测试很简单

# test for dealer or player blackjack

handValue ${player[1]} ${player[2]}
playerhandvalue=$handvalue
handValue ${dealer[1]} ${dealer[2]}
dealerhandvalue=$handvalue

echo ""

if [ $playerhandvalue -eq 21 -a $dealerhandvalue -eq 21 ] ; then
  echo "Extraordinary! Both the dealer and player were dealt a blackjack!"
  echo "Unfortunately, this means you didn't win: it's a push (or tie)."
  echo ""
  exit 0
fi

if [ $dealerhandvalue -eq 21 ] ; then
  echo "Darn it! Dealer pulled a blackjack out of his hat. You lose."
  echo ""
  exit 0
elif [ $playerhandvalue -eq 21 ] ; then
  echo "NICE! You got a blackjack and won the game. Payout would be 3:2!"
  echo ""
  exit 0
fi

您可以看到我只是为这些数值测试创建了 playerhandvalue 和 dealerhandvalue,然后检查是否都为 21,庄家为 21,玩家为 21。 这就是全部。

要测试新代码,只需在上面的 handValue 调用之前插入以下任一行或两行

player[1]=1 ; player[2]=11
dealer[1]=1 ; dealer[2]=12

然后,您可以,嗯,作弊并创建您想要的特定测试情况。

几乎完成

本专栏的空间已经用完了,但剩下的就是实现整体游戏逻辑。 现在的游戏会洗牌,为玩家和庄家发牌,让玩家添加新牌(在 Blackjack 术语中称为“要牌”)直到他或她满意或超过 21 点,并显示庄家的牌。 在下一专栏中,我们将编写循环,让庄家打完他的牌并确定谁赢了这手牌。 毫无疑问,我们也会偷偷添加一些界面功能,当然,但无论如何,下一专栏将是这个特定脚本项目的收尾。

这意味着我已经准备好迎接新的挑战了! 如果您有您认为可能是一个有趣的研究的特定脚本项目,请给我发电子邮件,我会考虑将其用于未来的专栏系列。 否则,我一直在密切关注游戏 Yahtzee

Dave Taylor 是 UNIX 领域的 26 年资深人士,Elm 邮件系统的创建者,以及最近畅销书 Wicked Cool Shell ScriptsTeach Yourself Unix in 24 Hours 的作者,以及他的 16 本技术书籍。 他的主要网站是 www.intuitive.com

加载 Disqus 评论