练习43--面向对象的分析和设计基础

一 设计方法和技巧

1 设计过程

  • 1. 把问题写或者划下来。
  • 2. 提炼出关键概念,并进行研究。
  • 3. 为这些概念创建一个类的层级和对象关系图。
  • 4. 写下这些类的代码,并测试运行。
  • 5. 重复和改进。
这是一种“自上而下”的方式,它从非常抽象、松散的想法开始,然后慢慢提炼,直到想法变得具体,可以通过代码来实现

2 详细设计技巧

       我会先从写下问题开始,尽可能地写下我所能想到的点。可能我还会画一两张图表、地图之类的,甚至会给我自己写一系列邮件来阐述这个问题。这样能让我针对这个问题把一些关键的概念表达出来,并且探索出关于该问题我可能已经掌握的东西
       然后我会过一遍这些笔记、图表以及描述,从其中抽象出关键概念。这里有一个小技巧:把你所写所画的东西里面所有的名词和动词列一个表出来,然后写下它们之间是如何相互关联的。这种方法让我得到了一个关于下一步的类、对象和函数名的列表。我拿着这个概念列表,研究其中我不明白的点,如果我需要的话,对其进行改进
       一旦我有了这个概念列表,我就创造了一个简单的概念框架,以及它们作为类是如何相互关联的。你可以经常列你的名词表,然后问自己“这个跟其他的概念名词类似吗?也就是说,它们有共同的父类吗?有的话应该叫什么?”重复这个过程直到你得到一个类的层级结构,可能就是一个简单的树状图或者示意图。然后把所有的动词挑出来,看看它们能不能作为每个类的函数名,然后把它们放到你的树状图里面。
       等类的层级结构梳理清楚之后,我会坐下来,写一些基本的代码框架,只是一些类和它们的函数,没有其他东西。然后我会写一些测试代码,跑一下,看这些有没有意义以及能不能正常运行。有时我会先写测试代码,有时候就是一小段测试,一小段代码,再一小段测试,以此类推,直到我把整个程序构建起来。
       最后,我会重复这个过程,并且在运行的过程中不断精简,在添加更多应用之前让代码更简洁明了。如果我在某个特定环节因为一个概念或者我没有预料到的问题而卡壳,我会坐下来,只运行这一部分,直到把问题弄明白之后再继续

