Work the Shell - Coping with Aces
不知何故,编写这个 二十一点 游戏开始感觉像是那个 三傻 小品节目的程序化等价物,其中 “他慢慢地转过身,一步一步...”,但在我们准备编写有趣的界面元素之前,我们仍然需要研究游戏的核心逻辑。
事实上,这个月我们很可能会发现我们必须拆开一些早期的脚本并重建它,以补偿 二十一点 游戏中一个令人困扰的方面:A 可以是高点或低点,也就是说它可以值 1 点或 11 点。发到两张 A,你就会有许多不同的可能值,这是一个问题。
事实证明,有一种巧妙的方法可以解决这个问题,只需最大化遇到的第一张 A 的值,只要手牌的总值不超过我们的 21 点上限即可。因此,两张 A 将自动价值 11 + 1(第一张最大化,但第二张没有,因为它会使我们超过 21 点)。
必须重写以补偿这种 A 估值策略的代码部分是 handValue 函数
function handValue { # feed this as many cards as are in the hand handvalue=0 # initialize for cardvalue do rankvalue=$(( $cardvalue % 13 )) case $rankvalue in 0|11|12 ) rankvalue=10 ;; 1 ) rankvalue=11 ;; esac handvalue=$(( $handvalue + $rankvalue )) done }
这是上个月的“之前”图片。请注意,case 语句中的第二行当前为遇到的每张 A 分配了 11 的等级值。显然这是一个错误!
但是,要更改它,我需要添加一个新变量来跟踪我是否已经在手中看到过之前的 A。我巧妙地称之为 seenAce
function handValue { # feed this as many cards as are in the hand handvalue=0 # initialize seenAce=0 for cardvalue do rankvalue=$(( $cardvalue % 13 )) case $rankvalue in 0|11|12 ) rankvalue=10 ;; 1 ) if [ $seenAce -eq 1 ] ; then rankvalue=1 else rankvalue=11 ; seenAce=1 fi ;; esac handvalue=$(( $handvalue + $rankvalue )) done }
看起来它会完成这项工作——或者会吗?
这里的问题最好用 9 + 10 + A 这样的手牌来说明。这是一个有效的 二十一点 手牌,应该价值 20 点。但是 handValue 会将其评为 30 点,并且程序会错误地将该手牌归类为爆牌。
一旦问题被认识到,解决这个问题并不太难,但这不正是编写任何代码的巨大挑战吗?正确地预测和描述错误和故障。解决方案通常非常简单,但是首先知道存在错误,啊,这就是伟大的程序员找到他们使命的地方!
这种情况下的解决方案是,如果手牌得分超过 21 点并且有 A,我们需要从手牌得分中扣除十分——事实证明,这个条件很容易添加到函数的末尾
handvalue=$(( $handvalue + $rankvalue )) done if [ $handvalue -gt 21 -a $seenAce -eq 1 ] ; then handvalue=$(( $handvalue - 10 )) fi }
这是我第一次在我们的脚本中使用复杂的条件语句,但是您已经熟悉这种类型的多表达式条件。如果我们使用类似 C 的语言,则条件可能如下所示
if ( ( handvalue > 21 ) && (seenAce == 1))
上面显示的 shell 脚本片段是等效的条件,其中 -a 用作逻辑 AND 语句。它在这种情况下不起作用,但 -o 也是 shell 测试条件中的逻辑 OR 语句,如果需要,您可以使用括号进行分组。
为了测试我们的新代码,我将临时用一些预加载的测试手牌替换程序的主体,看看返回什么样的手牌值
echo "Starting out with two aces..." handValue 1 14 echo "handvalue = $handvalue" echo "now testing 9 + 10 + A" handValue 9 10 1 echo "handvalue = $handvalue" echo "and, for good luck, testing K + A" handValue 12 1 echo "handvalue = $handvalue"
首先,我将使用原始的 handValue 函数运行它,预测错误
Starting out with two aces... handvalue = 22 now testing 9 + 10 + A handvalue = 30 and, for good luck, testing K + A handvalue = 21
是的。这不好。对于这种计数方式,我们的拉斯维加斯很快就会用完。
现在,我将插入前面解释的新的 seenAce 代码段,并尝试相同的测试手牌集
Starting out with two aces... handvalue = 12 now testing 9 + 10 + A handvalue = 20 and, for good luck, testing K + A handvalue = 21
您知道吗,看起来我们已经想出了一种明智的方法,可以在不大幅重写代码的情况下允许 A 具有两个可能的值。
好的。事实上,我坚信最好的程序员实际上是懒惰的,并且希望以最简单和最有效的方式解决问题。记住,懒惰孕育了独创性,所以虽然我可以重写二十一点脚本以使用可能的手牌值数组来模拟多值手牌,但何必呢?只要我们可以在代码中正确补偿这一事实,给定手牌具有多个值这一事实并不是真正重要的。
许多程序员谈论高效的代码是“优雅的”,但在我的经验中,最优雅的代码也是懒惰的代码。我知道我一直在寻找那些聪明的捷径,那些让我创造出性能可能效率较低,但编码更容易、调试更快、部署到现场更快的代码的见解。
程序员可以培养的一项重要技能是能够快速识别足够好的解决方案。本质上是高度分析性的,我们这些代码极客都患有一点完美主义,并且以额外的几天或几周的开发为代价编写完美的例程很容易最终变得不如拥有一个相当不错的例程更有用,这个例程可以完成工作,并且可以在以后的版本、维护补丁或任何其他版本中进行改进。
难道这种懒惰是导致我们的软件有这么多该死的错误的原因吗?我不这么认为。我认为产品中的错误是由于软件复杂性不断提高造成的,无论是 Linux 盒子的管理工具、Apache 模块还是 Ajax-y 基于 Web 的实用程序。像操作系统或内核这样的软件呢?当然会有错误。它太复杂了,无法测试所有可能的条件、情况和情景。事实上,寻求可以推向现场的有效解决方案可以帮助减少错误。不是测试软件就能发现最严重的错误,而是客户将软件投入到实际任务中。
但是,我并不是提倡我们应该交付马虎的代码。只是在 alpha 和 beta 发布的经典模型中,将代码投入到现场最终可以比让它永远停留在开发中,并且通过模拟器推送越来越复杂的测试用例和使用场景,产生更强大的应用程序。
但是,嗯,我离题了!
目前,我们已经为 A 的双重价值问题提出了一个不错的、简单的解决方案,让我们本月将我们的脚本留在这里。下个月,我们将把新代码重新整合到主游戏中,并添加一些额外的代码来检测玩家或庄家何时拥有二十一点(两张牌 21 点)。
Dave Taylor 是 UNIX 领域 26 年的资深人士,Elm 邮件系统的创建者,并且是畅销书 Wicked Cool Shell Scripts 和 Teach Yourself Unix in 24 Hours 的作者,以及他的 16 本技术书籍。他的主要网站是 www.intuitive.com。