随机性之Crazy Eights,,


  Crazy Eights

  你可能听说过一个叫做Crazy Eights 的纸牌游戏,可能还玩过。

  

  

  计算机上的纸牌游戏都存在一个问题:这些游戏很难有多个玩家。这是因为,在大多数纸牌游戏中,都不希望你看到其他玩家的牌。如果每个人都在看同一台计算机,那么每个人都会看到所有其他人的牌,所以计算机上玩纸牌游戏时,最好只有两个玩家,也就是你和计算机。Crazy Eights就是这种适合两个玩家的游戏,下面就来建立一个Crazy Eights游戏,用户可以和计算机玩这个游戏。

  这里给出这个程序的规则,这是一个有两个玩家参与的游戏。每个玩家有5张牌,其他的牌都面朝下扣着,翻开一张牌,开始出牌,这个游戏的目标是要在另一个人之前而且在取完一副牌之前出光所有牌。

  (1) 每一轮,玩家必须做下面的操作之一。

  (2) 出一张牌,要与翻开的牌花色相同。

  (3) 出一张牌,要与翻开的牌点数相同。

  (4) 出一张8

  如果玩家出了一张8,他可以“叫花色”,这说明他可以选择花色,下一个玩家要根据这个花色出牌。

  如果玩家无法出牌,必须从这副牌中选择一张牌,增加到自己手中。

  如果玩家出光了手中的所有牌,他就赢了,根据另一个玩家手中剩余的牌计算得分:

  每个8得50分

  每个花牌(J、Q和K)得10分

  每个其他的牌按值得分

  每个A得1分

  如果一副牌发光时仍没有人获胜,游戏结束,在这种情况下,每个玩家会根据对方剩余的牌计算得分。

  可以一直玩到达到某个总分,或者直到你累了,得分最高的获胜。

  首先要对我们的纸牌对象稍做点修改,Crazy Eights中的分值与前面基本上一样,只是8除外,它的分值是50分而不是8分,可以修改Card类中的_init_方法,让8值50分,不过这会影响可能用到cards模块的所有其他游戏,最好在主程序中做这个修改,而类定义不变,我们可以这样做:

  与孩子一起学编程-python教程

  

  在这里,将新牌增加到一副牌之前,要检查它是不是一个8,如果是,就把它的分值设置为50.

  现在已经做好准备,可以具体建立游戏了,程序需要做以下工作。

  跟踪面朝上的牌。

  得到玩家的下一步选择(出牌还是抽牌)

  如果玩家想要出牌,要确保出牌是合法的。

  这张牌必须是合法的牌

  这张牌必须是玩家的手里

  这张牌要与面朝上的牌花色或点数一致,或者是一个8.

  如果玩家出一张8,叫一个新的花色(并确保选择的是一个合法的花色)。

  轮到计算机选择(稍后介绍)

  确定游戏何时结束

  统计得分

  在本章后面,我们会逐条地完成上面的各项工作。其中一些工作只需一行或两行代码就可以完成,有些可能稍长一些,对这些稍长的代码,我们会创建函数,以便从主循环调用。

  主循环

  介绍具体细节之前,首先要明白程序的主循环,基本说来,玩家的计算机必须轮流选择(出牌或抽牌),直到人获胜或者双方都无法继续。如代码清单23-6所示。

  与孩子一起学编程-python教程

  

  主循环部分要确定游戏何时结束,可能在玩家或计算机出完手上的所有牌时结束,也可能双方手上都还有牌但是都无法继续(也就是说双方都不能合法地出牌),此时游戏也会结束,轮到玩家出牌时,如果玩家无法继续,会在相应代码中设置blocked变量,轮到计算机出牌时,如果计算机无法继续,同样会在相应代码中设置blocked变量,我们会一直等到blocked=2,确保玩家和计算机都无法继续1.

  注意代码清单23-6不是一个完整的程序,所以如果试图运行这个代码,你会得到一条错误消息,这只是一个主循环,我们还需要其他部分来构成一个完整的程序。

  这个代码对应一次游戏,如果希望继续玩多次,可以把整个代码包在另外一个外部while循环中:

  与孩子一起学编程-python教程

  这就得到了程序的主结构,下面需要增加各个部分来实现我们需要的功能。

  与孩子一起学编程-python教程

  明牌

  最开始发牌时,要从一副牌中选出一张牌翻过来面朝上,作为不要的一堆牌(弃牌堆)中的第一张牌,玩家出牌时,他出的这张牌也要面朝上放在弃牌堆中,弃牌堆中显示的牌叫做明牌(up card)。可以为弃牌堆建立一个列表来跟踪明牌,具体做法与代码清单23-5的测试代码中为“一手牌”建立列表相同。不过我们并不关心弃牌堆中的所有牌,我们只关心最后增加的那张牌,所以可以使用Card对象的一个实例来跟踪这张牌。

  玩家或计算机出牌时,我们会这样做:

  hand.remove(chosen_card)

  up_card=chosen_card

  当前花色

  通常,当前花色就是明牌的花色,玩家或计算机出牌时要与这个花色一致,不过,也有例外,出一张8时,玩 家可以叫花色,所以如果玩家出了一张方块8,他可能会叫花色为黑桃,这意味着下一张牌必须是黑桃,尽管现在显示的是方块(广场8).

  这说明,我们需要跟踪当前花色,因为它可能与现在显示的花色不同。可以使用一个变量active_suit来做到,active_suit=card.suit

  只要出一张牌,我们就会更新当前花色,玩家出一张8时,他会选择新的当前花色。

  轮到玩家选择

  轮到玩家出牌时,首先我们要得到他选择做什么,他可能从手中出一张牌(如果可能的话),或者从这副牌中抽一张牌。如果建立这个程序的一个GUI版本,我们会让玩家点击他想出的牌,或者点击这副牌来抽牌,不过现在先建立这个程序的一个基于文本的版本,所以玩家必须键入他的选择,然后我们要检查他键入的内容,明确他想做什么,还想检查输入是否合法。

  玩家需要提供什么样的输入呢?为了让你对这些输入有所认识,下面看一个示例游戏,玩家的输入用粗体显示:

  与孩子一起学编程-python教程与孩子一起学编程-python教程

  尽管这还不是一个完整的游戏,不过你应该已经有些了解了,玩家必须键入QS或Draw之类的文本,把他的选择告诉程序。程序要检查玩家键入的内容是合法的。这里将要使用一些字符串方法(第21章中介绍的方法)来提供帮助。

  显示手中的牌

  询问玩家想要做什么之前,我们应当为他显示他手中有哪些牌以及明牌是什么,下面是相关的代码:

  与孩子一起学编程-python教程

  如果出了一张8,我们还要告诉他当前花色是什么,所以下面再增加几行代码,如代码清单23-7所示。

  与孩子一起学编程-python教程

  就像代码清单23-6一样,代码清单23-7也不是一个完整的程序。我们还需要构建其他部分才能建立一个完整的程序,不过运行代码清单23-7中的代码时(作为完整程序的一部分),它会给出类似下面的输出:

  Your hand: 4s,QS,3c Up card: 8c Suit:Spades

  如果想使用纸牌的长名而不是短名,输出会像这样:

  与孩子一起学编程-python教程

  在我们的例子中,我们将使用知名。

   得到玩家的选择

  现在我们需要询问玩家想做什么,并处理他的响应。他主要有两种选择:

  出一张牌

  抽一张牌

  如果他决定出一张牌,我们需要确保这张牌是合法的,之前说过,需要检查3个方面。

  他选择的是一张合法的牌吗?(他是不是想出一张“蜀葵”4?)

  这张牌在他手里吗?

  选择的这张牌能合法出牌吗?(是否与明牌的点数或花色一致,或者是不是一张8?)

  不过如果再考虑一下,可以想到:他手里只能有合法的牌,所以如果我们检查到这张牌确实在他手里,就不用再考虑检查这张牌是否合法。他手里不可能有类似“蜀葵”4之类的牌,因为这在一副牌中根本不存在。

  术语箱

  验证(validate)是指确保一样东西是合法的,即允许的或者合理的。

  下面的代码可以得到并验证玩家的选择,见代码清单23-8.

  与孩子一起学编程-python教程与孩子一起学编程-python教程

  在这里,我们会得到一个合法的选择,玩家可能抽牌,也可能出一张合法的牌。如果玩家抽牌,只要这副牌中还有剩余的牌,就在玩家手里增加一张牌1.

  如果出一张牌,需要从玩家手里删除这张牌,让它成为明牌:

  与孩子一起学编程-python教程

  如果出的牌是一张8,玩家要告诉我们他下一步想要什么花色,因为player_turn()函数稍有点长,我们把得到新花色的代码放在一个单独的函数中,名为get_new_suit()。代码清单23-9显示了这个函数的代码。

  与孩子一起学编程-python教程

  轮到玩家出牌时所要做的就是这些,下一节中,我们要让计算机变得足够聪明来玩这个Crazy Eights游戏。

  轮到计算机选择

  轮到选择之后,就轮到计算机了,所以我们要告诉程序怎么玩Crazy Eights。它必须与玩家遵循同样的规则,不过程序需要确定出哪一张牌,我们必须专门告诉它如何处理所有可能的情况:

  出一张8(并挑选一个新花色)

  出另一张牌

  抽牌

  为了简化程序,我们要告诉计算机如果有8就总是出8.这可能不是最佳的策略,不过很简单。

  如果计算机出了一张8,它必须挑选新花色,最简单的方法就是统计计算机手中每种花色各有多少张牌,并选择牌数最多的花色,同样,这也不是最完美的策略,不过这样编写代码最为简单。

  如果计算机手中没有8,程序就必须检查所有牌,查看哪些牌可以出。在这些牌中,它会选择出分值最大的牌。

  如果根本无法出牌,计算机会抽牌。倘若计算机想要抽牌,但这副牌中已经没有任何牌了,计算机就无法继续,这和人类玩家是一样的。

  代码清单23-10显示了轮到计算机选择的相应代码,这里给出了一些说明来作出解释 。

  与孩子一起学编程-python教程与孩子一起学编程-python教程

  这个程序已经基本上完成了,只需要增加几点就可以了,你可能已经注意到,轮到计算机选择定义为一个函数,而且我们在这个函数中使用了一些全局变量,其实也可以向这个函数传入变量,不过使用全局变量也完全可以,而且与真实世界的实际情况更接近,副牌是“全局”的----任何人都可以拿到并从中取一张牌。

  轮到玩家选择也是一个函数,不过我们还没有显示这个函数定义的第一部分,这部分是这样的:

  与孩子一起学编程-python教程与孩子一起学编程-python教程

  现在还有一点要做,我们必须跟踪最终谁获胜!

  记录分数

  要完成这个游戏,还需要最后一点:这就是记录得分。游戏结束时,需要得到赢家的得分,还要根据输家剩余的牌来计算,我们要显示这次游戏的得分,还要显示所有游戏的总分,加入这些内容后,就得到了类似代码清单23-11的主循环。

  与孩子一起学编程-python教程与孩子一起学编程-python教程

  Init_cards()函数(这里没有显示)的工作只是建立一副牌并创建玩家的一手牌(5张牌)、计算机的一手牌(5张牌)以及第一张明牌1.

  代码清单23-11仍然不是一个完整的程序,所以如果你运行这个代码,就会得到一条错误消息,不过如果你一直按我说的做,现在你的编辑器里应该已经有了几乎整个程序。Crazy Eights的完整代码清单太长了,无法在这里全部列出(大约200行代码,还要加上空行和注释),不过你可以在examples文件夹找到这个代码。

  可以使用IDLE或SPE来编辑和运行这个程序,如果使用SPE,要用Run in terminal without arguments选项(Shift-F9),这会在它自己的命令窗口运行这个程序。

  你学到了什么

  在这一章,你学到了以下内容。

  什么是随机性和随机事件

  有关概率的一点内容

  如何使用random模块在程序中生成随机事件。

  如何模拟扔硬币或掷骰子

  如何模拟从一副洗过的牌中抽牌

  如何玩Crazy Eights(如果你以前不知道)

  测试题

  说明什么是“随机事件”,给出两个例子

  为什么扔一个11面(各个面上的数为2-12)的骰子与扔两个6面的骰子(总和也是2-12)不同?

  在Python中有哪两种方法来模拟掷骰子?

  我们使用哪种Python变量表示一张牌?

  要在抽牌时从一副牌中删除一张牌,或者出牌时从一手牌中删除一张牌,要使用什么方法?

  动手试一试

  使用代码清单23-3的程序试一试“连续10次面朝上”试验,不过可以试试不同的连续次数。多久能出现一次连续5个面朝上?6个呢?7个呢?8个呢?……你发现规律了吗?

评论关闭