二 一个简单的游戏引擎分析——“来自25号行星的哥顿人”(Gothons from Planet Percal #25)

1 写或画出这个问题

(1)我会写一小段关于这个游戏的文字——游戏是干什么的,类似于需求分析吧,或者最后的实现效果:

“外星人入侵了一艘宇宙飞船,我们的英雄必须穿过迷宫般的房间打败他们,这样他才能逃到逃生舱去到下面的星球。游戏更像是 Zork 之类的文字冒险游戏,并且有着很有意思的死亡方式。这款游戏的引擎会运行一张满是房间或场景的地图。当玩家进入游戏时,每个房间都会打印自己的描述,然后告诉引擎下一步该运行地图中的哪个房间。”

(2)这时我有了一个关于这个游戏以及它如何运行的好想法,所以现在我要描述一下每个场景:——将需求分解,逐步完成

  • 死亡(Death):玩家死的时候,会非常有意思。
  • 中央走廊(Central Corridor):这是起点,已经有一个哥顿人站在那里,在继续之前,玩家必须用一个笑话来击败他。
  • 房间(Room):不同的房间存在不同的陷阱,成功避过这些陷阱的话就会更加接近目的地。
  • 激光武器军械械库(Laser Weapon Armory):这是英雄在到达逃生舱之前用中子弹炸毁飞船的地方。这里有一个键盘,英雄必须猜出数字。
  • (The Bridge):另一个和哥顿人战斗的场景,英雄在这里放置了炸弹。
  • 逃生舱(Escape Pod):英雄逃脱的地方,前提是他猜出正确的逃生舱。
 

2 抽取关键概念并予以研究

我现在有足够的信息来提取其中的名词,并分析他们的类层级结构。首先,我会做一个所有名词的列表:
• Alien(外星人)
• Player(玩家)
• Ship(飞船)
• Maze(迷宫)
• Room(房间)
• Scene(场景)
• Gothon(哥特人)
• Escape Pod(逃生舱)
• Planet(行星)
• Map(地图)
• Engine(引擎)
• Death(死亡)
• Central Corridor(中央走廊)
• Laser Weapon Armory(激光武器军械库)
• The Bridge(桥)

我可能还会浏览一遍所有的动词,看它们适不适合作为函数名,但是我会先暂时跳过这一步。

       现在你可能也会研究一下每个概念以及任何你不明白的东西。比如,我会玩几个同类型的游戏,确保我知道它们是如何工作的。我可能还会研究船是如何设计的或者炸弹是怎么用的。还有一些技术性问题,比如如何把游戏状态储存在数据库中。当我完成这些研究,我可能会基于这些新信息从第一步开始,重新写我的描述,并做概念提取

3 为这些概念创建类的层级结构和对象地图

        很快我就发现“房间”(“Room”)和“场景”(“Scene”)基本上是同一种东西,取决于我想用它们来做什么。在这个游戏中我选择用“场景”。然后我意识到所有特定的房间比如“中央走廊”其实就是“场景”。我还发现“死亡”(“Death”)也可以说是场景,这确认了我选择“场景”而不是“房间”的正确性,因为你可以说“死亡”是一种场景,但如果说它是一个“房间”就有点奇怪了。“迷宫”(“Maze”)和“地图”(“Map”)也基本上是同一种东西,我会选择用“地图”,因为我更常用它。我不想做一个战斗系统,所以我会暂时忽略“外星人”(“Alien”)和“玩家”(“Player”)这两个东西,先保存起来以备后用。“行星”(“Planet”)也可以是另一种场景,而不是其他特定的东西。

经过上述思考过程,我开始创建一个看起来像这样的类的层级结构:

然后我会浏览一遍,基于我描述里面的东西,想想看每个类下面需要些什么动作。例如,我从描述里知道,我需要一种方式来“运行”这个引擎,从地图“到达下一个场景”,到达“开场”,并“进入”一个场景,我会像这样把这些动作加上:

注意我只把“enter”放在了“场景”下面,所有“场景”下面的东西都会继承这个动作,需要随后再重写。

4 编写类代码并通过测试来运行

一旦我有了这个类和函数的树状图,我在我的编辑器里面打开一个源文件,试着写它们的代码。通常我就是把树状图里的东西复制粘贴到源文件里,然后把它们编辑成类。下面是它们最开始的样子,文件最后放了一个小测试:
ex43_classes.py
 1 class Scene(object):
 2 
 3     def enter(self):
 4     pass
 5 
 6 class Engine(object):
 7     
 8     def __init__(self, scene_map):
 9     pass
10 
11     def play(self):
12     pass
13 
14 class Death(Scene):
15 
16     def enter(self):
17     pass
18     
19 class CentralCorridor(Scene):
20 
21     def enter(self):
22     pass
23     
24 class LaserWeaponArmory(Scene):
25     
26     def enter(self):
27     pass
28 
29 class TheBridge(Scene):
30 
31     def enter(self):
32     pass
33     
34 class EscapePod(Scene):
35 
36     def enter(self):
37     pass
38 
39 class Map(object):
40 
41     def __init__(self, start_scene):
42     pass
43 
44     def next_scene(self, scene_name):
45     pass
46 
47     def opening_scene(self):
48     pass
49 
50 
51 a_map = Map('central_corridor')
52 a_game = Engine(a_map)
53 a_game.play()
ex43_classes.py
在这个文件中你可以看到,我只是复制了层级结构中我想要的东西,并在最后加上了一些测试代码来运行,看这个基本结构能不能成立。在这个练习后面的几部分,你会填上剩余的代码,让它像游戏描述中那样运行。 

5 重复和改进

       过程的最后一步准确来说不是一个步骤,而像是一个 while 循环。你不可能一次完成这个过程。相反,你会再次回顾整个过程,并根据你从后续步骤中学到的信息对其进行改进。有时我会进入第三步,然后意识到我需要再回到第一步和第二步,那我就会停下来,回到前面去做。有时我会灵光一闪,跳到最后,把脑子里的解决方案代码敲出来,然后再回过头来做前面的步骤,以确保我涵盖了所有可能的情况。

       在这个过程中你需要注意的另一个问题是,它不仅仅是你在一个单一层面上做的事,而是当你遇到一个特定的问题时,你可以在每个层面上做的事情。假设我不知道怎么写 Engine.play 这个方法。我可以停下来,把整个过程专注在这一个函数上来弄明白代码应该怎么写。

三 自上而下VS自下而上

这个过程通常被称为“自上而下”,因为它从最抽象的概念(上)开始,然后一直向下到实际的应用。我希望你能从现在开始分析这本书里遇到的问题时使用我刚才描述的这个过程,但是你应该知道编程中还有另一种解决问题的方式,那就是,从写代码开始,然后逐渐“上升”到抽象的概念,这种方式被称为“自下而上”。它的步骤大致如下: 

1. 从问题中拿出一小部分,开始写简单的能运行的代码。
2. 然后用类和自动化测试来把代码改进地更正式一些。
3. 抽象出你所使用的关键概念,试着探究一下它们。
4. 针对正在发生的事情写一段描述。
5. 回过头去继续改进代码,也可能把之前写的删掉重新开始。
6. 转到这个问题的其他部分,然后重复以上步骤。 
       这个过程只有在你对编程已经比较熟练并且面对问题能够自然使用编程思维的情况下才会更好,同时,当你知道整个工程的一小部分、却对全局概念的信息掌握不全的时候,这个方法也很好用。你可以将整个过程拆解成很多小块,然后边写代码边探索,这样可以帮助你一点一点地钻研这个问题,直到整个问题都得到解决。

四  “来自25号行星的哥顿人”游戏代码

1 代码讲解

导入部分:

1 from sys import exit
2 from random import randint
3 from textwrap import dedent
导入相应的模块
这是游戏所需库的基本引入。唯一的新东西是从 textwrap 模块导入 dedent 函数。这个函数将帮助我们使用 """ (三引号)字符串来编写我们的房间描述。它就是简单地从字符串的行首删除空白。如果没有这个函数,使用 """ 样式字符串就会失败,因为它们在屏幕上缩进的程度与Python代码相同。 

基类Scene创建部分:

1 class Scene(object):
2     
3     def enter(self):
4         print("This scene is not yet configured.")
5         print("Subclass it and implement enter().")
6         exit(1)
基类Scene创建
正如你在框架代码中看到的,我有一个基类 Scene,它具有所有场景都具有的公共功能。在这个简单的程序中,它们不会做太多的工作,只是向你演示如何创建基类。 

Engine类创建部分:

 1 class Engine(object):
 2     
 3     def __init__(self,scene_map):
 4         self.scene_map = scene_map
 5         
 6     def play(self):
 7         current_scene = self.scene_map.opening_scene()
 8         last_scene = self.scene_map.next_scene('finished')
 9         
10         while current_scene != last_scene:
11             next_scene_name = current_scene.enter()
12             current_scene = self.scene_map.next_scene(next_scene_name)
13         
14         # be sure to print out the last scene
15         current_scene.enter()
Engine类
我还有 Engine 类。你可以看到我已经在使用 Map.opening_scene 和 Map.next_scene 这两个方法了。因为我已经提前计划好了,所以可以在写出 Map 类之前就把这些方法写下来并用起来。 

Death类创建部分:

 1 class Death(Scene):
 2     
 3     quips = [
 4         "You died.You kinda suck at this.",
 5         "Your Mom would be proud...if she ware smarter.",
 6             "Such a     luser.",
 7             "I have     a small puppy that's better at this.",
 8             "You're     worse than your Dad's jokes."
 9     ]
10     
11     def enter(self):
12         print(Death.quips[randint(0,len(self.quips)-1)])
13         exit(1)
Death类

我的第一个场景很反常地设置为了 Death,主要是想向你展示你可以写的最简单的场景。

CentralCorridor中央走廊部分的创建:

 1 class CentralCorridor(Scene):
 2     
 3     def enter(self):
 4         print(dedent("""
 5             The Gothons of Planet Percal #25 have invaded your ship and
 6             destroyed your entire crew.You are the last surviving
 7             member and your last mission is to get the neutron destruct
 8             bomb from the weapons Armory,put it in the bridge,and 
 9             blow the ship up after getting into an escape pod.
10             
11             You're running down the central corridor to the Weapons
12             Armory when a Gothon jumps out,red scaly skin,dark grimy
13             teeth,and evil clown costume flowing around his hate
14             filled body.He's blocking the door to the Armory and
15             about to pull a weapon to blast you.
16         """))
17         
18         action = input("> ")
19         
20         if action == "shoot!":
21             print(dedent("""
22                 Quick on the draw you yank out your blaster and fire
23                 it an the Gothon. His clown costume is flowing and
24                 moving around his body,which throws off your aim.
25                 Your laser hits his costume but misses him entirely.
26                 This completely ruins his brand new costume his mother
27                 bought him,which makes him fly into an insame rage
28                 and blast you repeatedly in the face until you are 
29                 dead.Then he eats you.
30             """))
31             return 'death'
32         
33         elif action == "dodge!":
34             print(dedent("""
35                 Like a word class boxer you dodge,weave,slip and
36                 slide right as the Gothon's blaster cranks a laser
37                 past your head.In the middle of your artful dodge
38                 your foot slips and you bang your head on the metal
39                 wall and pass out.You wake up shortly after only to
40                 die as the Gothon stomps on your head and eats you.
41             """))
42             return 'death'
43         
44         elif action == "tell a joke":
45             print(dedent("""
46                 Lucky for you they made you learn Gothon insults in 
47                 the academy. You tell the one Gothon joke you know:
48                 Lbhe zbgure vf fb sng,jura fur fvgf nebhaq gur ubhfr,
49                 fur fvgf nebhaq gur ubfhr.The Gothon stops,tries
50                 not to laugh, then busts out laughing and can't move.
51                 While he's laughing you run up and shoot him square in 
52                 the head putting him down,then jump through the 
53                 Weapon Armory door.
54             """))
55             return 'laser_weapon_armory'
56         
57         else:
58             print("DOES NOT COMPUTE!")
59             return 'central_corridor'
CentralCorridor类
然后我创建了中央走廊,这是游戏的开始。我把游戏的场景放在 Map 之前,是因为我需要在随后引用它们。你应该也看到了我在第4行用了 dedent 函数。稍后你可以尝试删除它,看看它会做什么。

剩余的游戏场景创建:

  1 class LaserWeaponArmory(Scene):
  2     
  3     def enter(self):
  4         print(dedent("""
  5             You do a dive roll into the Weapon Armory, crouch and scan
  6             the room for more Gothons that might be hiding. It's dead
  7             quiet, too quiet. You stand up and run to the far side of
  8             the room and find the neutron bomb in its container.
  9             There's a keypad lock on the box and you need the code to
 10             get the bomb out. If you get the code wrong 10 times then
 11             the lock closes forever and you can't get the bomb. The
 12             code is 3 digits.
 13         """))
 14         
 15         code = f"{randint(1,9)}{randint(1,9)}{randint(1,9)}"
 16         guess = input("[keypad]> ")
 17         guesses = 0
 18         
 19         while guess != code and guesses < 10:
 20             print("BZZZZEDDD!")
 21             guesses += 1
 22             guess = input("[keypad]> ")
 23         
 24         if guess == code:
 25             print(dedent("""
 26                 The container clicks open and the seal breaks, letting
 27                 gas out. You grab the neutron bomb and run as fast as
 28                 you can to the bridge where you must place it in the
 29                 right spot.
 30             """))
 31             return 'the_bridge'
 32         else:
 33             print(dedent("""
 34                 The lock buzzes one last time and then you hear a
 35                 sickening melting sound as the mechanism is fused
 36                 together. You decide to sit there, and finally the
 37                 Gothons blow up the ship from their ship and you die.
 38             """))
 39             return 'death'
 40 
 41 class TheBridge(Scene):
 42     
 43     def enter(self):
 44         print(dedent("""
 45             You burst onto the Bridge with the netron destruct bomb
 46             under your arm and surprise 5 Gothons who are trying to
 47             take control of the ship. Each of them has an even uglier
 48             clown costume than the last. They haven't pulled their
 49             weapons out yet, as they see the active bomb under your
 50             arm and don't want to set it off.
 51         """))
 52         
 53         action = input("> ")
 54         
 55         if action == "throw the bomb":
 56             print(dedent("""
 57                 In a panic you throw the bomb at the group of Gothons
 58                 and make a leap for the door. Right as you drop it a
 59                 Gothon shoots you right in the back killing you. As
 60                 you die you see another Gothon frantically try to
 61                 disarm the bomb. You die knowing they will probably
 62                 blow up when it goes off.
 63             """))
 64             return 'death'
 65         
 66         elif action == "slowly place the bomb":
 67             print(dedent("""
 68                 You point your blaster at the bomb under your arm and
 69                 the Gothons put their hands up and start to sweat.
 70                 You inch backward to the door, open it, and then
 71                 carefully place the bomb on the floor, pointing your
 72                 blaster at it. You then jump back through the door,
 73                 punch the close button and blast the lock so the
 74                 Gothons can't get out. Now that the bomb is placed
 75                 you run to the escape pod to get off this tin can.
 76             """))
 77             return 'escape_pod'
 78         
 79         else:
 80             print("DOES NOT COMPUTE!")
 81             return 'the_bridge'
 82 
 83 class EscapedPod(Scene):
 84     
 85     def enter(self):
 86         print(dedent("""
 87             You rush through the ship desperately trying to make it
 88             the escape pod before the whole ship explodes. It seems
 89             like hardly any Gothons are on the ship, so your run is
 90             clear of interference. You get to the chamber with the
 91             escape pods, and now need to pick one to take. Some of
 92             them could be damaged but you don't have time to look.
 93             There's 5 pods, which one do you take?
 94         """))
 95         
 96         good_pod = randint(1,5)
 97         guess = input("[pod #]> ")
 98         
 99         if int(guess) != good_pod:
100             print(dedent("""
101                 You jump into pod {guess} and hit the eject button.
102                 The pod escapes out into the void of space, then
103                 implodes as the hull ruptures, crushing your body into
104                 jam jelly.
105             """))
106             return 'death'
107         else:
108             print(dedent("""
109                 You jump into pod {guess} and hit the eject button.
110                 The pod easily slides out into space heading to the
111                 planet below. As it flies to the planet, you look
112                 back and see your ship implode then explode like a
113                 bright star, taking out the Gothon ship at the same
114                 time. You won!
115             """))
116             return 'finished'
117 
118 class Finished(Scene):
119     
120     def enter(self):
121         print("You won! Good job.")
122         return 'finished'
激光武器军械库和桥的部分
这是游戏的剩余场景,因为我知道我需要它们,并且已经想过它们之间如何流转,所以我能直接把代码写出来。
顺便说一句,我不会把所有这些代码都输入进去。还记得我说过要循序渐进,一点一点来。现在我只给你们看最后的结果。

Map地图类创建部分:

 1 class Map(object):
 2     
 3     scenes = {
 4         'central_corridor':CentralCorridor(),
 5         'laser_weapon_armory':LaserWeaponArmory(),
 6         'the_bridge':TheBridge(),
 7         'escape_pod':EscapedPod(),
 8         'death':Death(),
 9         'finished':Finished(),
10     }
11     
12     def __init__(self,start_scene):
13         self.start_scene = start_scene
14     
15     def next_scene(self,scene_name):
16         val = Map.scenes.get(scene_name)
17         return val
18     
19     def opening_scene(self):
20         return self.next_scene(self.start_scene)
Map类
然后是我的 Map 类,你可以看到它把每个场景的名字存储在一个字典里,然后我用Map.scenes 引用那个字典。这也是为什么地图出现在场景之后的原因,因为字典必须引用场
景,所以场景必须先存在。

启动游戏部分/实例化/调用过程:

1 a_map = Map('central_corridor')
2 a_game = Engine(a_map)
3 a_game.play()
实例化/启动游戏
最后是我通过制作地图来运行游戏的代码,在调用 play 使游戏工作之前,我把地图交给了引擎。 

ex43.py完整代码:

  1 from sys import exit
  2 from random import randint
  3 from textwrap import dedent
  4 
  5 class Scene(object):
  6     
  7     def enter(self):
  8         print("This scene is not yet configured.")
  9         print("Subclass it and implement enter().")
 10         exit(1)
 11 
 12 class Engine(object):
 13     
 14     def __init__(self,scene_map):
 15         self.scene_map = scene_map
 16         
 17     def play(self):
 18         current_scene = self.scene_map.opening_scene()
 19         last_scene = self.scene_map.next_scene('finished')
 20         
 21         while current_scene != last_scene:
 22             next_scene_name = current_scene.enter()
 23             current_scene = self.scene_map.next_scene(next_scene_name)
 24         
 25         # be sure to print out the last scene
 26         current_scene.enter()
 27 
 28 class Death(Scene):
 29     
 30     quips = [
 31         "You died.You kinda suck at this.",
 32         "Your Mom would be proud...if she ware smarter.",
 33             "Such a     luser.",
 34             "I have     a small puppy that's better at this.",
 35             "You're     worse than your Dad's jokes."
 36     ]
 37     
 38     def enter(self):
 39         print(Death.quips[randint(0,len(self.quips)-1)])
 40         exit(1)
 41 
 42 class CentralCorridor(Scene):
 43     
 44     def enter(self):
 45         print(dedent("""
 46             The Gothons of Planet Percal #25 have invaded your ship and
 47             destroyed your entire crew.You are the last surviving
 48             member and your last mission is to get the neutron destruct
 49             bomb from the weapons Armory,put it in the bridge,and 
 50             blow the ship up after getting into an escape pod.
 51             
 52             You're running down the central corridor to the Weapons
 53             Armory when a Gothon jumps out,red scaly skin,dark grimy
 54             teeth,and evil clown costume flowing around his hate
 55             filled body.He's blocking the door to the Armory and
 56             about to pull a weapon to blast you.
 57         """))
 58         
 59         action = input("> ")
 60         
 61         if action == "shoot!":
 62             print(dedent("""
 63                 Quick on the draw you yank out your blaster and fire
 64                 it an the Gothon. His clown costume is flowing and
 65                 moving around his body,which throws off your aim.
 66                 Your laser hits his costume but misses him entirely.
 67                 This completely ruins his brand new costume his mother
 68                 bought him,which makes him fly into an insame rage
 69                 and blast you repeatedly in the face until you are 
 70                 dead.Then he eats you.
 71             """))
 72             return 'death'
 73         
 74         elif action == "dodge!":
 75             print(dedent("""
 76                 Like a word class boxer you dodge,weave,slip and
 77                 slide right as the Gothon's blaster cranks a laser
 78                 past your head.In the middle of your artful dodge
 79                 your foot slips and you bang your head on the metal
 80                 wall and pass out.You wake up shortly after only to
 81                 die as the Gothon stomps on your head and eats you.
 82             """))
 83             return 'death'
 84         
 85         elif action == "tell a joke":
 86             print(dedent("""
 87                 Lucky for you they made you learn Gothon insults in 
 88                 the academy. You tell the one Gothon joke you know:
 89                 Lbhe zbgure vf fb sng,jura fur fvgf nebhaq gur ubhfr,
 90                 fur fvgf nebhaq gur ubfhr.The Gothon stops,tries
 91                 not to laugh, then busts out laughing and can't move.
 92                 While he's laughing you run up and shoot him square in 
 93                 the head putting him down,then jump through the 
 94                 Weapon Armory door.
 95             """))
 96             return 'laser_weapon_armory'
 97         
 98         else:
 99             print("DOES NOT COMPUTE!")
100             return 'central_corridor'
101 
102 class LaserWeaponArmory(Scene):
103     
104     def enter(self):
105         print(dedent("""
106             You do a dive roll into the Weapon Armory, crouch and scan
107             the room for more Gothons that might be hiding. It's dead
108             quiet, too quiet. You stand up and run to the far side of
109             the room and find the neutron bomb in its container.
110             There's a keypad lock on the box and you need the code to
111             get the bomb out. If you get the code wrong 10 times then
112             the lock closes forever and you can't get the bomb. The
113             code is 3 digits.
114         """))
115         
116         code = f"{randint(1,9)}{randint(1,9)}{randint(1,9)}"
117         guess = input("[keypad]> ")
118         guesses = 0
119         
120         while guess != code and guesses < 10:
121             print("BZZZZEDDD!")
122             guesses += 1
123             guess = input("[keypad]> ")
124         
125         if guess == code:
126             print(dedent("""
127                 The container clicks open and the seal breaks, letting
128                 gas out. You grab the neutron bomb and run as fast as
129                 you can to the bridge where you must place it in the
130                 right spot.
131             """))
132             return 'the_bridge'
133         else:
134             print(dedent("""
135                 The lock buzzes one last time and then you hear a
136                 sickening melting sound as the mechanism is fused
137                 together. You decide to sit there, and finally the
138                 Gothons blow up the ship from their ship and you die.
139             """))
140             return 'death'
141 
142 class TheBridge(Scene):
143     
144     def enter(self):
145         print(dedent("""
146             You burst onto the Bridge with the netron destruct bomb
147             under your arm and surprise 5 Gothons who are trying to
148             take control of the ship. Each of them has an even uglier
149             clown costume than the last. They haven't pulled their
150             weapons out yet, as they see the active bomb under your
151             arm and don't want to set it off.
152         """))
153         
154         action = input("> ")
155         
156         if action == "throw the bomb":
157             print(dedent("""
158                 In a panic you throw the bomb at the group of Gothons
159                 and make a leap for the door. Right as you drop it a
160                 Gothon shoots you right in the back killing you. As
161                 you die you see another Gothon frantically try to
162                 disarm the bomb. You die knowing they will probably
163                 blow up when it goes off.
164             """))
165             return 'death'
166         
167         elif action == "slowly place the bomb":
168             print(dedent("""
169                 You point your blaster at the bomb under your arm and
170                 the Gothons put their hands up and start to sweat.
171                 You inch backward to the door, open it, and then
172                 carefully place the bomb on the floor, pointing your
173                 blaster at it. You then jump back through the door,
174                 punch the close button and blast the lock so the
175                 Gothons can't get out. Now that the bomb is placed
176                 you run to the escape pod to get off this tin can.
177             """))
178             return 'escape_pod'
179         
180         else:
181             print("DOES NOT COMPUTE!")
182             return 'the_bridge'
183 
184 class EscapedPod(Scene):
185     
186     def enter(self):
187         print(dedent("""
188             You rush through the ship desperately trying to make it
189             the escape pod before the whole ship explodes. It seems
190             like hardly any Gothons are on the ship, so your run is
191             clear of interference. You get to the chamber with the
192             escape pods, and now need to pick one to take. Some of
193             them could be damaged but you don't have time to look.
194             There's 5 pods, which one do you take?
195         """))
196         
197         good_pod = randint(1,5)
198         guess = input("[pod #]> ")
199         
200         if int(guess) != good_pod:
201             print(dedent("""
202                 You jump into pod {guess} and hit the eject button.
203                 The pod escapes out into the void of space, then
204                 implodes as the hull ruptures, crushing your body into
205                 jam jelly.
206             """))
207             return 'death'
208         else:
209             print(dedent("""
210                 You jump into pod {guess} and hit the eject button.
211                 The pod easily slides out into space heading to the
212                 planet below. As it flies to the planet, you look
213                 back and see your ship implode then explode like a
214                 bright star, taking out the Gothon ship at the same
215                 time. You won!
216             """))
217             return 'finished'
218 
219 class Finished(Scene):
220     
221     def enter(self):
222         print("You won! Good job.")
223         return 'finished'
224 
225 class Map(object):
226     
227     scenes = {
228         'central_corridor':CentralCorridor(),
229         'laser_weapon_armory':LaserWeaponArmory(),
230         'the_bridge':TheBridge(),
231         'escape_pod':EscapedPod(),
232         'death':Death(),
233         'finished':Finished(),
234     }
235     
236     def __init__(self,start_scene):
237         self.start_scene = start_scene
238     
239     def next_scene(self,scene_name):
240         val = Map.scenes.get(scene_name)
241         return val
242     
243     def opening_scene(self):
244         return self.next_scene(self.start_scene)
245 
246 a_map = Map('central_corridor')
247 a_game = Engine(a_map)
248 a_game.play()
“来自25号行星的哥顿人”游戏代码

五 代码中涉及到的知识

 1 sys.exit(n) 

功能:执行到主程序末尾,解释器自动退出,但是如果需要中途退出程序,可以调用sys.exit函数,带有一个可选的整数参数返回给调用它的程序,表示你可以在主程序中捕获对sys.exit的调用。(0是正常退出,其他为异常)

有关sys模块的其它内容可参考:https://www.cnblogs.com/cherishry/p/5725184.html

2 textwrap.dedent()函数

功能:去除缩进

有关textwrap模块的其它内容,可参考:https://www.cnblogs.com/wj5633/p/6931187.html

3 代码详解

  1 from sys import exit                                         # 导入exit()方法,用来控制程序的结束时间
  2 from random import randint                                   # 导入randint()方法产生随机整数
  3 from textwrap import dedent                                  # 导入dedent()方法去除缩进
  4 
  5 # 定义场景基类
  6 class Scene(object):
  7     
  8     def enter(self):
  9         print("This scene is not yet configured.")
 10         print("Subclass it and implement enter().")
 11         exit(1)
 12 
 13 # 程序运行的主体部分Engine类
 14 class Engine(object):
 15     
 16     def __init__(self,scene_map):                              # 在创建Engine类的对象a_game的过程中,已经将Map类的对象a_map的值传给了参数scene_map
 17         self.scene_map = scene_map                             # 将a_map的值传给self.scene_map,相当于创建了一个新的Map类的对象
 18         # print(f"self.scene_map = {self.scene_map}\n")
 19         
 20     def play(self):                                            # 通过Engine类的对象a_game调用Engine类的方法play,开始游戏:
 21         # print(f"self.scene_map.start_scene = {self.scene_map.start_scene}\n")
 22         current_scene = self.scene_map.opening_scene()         # 通过Map类的对象self.scene_map调用opening_scene(),得到返回值val(此时为类CentralCorridor()的对象);
 23         # print(f"current_scene1 = {current_scene}\n")         # 通过将val的值赋给current_scene把它初始化为类CentralCorridor()的对象
 24         last_scene = self.scene_map.next_scene('finished')     # 通过Map类的对象self.scene_map调用next_scene()方法,得到返回值val(此时为类Finished()的对象)
 25         # print(f"last_scene = {last_scene}\n")                # 通过将val的值赋给last_scene把它初始化为类Finished()的对象
 26         
 27         while current_scene != last_scene:
 28             next_scene_name = current_scene.enter() # 通过类CentralCorridor()的对象current_scene调用类CentralCorridor()中的方法enter(),并将函数返回值赋给next_scene_name变量
 29             # print(f"next_scene_name = {next_scene_name}\n")
 30             current_scene = self.scene_map.next_scene(next_scene_name)# 通过Map类的对象self.scene_map调用Map类中的方法,将current_scene初始化为新的场景类的对象
 31             # print(f"current_scene2 = {current_scene}\n")
 32             
 33         # be sure to print out the last scene
 34         current_scene.enter()
 35         # print(f"current_scene3 = {current_scene}\n")
 36 
 37 # 只要出错,就会触发Death()类
 38 class Death(Scene):
 39     
 40     quips = [
 41         "You died.You kinda suck at this.",
 42         "Your Mom would be proud...if she ware smarter.",
 43             "Such a     luser.",
 44             "I have     a small puppy that's better at this.",
 45             "You're     worse than your Dad's jokes."
 46     ]
 47     
 48     def enter(self):
 49         print(Death.quips[randint(0,len(self.quips)-1)])      # 通过生成随机数的方式任意返回列表quips中的一个元素
 50         exit(1)
 51 
 52 # 程序运行的初始场景,通过a_map = Map('central_corridor')语句第一次进入
 53 class CentralCorridor(Scene):
 54     
 55     def enter(self):
 56         print(dedent("""
 57             The Gothons of Planet Percal #25 have invaded your ship and
 58             destroyed your entire crew.You are the last surviving
 59             member and your last mission is to get the neutron destruct
 60             bomb from the weapons Armory,put it in the bridge,and 
 61             blow the ship up after getting into an escape pod.
 62             
 63             You're running down the central corridor to the Weapons
 64             Armory when a Gothon jumps out,red scaly skin,dark grimy
 65             teeth,and evil clown costume flowing around his hate
 66             filled body.He's blocking the door to the Armory and
 67             about to pull a weapon to blast you.
 68         """))
 69         
 70         action = input("> ")                      # 根据用户的输入,执行不同的语句,返回不同的结果
 71         
 72         if action == "shoot!":
 73             print(dedent("""
 74                 Quick on the draw you yank out your blaster and fire
 75                 it an the Gothon. His clown costume is flowing and
 76                 moving around his body,which throws off your aim.
 77                 Your laser hits his costume but misses him entirely.
 78                 This completely ruins his brand new costume his mother
 79                 bought him,which makes him fly into an insame rage
 80                 and blast you repeatedly in the face until you are 
 81                 dead.Then he eats you.
 82             """))
 83             return 'death'                        # 如果用户输入的操作为"shoot!",将'death'字符串变量返回给a_map
 84         
 85         elif action == "dodge!":
 86             print(dedent("""
 87                 Like a word class boxer you dodge,weave,slip and
 88                 slide right as the Gothon's blaster cranks a laser
 89                 past your head.In the middle of your artful dodge
 90                 your foot slips and you bang your head on the metal
 91                 wall and pass out.You wake up shortly after only to
 92                 die as the Gothon stomps on your head and eats you.
 93             """))
 94             return 'death'                        # 如果用户输入的操作为"dodge!",将'death'字符串变量返回给a_map
 95         
 96         elif action == "tell a joke":
 97             print(dedent("""
 98                 Lucky for you they made you learn Gothon insults in 
 99                 the academy. You tell the one Gothon joke you know:
100                 Lbhe zbgure vf fb sng,jura fur fvgf nebhaq gur ubhfr,
101                 fur fvgf nebhaq gur ubfhr.The Gothon stops,tries
102                 not to laugh, then busts out laughing and can't move.
103                 While he's laughing you run up and shoot him square in 
104                 the head putting him down,then jump through the 
105                 Weapon Armory door.
106             """))
107             # print(f"a_map2 = {a_map}\n")
108             return 'laser_weapon_armory'           # 如果用户输入的操作为"tell a joke",将'laser_weapon_armory'字符串变量返回给a_map
109         
110         else:
111             print("DOES NOT COMPUTE!")
112             return 'central_corridor'              # 其它情况,返回'central_corridor',重新匹配
113 
114 # LaserWeaponArmory()场景只有在用户动作为"tell a joke"的情况下才会触发
115 class LaserWeaponArmory(Scene):                    # 通过Engine类中的current_scene = self.scene_map.opening_scene()语句进入该场景
116     
117     def enter(self):
118         print(dedent("""
119             You do a dive roll into the Weapon Armory, crouch and scan
120             the room for more Gothons that might be hiding. It's dead
121             quiet, too quiet. You stand up and run to the far side of
122             the room and find the neutron bomb in its container.
123             There's a keypad lock on the box and you need the code to
124             get the bomb out. If you get the code wrong 10 times then
125             the lock closes forever and you can't get the bomb. The
126             code is 3 digits.
127         """))
128         
129         code = f"{randint(1,9)}{randint(1,9)}{randint(1,9)}"
130         print(f"code = {code}\n")
131         guess = input("[keypad]> ")                          # 用户输入不同的keypad,猜谜
132         guesses = 0
133         
134         while guess != code and guesses < 10:                # 保证用户有10次猜谜机会,否则执行else语句
135             print("BZZZZEDDD!")
136             guesses += 1
137             guess = input("[keypad]> ")
138         
139         if guess == code:                                    # 如果用户猜中了,返回字符串'the_bridge'给对象current_scene
140             print(dedent("""
141                 The container clicks open and the seal breaks, letting
142                 gas out. You grab the neutron bomb and run as fast as
143                 you can to the bridge where you must place it in the
144                 right spot.
145             """))
146             return 'the_bridge'
147         else:
148             print(dedent("""
149                 The lock buzzes one last time and then you hear a
150                 sickening melting sound as the mechanism is fused
151                 together. You decide to sit there, and finally the
152                 Gothons blow up the ship from their ship and you die.
153             """))
154             return 'death'                                   # 用户猜了10次还是没有猜中,返回字符串'death'给对象current_scene
155 
156 # TheBridge()场景只有在用户成功进入LaserWeaponArmory()场景并且在猜谜游戏中猜中才会触发
157 class TheBridge(Scene):
158     
159     def enter(self):
160         print(dedent("""
161             You burst onto the Bridge with the netron destruct bomb
162             under your arm and surprise 5 Gothons who are trying to
163             take control of the ship. Each of them has an even uglier
164             clown costume than the last. They haven't pulled their
165             weapons out yet, as they see the active bomb under your
166             arm and don't want to set it off.
167         """))
168         
169         action = input("> ")
170         
171         if action == "throw the bomb":
172             print(dedent("""
173                 In a panic you throw the bomb at the group of Gothons
174                 and make a leap for the door. Right as you drop it a
175                 Gothon shoots you right in the back killing you. As
176                 you die you see another Gothon frantically try to
177                 disarm the bomb. You die knowing they will probably
178                 blow up when it goes off.
179             """))
180             return 'death'
181         
182         elif action == "slowly place the bomb":
183             print(dedent("""
184                 You point your blaster at the bomb under your arm and
185                 the Gothons put their hands up and start to sweat.
186                 You inch backward to the door, open it, and then
187                 carefully place the bomb on the floor, pointing your
188                 blaster at it. You then jump back through the door,
189                 punch the close button and blast the lock so the
190                 Gothons can't get out. Now that the bomb is placed
191                 you run to the escape pod to get off this tin can.
192             """))
193             return 'escape_pod'
194         
195         else:
196             print("DOES NOT COMPUTE!")
197             return 'the_bridge'
198 
199 # EscapedPod()场景只有在用户成功触发TheBridge()场景并且输入动作为"slowly place the bomb"的情况下才会触发
200 class EscapedPod(Scene):
201     
202     def enter(self):
203         print(dedent("""
204             You rush through the ship desperately trying to make it
205             the escape pod before the whole ship explodes. It seems
206             like hardly any Gothons are on the ship, so your run is
207             clear of interference. You get to the chamber with the
208             escape pods, and now need to pick one to take. Some of
209             them could be damaged but you don't have time to look.
210             There's 5 pods, which one do you take?
211         """))
212         
213         good_pod = randint(1,5)
214         print(f"good_pod = {good_pod}\n")
215         guess = input("[pod #]> ")
216         
217         if int(guess) != good_pod:
218             print(dedent("""
219                 You jump into pod {guess} and hit the eject button.
220                 The pod escapes out into the void of space, then
221                 implodes as the hull ruptures, crushing your body into
222                 jam jelly.
223             """))
224             return 'death'
225         else:
226             print(dedent("""
227                 You jump into pod {guess} and hit the eject button.
228                 The pod easily slides out into space heading to the
229                 planet below. As it flies to the planet, you look
230                 back and see your ship implode then explode like a
231                 bright star, taking out the Gothon ship at the same
232                 time. You won!
233             """))
234             return 'finished'
235 
236 # Finished()场景的触发条件:用户选择流程:"tell a joke"——猜谜成功(是否等于code变量的值)——"slowly place the bomb"——再次猜谜成功(是否等于good_pod变量的值)
237 class Finished(Scene):
238     
239     def enter(self):
240         print("You won! Good job.")
241         return 'finished'
242 
243 class Map(object):
244     
245     scenes = {
246         'central_corridor':CentralCorridor(),
247         'laser_weapon_armory':LaserWeaponArmory(),
248         'the_bridge':TheBridge(),
249         'escape_pod':EscapedPod(),
250         'death':Death(),
251         'finished':Finished(),
252     }
253     
254     def __init__(self,start_scene):
255         self.start_scene = start_scene
256         # print(f"self.start_scene = {self.start_scene}\n")
257     
258     def next_scene(self,scene_name):
259         val = Map.scenes.get(scene_name)
260         # print(f"val = {val}\n")
261         return val
262     
263     def opening_scene(self):
264         return self.next_scene(self.start_scene)
265 
266 a_map = Map('central_corridor')         # a_map是Map类的一个对象,此时完成的操作是:
267 # print(f"a_map1 = {a_map}\n")          # (1)将'central_corridor'传给参数start_scene,并将其赋值给self.start_scene变量
268                                         # (2)然后通过opening_scene()方法调用next_scene()方法,通过变量self.start_scene将'central_corridor'传给参数scene_name
269                                         # (3)在next_scene方法内部,通过调用Map类的字典变量scenes的内置方法get,得到键'central_corridor'
270                                         #      对应的值CentralCorridor(),然后将val初始化为类CentralCorridor()的对象
271 a_game = Engine(a_map)                  # a_game是Engine()类的一个对象,此时完成的操作是:
272 # print(f"a_game = {a_game}\n")         # (1)将Map类的对象a_map的地址作为参数传给Engine()类中构造方法的参数scene_map
273 a_game.play()                           # 通过Engine类的对象a_game调用Engine类的方法play,开始游戏:
“来自25号行星的哥顿人”游戏代码详解

运行结果:

 1 PS E:\6_AI-Study\1_Python\2_code_python\02_LearnPythonTheHardWay> python ex43.py
 2 
 3 The Gothons of Planet Percal #25 have invaded your ship and
 4 destroyed your entire crew.You are the last surviving
 5 member and your last mission is to get the neutron destruct
 6 bomb from the weapons Armory,put it in the bridge,and
 7 blow the ship up after getting into an escape pod.
 8 
 9 You're running down the central corridor to the Weapons
10 Armory when a Gothon jumps out,red scaly skin,dark grimy
11 teeth,and evil clown costume flowing around his hate
12 filled body.He's blocking the door to the Armory and
13 about to pull a weapon to blast you.
14 
15 > tell a joke
16 
17 Lucky for you they made you learn Gothon insults in
18 the academy. You tell the one Gothon joke you know:
19 Lbhe zbgure vf fb sng,jura fur fvgf nebhaq gur ubhfr,
20 fur fvgf nebhaq gur ubfhr.The Gothon stops,tries
21 not to laugh, then busts out laughing and can't move.
22 While he's laughing you run up and shoot him square in
23 the head putting him down,then jump through the
24 Weapon Armory door.
25 
26 
27 You do a dive roll into the Weapon Armory, crouch and scan
28 the room for more Gothons that might be hiding. It's dead
29 quiet, too quiet. You stand up and run to the far side of
30 the room and find the neutron bomb in its container.
31 There's a keypad lock on the box and you need the code to
32 get the bomb out. If you get the code wrong 10 times then
33 the lock closes forever and you can't get the bomb. The
34 code is 3 digits.
35 
36 code = 839
37 
38 [keypad]> 839
39 
40 The container clicks open and the seal breaks, letting
41 gas out. You grab the neutron bomb and run as fast as
42 you can to the bridge where you must place it in the
43 right spot.
44 
45 
46 You burst onto the Bridge with the netron destruct bomb
47 under your arm and surprise 5 Gothons who are trying to
48 take control of the ship. Each of them has an even uglier
49 clown costume than the last. They haven't pulled their
50 weapons out yet, as they see the active bomb under your
51 arm and don't want to set it off.
52 
53 > slowly place the bomb
54 
55 You point your blaster at the bomb under your arm and
56 the Gothons put their hands up and start to sweat.
57 You inch backward to the door, open it, and then
58 carefully place the bomb on the floor, pointing your
59 blaster at it. You then jump back through the door,
60 punch the close button and blast the lock so the
61 Gothons can't get out. Now that the bomb is placed
62 you run to the escape pod to get off this tin can.
63 
64 
65 You rush through the ship desperately trying to make it
66 the escape pod before the whole ship explodes. It seems
67 like hardly any Gothons are on the ship, so your run is
68 clear of interference. You get to the chamber with the
69 escape pods, and now need to pick one to take. Some of
70 them could be damaged but you don't have time to look.
71 There's 5 pods, which one do you take?
72 
73 good_pod = 4
74 
75 [pod #]> 4
76 
77 You jump into pod {guess} and hit the eject button.
78 The pod easily slides out into space heading to the
79 planet below. As it flies to the planet, you look
80 back and see your ship implode then explode like a
81 bright star, taking out the Gothon ship at the same
82 time. You won!
83 
84 You won! Good job.
运行结果

 

posted @ 2020-08-04 17:38  洛兰123  阅读(256)  评论(0编辑  收藏  举报