首先,先放上编写这款游戏的指导方针:
自顶向下:从很抽象的概念入手,逐渐细化,直到概念变成具体的可用代码实现的东西。
'''
游戏名字叫《来自Percal25号行星的哥顿人》,是一款空间冒险游戏。
游戏内容:外星人入侵了宇宙飞船,我们的英雄需要通过各种房间组成的迷宫,
打败遇到的外星人,这样才能通过救生船回到下面的行星上去。
游戏中一些概念:
· 死亡(Death):玩家死去的场景,应该比较搞笑。
· 中央走廊(Central Corridor):游戏的起点,哥顿人已经在那里把手了,需要讲笑话通过去。
· 激光武器库(Laser Weapon Armory):在这里英雄会找到一个中子弹,在乘坐救生船离开时需要把飞船炸掉。
这个房间有一个数字键盘,需要猜测密码组合。
· 飞船主控舱(The Bridge):另一个战斗场景,英雄需要在这里埋炸弹。
· 救生舱(Escape Pod):英雄逃离的场景,但需要猜对是哪艘救生船。
'''
'''
外星人(Alien) 玩家(Player) 飞船(Ship) 迷宫(Maze)
房间(Room) 场景(Scene) 哥顿人(Gothon) 救生舱(Escape Pod)
行星(Planet) 地图(Map) 引擎(Engine) 死亡(Death)
中央走廊(Central Corridor) 激光武器库(Laser Weapon Armory) 主控舱(The Bridge)
基本上这些就是游戏里的全部概念了,现在理清楚它们之间的关系,确定层次结构关系。
我们可以知道房间和场景其实功能一样,中央走廊啊死亡啊行星啊也其实算上一个场景。
'''
'''
所以我可以建立出这样的层次结构
Map:next_scene、opening_scene
Engine:play
Scene:enter、Death、Central Corridor、Laser Weapon Armory、The Bridge、Escape Pod
接下来的工作呢,就是根据自顶向下的方式编写出各个层次结构之间的类,然后引用必要的import模块,最后完善游戏的主体。
from sys import exit
from random import randint
# Scene是一个总类
class Scene(object):
def enter(self):
print("此Scence尚未配置,实例化它并实现enter()。")
exit(1)
# Engine是一个总类
class Engine(object):
def __init__(self, scene_map):
(self.scene_map) = scene_map
def play(self):
current_scene = (self.scene_map).opening_scene()
while True:
print("
-----------")
next_scene_name = (current_scene).enter()
current_scene = (self.scene_map).next_scene(next_scene_name)
# Death is-a Scene,Death和Scene两个类存在从属关系,以下同理。
class Death(Scene):
quips = [
"你挂了。你这方面有点烂。",
"如果你妈妈更聪明点……她会为你骄傲的。",
"这样的一个失败者……",
"我有一只小狗,它更擅长这个。"
]
def enter(self):
print(Death.quips[randint(0, len(self.quips) - 1)])
exit(1)
class CentralCorridor(Scene):
def enter(self):
print("来自Percal25号行星的哥顿人已经入侵并消灭了你的飞船上全体船员,")
print("你是最后一个幸存的成员。")
print("你的最后一个任务是从激光武器库中取出中子毁灭弹,")
print("将它放在主控舱,然后进入逃生舱后将飞船炸了。")
print("
")
print("你正沿着中央走廊往武库跑的时候,")
print("一只长着红色鳞片皮肤的哥顿人跳了出来。黑暗肮脏的牙齿,邪恶的小丑服装在他庞大的身体似乎要撑爆开来,")
print("它挡着激光武器库的门正要拔出武器来攻击你。")
print("这时候,你有三种选择:")
print("A.射击! B.闪避! C.讲个笑话! ")
action = input("你的选择是:")
if action == "A" or action == "a":
print("你快速拔枪,拔出你的爆能枪,朝那个哥顿人开火,")
print("它的小丑戏服忽然牵动它的身体飘动,偏离了你的目标,")
print("你的激光击中了它的服装,但完全没有击中它,")
print("这完全毁了它妈妈给它买的新衣服,这让它勃然大怒,")
print("它以闪电之势瞬移到你的身边,不停地打你的脸,直到你死了。")
print("然后你死了。")
return 'death'
elif action == 'B' or action == 'b':
print("你就像一个世界级的拳击手一般,当哥顿人的激光发射器从你的头顶掠过时,")
print("你会躲闪、滑铲和跳跃,在你巧妙的躲闪过程中,")
print("你的脚滑倒了,你的头撞在了金属墙上,晕了过去。")
print("你醒来后不久后,你绝望地发现哥顿人正准备踩你的头,把你吃掉。")
print("你死了。")
return 'death'
elif action == 'C' or action == 'c':
print("幸运的是,你在学院里正好学习了哥顿人的语言。")
print("你讲了一个你所知道的哥顿人的笑话。")
print("Lbhe zbgure vf fb sng,jura fur fvgf nebhaq gur ubhfr,fur fvgf nrbhaq gur ubhfr。")
print("哥顿人听完之后僵硬地停了下来,试图不笑。随后就爆发出大笑,笑得动弹不得。")
print("当它笑的时候,你跑过去给它的脑袋开了一枪,然后进去了激光哭泣库的门。")
return 'laser_weapon_armory'
else:
print("请你遵循游戏规则,正确输入选择。")
return 'central_corridor'
class LaserWeaponArmory(Scene):
def enter(self):
print("你以动作翻滚的方式进入武器库,蹲下,扫视房间,")
print("死一般的寂静,太安静了,可能有更多的哥顿人隐藏起来。")
print("你站起来,跑到房间的另一边,发现中子弹在一个盒子里,")
print("盒子上有个键盘锁,你需要密码把炸弹取出来。")
print("如果你错了10次,锁就会永远关闭,你就得不到炸弹了。密码是3位数字。")
code = "%d%d%d" % (randint(1, 9), randint(1, 9), randint(1, 9))
guess = input("[keypad]> ")
guesses = 1
while guess != code and guesses < 10:
print("嗡嗡嗡!")
guesses += 1
print("秘籍:", code) # 秘籍
guess = input("[keypad]> ")
if guess == code:
print("容器咔哒一声打开,密封就破裂了,气体就出来了。")
print("你抓起中子弹,以最快的速度跑到主控舱,你必须把它放在正确的位置上。")
return 'the_bridge'
else:
print("锁最后一次发出嗡嗡声,然后你听到一种令你心悸的熔化声,因为机械装置被熔在一起了。")
print("你此时惊在慌无措,四目张望。")
print("最后哥特人离开了这艘飞船,并把船给炸了。")
print("你的任务失败了。")
return 'death'
class TheBridge(Scene):
def enter(self):
print("你胳膊下夹着中子毁灭炸弹冲进主控室,让试图控制这艘船的5个哥顿人大吃一惊。")
print("他们每个人都有比上一个哥顿人更丑的小丑服装。")
print("他们还没把武器拿出来,因为他们看到你腋下的炸弹,不想引爆。")
print("现在这个局面,你有两个选择:")
print("A.扔炸弹 B.慢慢地放置炸弹")
action = input("> ")
if action == "A" or action == "a":
print("在恐慌中,你向哥顿人扔了炸弹,然后朝门跳去。")
print("就在你掉下去的时候,一个哥顿人从背后朝你开枪打死了你。")
print("当你死的时候,你看到另一个哥顿人疯狂地试图解除炸弹。")
print("当你死的时候,你可能知道它们会爆炸。")
print("你死了。")
return 'death'
elif action == "B" or action == "b":
print("你把你的爆能枪对准你腋下的炸弹。")
print("哥特人举起手来,开始微微出汗。")
print("你一寸一寸地退到门口,打开它,然后小心地把炸弹放在地板上,用你的爆震器对准它。")
print("然后你从门里跳回去,按下关闭按钮,然后把锁炸开,这样哥特一家就出不去了")
print("既然炸弹已经放好了,你就跑到逃生舱,从这个易拉罐上下来。")
return 'escape_pod'
else:
print("请你遵循游戏规则,正确输入选择。")
return 'the_bridge'
class EscapePod(Scene):
def enter(self):
print("你拼命地冲过飞船,想在整个飞船爆炸前赶到逃生舱。")
print("看来船上几乎没有哥顿人,所以你的计划没有任何干扰。")
print("你进入逃生舱房间,现在需要选择一个逃生舱。")
print("其中四个可能会被损坏,但是你没有时间去看。")
print("有五个逃生舱,你选择哪一个?")
good_pod = randint(1, 5)
print("秘籍:", good_pod) # 秘籍
guess = input("[pod]> ")
if int(guess) != good_pod:
print("你跳进了%s号逃生舱并按下了弹出按钮。" % guess)
print("逃生舱逃到太空中,然后随着船体的破裂而爆裂,把你的身体压碎成火腿三明治")
print("很可惜,就差那么一点了,你死了。")
return 'death'
else:
print("")
print("逃生舱很轻易地滑进太空,飞向下面的行星。")
print("当它飞向这颗行星时,你回头看,看到那艘飞船爆炸了,")
print("像一颗明亮的恒星一样,同时摧毁了哥特人的飞船。")
print("恭喜你,你成功地逃生出来了。")
return 'finished'
# Map是一个总类
class Map(object):
scenes = {
'central_corridor': CentralCorridor(),
'laser_weapon_armory': LaserWeaponArmory(),
'the_bridge': TheBridge(),
'escape_pod': EscapePod(),
'death': Death()
}
def __init__(self, start_scene):
(self.start_scene) = start_scene
def opening_scene(self):
return self.next_scene(self.start_scene)
def next_scene(self, scene_name):
return (Map.scenes).get(scene_name)
a_map = Map('central_corridor')
a_game = Engine(a_map)
a_game.play()
按照惯例,先试一遍通关的方式。
输出结果(加粗的地方是输入的数据):
-----------
来自Percal25号行星的哥顿人已经入侵并消灭了你的飞船上全体船员,
你是最后一个幸存的成员。
你的最后一个任务是从激光武器库中取出中子毁灭弹,
将它放在主控舱,然后进入逃生舱后将飞船炸了。
你正沿着中央走廊往武库跑的时候,
一只长着红色鳞片皮肤的哥顿人跳了出来。黑暗肮脏的牙齿,邪恶的小丑服装在他庞大的身体似乎要撑爆开来,
它挡着激光武器库的门正要拔出武器来攻击你。
这时候,你有三种选择:
A.射击! B.闪避! C.讲个笑话!
你的选择是:C
幸运的是,你在学院里正好学习了哥顿人的语言。
你讲了一个你所知道的哥顿人的笑话。
Lbhe zbgure vf fb sng,jura fur fvgf nebhaq gur ubhfr,fur fvgf nrbhaq gur ubhfr。
哥顿人听完之后僵硬地停了下来,试图不笑。随后就爆发出大笑,笑得动弹不得。
当它笑的时候,你跑过去给它的脑袋开了一枪,然后进去了激光哭泣库的门。
-----------
你以动作翻滚的方式进入武器库,蹲下,扫视房间,
死一般的寂静,太安静了,可能有更多的哥顿人隐藏起来。
你站起来,跑到房间的另一边,发现中子弹在一个盒子里,
盒子上有个键盘锁,你需要密码把炸弹取出来。
如果你错了10次,锁就会永远关闭,你就得不到炸弹了。密码是3位数字。
[keypad]> 666
嗡嗡嗡!
秘籍: 763
[keypad]> 763
容器咔哒一声打开,密封就破裂了,气体就出来了。
你抓起中子弹,以最快的速度跑到主控舱,你必须把它放在正确的位置上。
-----------
你胳膊下夹着中子毁灭炸弹冲进主控室,让试图控制这艘船的5个哥顿人大吃一惊。
他们每个人都有比上一个哥顿人更丑的小丑服装。
他们还没把武器拿出来,因为他们看到你腋下的炸弹,不想引爆。
现在这个局面,你有两个选择:
A.扔炸弹 B.慢慢地放置炸弹
> B
你把你的爆能枪对准你腋下的炸弹。
哥特人举起手来,开始微微出汗。
你一寸一寸地退到门口,打开它,然后小心地把炸弹放在地板上,用你的爆震器对准它。
然后你从门里跳回去,按下关闭按钮,然后把锁炸开,这样哥特一家就出不去了
既然炸弹已经放好了,你就跑到逃生舱,从这个易拉罐上下来。
-----------
你拼命地冲过飞船,想在整个飞船爆炸前赶到逃生舱。
看来船上几乎没有哥顿人,所以你的计划没有任何干扰。
你进入逃生舱房间,现在需要选择一个逃生舱。
其中四个可能会被损坏,但是你没有时间去看。
有五个逃生舱,你选择哪一个?
秘籍: 3
[pod]> 3
逃生舱很轻易地滑进太空,飞向下面的行星。
当它飞向这颗行星时,你回头看,看到那艘飞船爆炸了,
像一颗明亮的恒星一样,同时摧毁了哥特人的飞船。
恭喜你,你成功地逃生出来了。
-----------
接下来,我将详细地讲解一下这段游戏的代码,首先讲解一下它们的三个结构:
①两个引用的模块:
from sys import exit
from random import randint
第一行是从sys模块中引入exit,想必这个大家都知道,这个功能可以退出当前程序,象征游戏结束。这是类Death的必备帮手。
第二行大家有点陌生,但是这个从random模块中引用出来的randint功能广泛应用,因为这是一个随机的函数,能在一组数据中随机出一个数据。
②八个类:
class Scene(object):
class Engine(object):
class Death(Scene):
class CentralCorridor(Scene):
class LaserWeaponArmory(Scene):
class TheBridge(Scene):
class EscapePod(Scene):
class Map(object):
看得出来的是,有三个是父类,分别是Scene,Engine和Map,剩下的五个类都是Scene的子类,因为它们都是游戏中的场景。
③游戏启动的部分:
a_map = Map('central_corridor')
a_game = Engine(a_map)
a_game.play()
游戏的起点是中央走廊,此时中央走廊是一个变量,是类Map的一个实例。
这时候创建一个a_map变量,作为存储游戏的起点的变量。
将a_map变量传入类Engine,也就是引擎的地方,a_game成为引擎的一个实例。
最后a_game访问类Engine的play函数。
这个时候,游戏就启动了。
那么三个结构讲完了,大家对游戏的整体结构有了一个大概的了解,接下来我将逐步讲解代码。
首先来详细讲解一下类Map的运行:
class Map(object):
scenes={
'central_corridor':CentralCorridor(),
'laser_weapon_armory':LaserWeaponArmory(),
'the_bridge':TheBridge(),
'escape_pod':EscapePod(),
'death':Death()
}
def __init__(self,start_scene):
(self.start_scene)=start_scene
def opening_scene(self):
return self.next_scene(self.start_scene)
def next_scene(self,scene_name):
return (Map.scenes).get(scene_name)
a_map = Map('central_corridor')
a_game = Engine(a_map)
a_game.play()
a_map = Map('central_corridor')
首先,向类Map传递一个'central_corridor'变量信息,你可以发现,列表scenes中的‘central_corridor'是对应着CentralCorridor(),但是不着急,
我们先用这个'central_corridor'变量逐行分析以下这个类Map的运行过程:
'central_corridor'传递给类Map中__init__函数中的start_scene,然后就类Map已经录进去了'central_corridor'这个信息,这个行为就成了一个实例,这个实例叫a_map。
这个过程就到此结束了,是的,按照文中的代码确实是这个过程,但是为了方便你理解接下来的类Engine的函数运行方式,我将假设继续运行下去。
然后'central_corridor'就来到了第二个opening_scene函数,同样传递给了self.start_scene,这个时候就被return返回next_scence,也就是返回给下一个函数。
于是,'central_corridor'又来到了下一层函数,被传递给scene_name,这个时候(Map.scenes).get()发挥了它的奇妙的作用,还记得上一篇的博客我讲了列表的get()吗?
没错,这个时候的运行过程就变成了(Map.scenes).get('central_corridor'),然后列表scenes确实有central_corridor'是对应着CentralCorridor(),答案就很明显了:
return (Map.scenes).get(scene_name) 等于 return CentralCorridor()
所以,游戏的起点就这样开始了,进入了中央走廊。
但是,按照文中的代码逻辑,实际运行的只有上面加粗的那一段话,我为什么下面继续假设运行,是为了方便你理解接下来的类Engine。
class Engine(object):
def __init__(self,scene_map):
(self.scene_map)=scene_map
def play(self):
current_scene = (self.scene_map).opening_scene()
while True:
print("
-----------")
next_scene_name = (current_scene).enter()
current_scene = (self.scene_map).next_scene(next_scene_name)
a_game=Engine(a_map)
a_game.play()
我们把类Map的a_map实例传入类Engine中,然后通过__init__函数中的scene_map存储起来,这个行为也成了一个实例,这个实例叫a_game。
注意,a_map是类Map的一个实例,a_game是类Engine的一个实例。
然后a_game这个实例访问类Engine中的play函数,这个时候,游戏就开始运行了,这儿也同时涉及到房间切换的原理。我们来分析一下play函数。
这个时候,self.scene_map是存储着a_map实例,a_map实例中含有'central_corridor'信息,然后将这个self.scene_map进行类Map的opening_scene函数处理,
当然,这个行为存储到了current_scene变量之中,于是这个变量就进入了while True无限循环之中:
首先打印一个换行符和一串分割线:print("
-----------"),
再定义一个next_scene_name变量,这个变量存储着对current_scene进行enter的处理,那么我们来对这句话改变一下思路,
保留开始和结尾,去掉中间的传递过程,next_scene_name这个变量是不是就等同于刚开始的存储'central_corridor'的实例a_map?
于是current_scene =(self.scene_map).next_scene(next_scene_name) 可以简化为:
current_scene=a_map.next_scene(a_map)=return (Map.scenes).get(a_map)=return CentralCorridor()
于是,游戏就以CentralCorridor()的开场启动了。那么游戏开始了,房间切换是如何运作的呢?
我们来看看第一次return ,干脆点,就是一上来就死的那种,return 'death'。
当代码运行到return 'death'的时候,其实你可以大胆的想象一下,这段游戏的开端是哪一个点?
是不是就个存储着'central_corridor'的实例a_map?
现在你return 'death',这时候a_map存储的信息是不是就被返回成了'death'?
于是,这个存储着'death'的a_map就进入了while True的循环,重复上面的动作,房间的切换就完成了。
我很清楚地知道我的理解可能是存在有误的,至少我是这么理解的,看上去还能说得通,当你阅读上面的时候,你如果清楚地知道正确的切换房间原理,
就保持你脑中的那个概念吧,跳过我这段话,方便的话你还可以给我留言,嘿嘿嘿。
好了,接下来我们来看看类Death的函数吧,看看这个游戏的死亡是怎么被定义的。
class Death(Scene):
quips = [
"你挂了。你这方面有点烂。",
"如果你妈妈更聪明点……她会为你骄傲的。",
"这样的一个失败者……",
"我有一只小狗,它更擅长这个。"
]
def enter(self):
print(Death.quips[randint(0,len(self.quips)-1)])
exit(1)
有意思的是,这儿利用到了一个四条消息的列表和随机函数randint,想必聪明的你们可能知道了,
这是一个游戏失败之后将从四句嘲讽的话语中随机打印出一条的死亡函数。
那么我先简单讲一下randint吧,设一个变量a=randint(1,9),则print(a)。
很显然,a的结果可能有九种,你也预测不到是哪一个,这是系统随机出来的数字。
那么问题来了,为什么这段代码中是randint(0,len(self.quips)-1),而不是randint(1,len(self.quips))呢?
不得不说,我们成功回到了索引的概念了,说实话我很讨厌详细讲这些过于概念抽象的东西,因为这些对于本人也是一个很大的烦恼。
我们先来谈谈我简单举的那个例子吧,randint(1,9),这是从1到9中随机出一个数字来,这个时候括号内是目标的群体,直接从目标群体里面打印出一个数字。
那么randint(0,len(self.quips)-1),括号内是目标群体吗?不是的,括号内是目标群体的序号,也就是索引,那么下面还需要用我继续吗?
代表第一个索引的基数,在python中是不是0?
所以,在使用randint()随机函数的时候,时刻弄清楚括号内是直接的目标群体,还是目标群体的索引,索引是从0开始的。
那么,代码中的一些需要讲的部分,我都一一介绍完了,其他的部分你们应该也看得懂。
至于秘籍这部分,emm……不加秘籍,根本过不去猜三位数字密码的部分,而且你好不容易猜出来了,
后面还要经历5选1的逃生舱,到时候选错了心态可能就会爆炸……