[Toc]
# 第5章 墨哥哥和小墨的一天
现在是周五晚上,闲下来的墨哥哥教小墨做了一款角色扮演类游戏叫《小墨历险记》。在游戏中,主人公小墨拿着家传宝刀墨月刀历经千辛万苦,最终击败boss夺回了墨家村。这样的游戏是如何一步一步做出来的呢?一起来看看吧。
# [插画:小墨家,小墨和墨哥哥在聊天]
时间:周五晚上
地点:小墨家
## 5.1 墨哥哥的猜数字游戏
墨哥哥:小墨,Python学的怎么样了?
小墨:墨哥哥,最近学习了分支结构、循环结构,你看,我还做了个猜数字游戏呢?
墨哥哥:哦,是吗?我来玩玩:
```
输入猜的数字:50
小了
输入猜的数字:75
小了
输入猜的数字:88
小了
输入猜的数字:94
小了
输入猜的数字:97
大了
输入猜的数字:95
猜对了
```
小墨:墨哥哥你和博士猜的一样快。嗯……我看看,我好想发现什么规律了。每次都猜总数的中间数字对不对?
墨哥哥:真是聪明的小墨。这个过程就好比给你了一本花名册,里面有很多名字,名字按照拼音的首字母排序,但是这本书没有目录,现在让你找出张三在第几页,你当然可以一页一页的翻找,但是太麻烦了。如何才能更快的找出张三来呢?
# [插画:一本正在翻页的书,像下图:]
![](https://img.kancloud.cn/80/71/8071b765c5ed9949b4f38b779504b5a4_1200x720.png)
因为所有名字都是按照拼音的首字母排好序的,我们可以先翻到最中间的一页,看中间的一页中的字母是在张三名字首字母“Z”的前面还是后面,如果是前面,就说明“Z”在本书的后半部分,这时候前半部分就不需要再管了,工作量一下子少了一半,如果是在“Z”的后面道理也一样。
在剩下的一半里,我们还是直接翻到中间的一页,看这一页中的字母是在“Z”的前面还是后面,从而再“淘汰”一半。
如此反复,用不了几次,张三就被我们找到了。
小墨:哇哦,这个好这个好。
墨哥哥:这种查找方法称为“二分查找”或“折半查找”。
小墨:嗯我记住了。对了墨哥哥,今天你不忙了?
墨哥哥:嗯奉墨妈妈之命,今天啊,专门用来教你学Python。
小墨:太好啦!那墨哥哥,如果让你做猜数字游戏,你会怎么做呢?
墨哥哥:好游戏的一个关键元素是游戏的难度等级,如果一个游戏太容易或太难游戏乐趣都会大打折扣。基于这点,可以限定猜数字的次数,比如6次,从而加大点难度。
同时可以设计一个场景把猜数字的游戏嵌入进去,比如侏罗纪时代,你被一只聪明的恐龙骗到了一个废弃的实验室里,这时候你发现了一条离开的暗门,但是门紧锁着。你需要输入正确的密码才能打开这道门,密码是随机生成好的,你一共有6次机会,6次都输入错误则门永远也打不开了,此时恐龙也在一步步的接近你……
# [插画:上述侏罗纪场景]
或者是战争场景,你需要去拆一颗炸弹,炸弹的规则依然是猜数字,同样你只有6次机会。
# [插画:上述拆炸弹场景]
小墨:说的一下子好紧张。
墨哥哥:这就是氛围的重要性,一个好游戏就像一部好电影一样,能创造某种氛围把你吸引进去。
所以,我的猜数字游戏是这样的:
```
import random
print('侏罗纪时代……')
print('天空下着大雨……')
print('你被恐龙骗到了一间废弃的实验室中,这时候你发现了一道暗门')
print('门上字迹斑驳,依稀可辩:猜数字游戏,请输入0-100内的数字,如果猜对门会自动打开,注意你只有6次机会。')
n = random.randint(0, 100)
# 定义猜的总次数
count = 6
while True:
ni = int(input('输入猜的数字:'))
# -= 和+=一样,是一个操作符,读作“减等”,先减再赋值,相当于count = count - 1
# 每次猜,次数都减去1
count -= 1
# 如果没有次数了,则游戏结束
if count == 0:
print('你身后传来了恐龙沉重的呼吸声……')
print('游戏结束')
break
if ni > n:
print('猜大了,')
print('你还剩%s次机会,恐龙的脚步声更近了……' % count)
continue
elif ni < n:
print('猜小了')
print('你还剩%s次机会,恐龙的脚步声更近了……' % count)
continue
else:
print('猜对了,一阵白光,你逃离了侏罗纪世界!')
break
```
小墨:哇!还能这样,我来玩一把:
```
侏罗纪时代……
天空下着大雨……
你被恐龙骗到了一间废弃的实验室中,这时候你发现了一道暗门
门上字迹斑驳,依稀可辩:猜数字游戏,请输入0-100内的数字,如果猜对门会自动打开,注意你只有6次机会。
输入猜的数字:50
猜大了,
你还剩5次机会,恐龙的脚步声更近了……
输入猜的数字:25
猜大了,
你还剩4次机会,恐龙的脚步声更近了……
输入猜的数字:13
猜小了
你还剩3次机会,恐龙的脚步声更近了……
输入猜的数字:19
猜大了,
你还剩2次机会,恐龙的脚步声更近了……
输入猜的数字:16
猜大了,
你还剩1次机会,恐龙的脚步声更近了……
输入猜的数字:14
你身后传来了恐龙沉重的呼吸声……
游戏结束
```
啊!吓的我二分查找都不知道该怎么去计算了。
墨哥哥:这样是不是就比单纯的猜数字有意思多了?
小墨:嗯我还从来没想过程序还能这样编写,就像写小说一样。
墨哥哥:这种一般称为文字游戏,因为你现在还没有学习编程中的界面部分,所以可以创作这样的文字游戏来巩固所学的内容。
小墨:那墨哥哥再教我做一款文字游戏吧。
## 5.2 墨哥哥的文字游戏
墨哥哥:到目前为止,虽然你学习的内容还不多,但是已经可以做出很多东西来了。
比如,可以开发一款RPG(角色扮演游戏),有主人公,有大boss,有凶险的搏斗,甚至可以有震撼宏大的故事场景,可歌可泣的英雄故事等。
# [插画:类似下图,RPG角色扮演游戏场景]
![](http://7xtc8l.com1.z0.glb.clouddn.com/timg.jpg)
做游戏之前呢,可以先给游戏起个名字,就叫《小墨历险记》好了。
最终效果就像这样:
```
欢迎来到墨家村,如今是妖兽的地盘
历经九死一生,你来到了boss狂风的老巢
狂风血量: 100 ,准备开始战斗!!
快输入数字1攻击他!1
你击中了狂风,打出了 20 点的伤害,狂风剩余血量 80
愤怒的狂风发起了反击,对你造成了17点伤害,你当前剩余血量83
快输入数字1攻击他!1
你击中了狂风,打出了 14 点的伤害,狂风剩余血量 66
愤怒的狂风发起了反击,对你造成了10点伤害,你当前剩余血量73
快输入数字1攻击他!1
你击中了狂风,打出了 11 点的伤害,狂风剩余血量 55
愤怒的狂风发起了反击,对你造成了15点伤害,你当前剩余血量58
快输入数字1攻击他!1
你击中了狂风,打出了 10 点的伤害,狂风剩余血量 45
愤怒的狂风发起了反击,对你造成了14点伤害,你当前剩余血量44
快输入数字1攻击他!1
你击中了狂风,打出了 12 点的伤害,狂风剩余血量 33
愤怒的狂风发起了反击,对你造成了18点伤害,你当前剩余血量26
快输入数字1攻击他!1
你击中了狂风,打出了 15 点的伤害,狂风剩余血量 18
愤怒的狂风发起了反击,对你造成了10点伤害,你当前剩余血量16
快输入数字1攻击他!1
你击中了狂风,打出了 16 点的伤害,狂风剩余血量 2
愤怒的狂风发起了反击,对你造成了17点伤害,你当前剩余血量-1
很遗憾,你未能完成冒险,请休息片刻重新开始。。。
```
小墨:这个我喜欢!
墨哥哥:好了,我们正式开始。
这个游戏呢,我们采用版本迭代的思路来做,先做最简单的一版,然后逐渐去丰富功能,先来看第一版。
### 5.2.1 小墨历险记1.0版
墨哥哥:第一版的策划如下:
> 故事背景:墨家村,一个偏远宁静的小村子。墨家世世代代生活在这里。到小墨出生那一年,已经整整一千年了。也就在这一年,村子里突然多了很多奇怪的人,到晚上更是妖风四起,邪魅横行。
小墨:继续说继续说,接下来怎么着了
墨哥哥:嘘~。
> 墨爸爸为了查出事情的真相,乔装打扮混进了陌生人的队伍,然而这一去竟杳无音信。一年之后,墨妈妈收到了一封信,上面就写了一句话:快走!越远越好!墨妈妈感觉到了事情的严重性,就带着小墨和附近的村民,在某天的拂晓时分悄悄离开了村子,走了不知道多久,再也走不动了,就在一僻静的山谷停了下来。
这一停就是十五年。这十五年间,小墨也长成了一个英俊的少年。
为了探寻父亲失踪的真相,为了带日思夜念墨家村的村民重新回到墨家村,在这一天的清晨,小墨带着家传宝刀墨月刀,告别墨妈妈,开始了自己的冒险之旅。
从记事起,这把墨月刀就一直陪着小墨。这几天不知道为什么,经常自己在寒夜中铿铿作响,小墨觉得墨月刀一定是感受到了什么,这也是他决定出来的原因。
小墨:墨哥哥你还真能编故事,这个小墨家还有祖传宝刀呢?
墨哥哥:哈哈,游戏背景嘛!根据这个我们能开发整部游戏了,不过今天我们来做的只是其中一个小场景:
> 小墨探听到,当年那些奇怪的人都是妖兽所化,现在整个墨家村方圆十里都被妖兽所占领,而妖兽的总部就在墨家村!
小墨千辛万苦,历经无数磨难,终于抵达了墨家村,见到了最后一个boss(游戏中的大怪物):狂风
。
现在游戏规则如下:它有10点血,你每打他一下,就减1点血,打他10下,他就被你打死了。
小墨:……,好无聊,就这,至于铺垫这么久嘛。
墨哥哥:我这是帮你打开下思路,让你从if啊,for啊这些语法中解脱出来。编程技术只是一方面,想法才是最重要的!
小墨:哦,好吧。
墨哥哥:好,开始编写我们的游戏。
首先使用输出语句交代故事的背景:
```
print("欢迎来到墨家村,如今是妖兽的地盘")
print("历经九死一生,你来到了boss狂风的老巢")
```
然后用一个变量表示boss的血量,boss的血量可能后面会多次用到,比如你打他之后,他的血量值要减少,所以我们使用变量表示:
```
hp_boss = 10
```
在python中,变量一般要全小写,如果由多个单词组成,则单词之间使用下划线_来连接,就比如上面的`hp_boss`,用它来表示boss的生命值,游戏中也叫血量。
小墨:这个博士曾经说过。说文件名也这样命名,另外就是见名知意。
墨哥哥:嗯,我们继续。打的动作,由键盘控制,其实就是一个输入,比如说输入数字1表示发起了攻击:
```
input_ni = int(input("快输入数字1攻击他!"))
```
因为在攻击的时候,按了键盘上的1,才表示发起了攻击,按其他键则不表示攻击,那么我们怎么知道按的是不是1呢?就需要把输入的内容和数字1做比较,这里input_ni就表示输入的内容,用它和数字1做比较就行了,因为是和数字比较,所以要先把输入的内容转为数字,这就是为什么上面的input外面要套一层int()。另外要注意,Python中的变量和数字的是否相当的比较,使用的是“==”
策划中,我们需要打10次才能把boss打死,对于重复性的工作,要想到用循环去解决,while循环和for循环都可以:
```
for i in range(10):
```
每打boss一下,boss血量就减少1,是hp_boss在减少:
```
hp_boss -= 1
```
最后,我们怎么知道boss是不是被打死了呢?只要判断boss的血量就可以了,如果血量小于等于0,这表示打死了boss:
```
if hp_boss == 0:
print("英雄,恭喜你,击败了boss狂风!")
```
好了,知识点就这么多,是不是全都学过呢?
小墨:嗯这些博士都已经教过我了。
墨哥哥:有了上述的这些内容,你能把整个游戏串起来了吗?
小墨:这个你都做得差不多了,串起来太简单了:
```
print("欢迎来到墨家村,如今是妖兽的地盘")
print("历经九死一生,你来到了boss狂风的老巢")
hp_boss = 10
print("狂风血量:", hp_boss, ",准备开始战斗!!")
for i in range(10):
input_ni = int(input("快输入数字1攻击他!"))
if input_ni == 1:
hp_boss -= 1
print("你击中了狂风,狂风剩余血量", hp_boss)
if hp_boss == 0:
print("小墨,恭喜你,击败了狂风!")
```
程序运行结果:
```
欢迎来到墨家村,如今是妖兽的地盘
历经九死一生,你来到了boss狂风的老巢
狂风血量: 10 ,准备开始战斗!!
快输入数字1攻击他!1
你击中了狂风,狂风剩余血量 9
快输入数字1攻击他!1
你击中了狂风,狂风剩余血量 8
快输入数字1攻击他!1
你击中了狂风,狂风剩余血量 7
快输入数字1攻击他!1
你击中了狂风,狂风剩余血量 6
快输入数字1攻击他!1
你击中了狂风,狂风剩余血量 5
快输入数字1攻击他!1
你击中了狂风,狂风剩余血量 4
快输入数字1攻击他!1
你击中了狂风,狂风剩余血量 3
快输入数字1攻击他!1
你击中了狂风,狂风剩余血量 2
快输入数字1攻击他!1
你击中了狂风,狂风剩余血量 1
快输入数字1攻击他!1
你击中了狂风,狂风剩余血量 0
小墨,恭喜你,击败了狂风!
```
### 5.2.2 小墨历险记2.0版
墨哥哥:接着呢,我们修复一个小bug,让程序逻辑更严谨,就是当玩家输入的不是1的时候,提醒他输入数字1才能攻击boss,这次呢我要求你采用while循环来改进,这个作为小墨历险记的2.0版本。
小墨:那就需要定义一个循环变量控制循环10次。然后再if分支后加上else分支,用于提示输入不是数字1的情况:
```
print("欢迎来到墨家村,如今是妖兽的地盘")
print("历经九死一生,你来到了boss狂风的老巢")
hp_boss = 10
print("狂风血量:", hp_boss, ",准备开始战斗!!")
i = 0
while i < 10:
input_ni = int(input("快输入数字1攻击他!"))
if input_ni == 1:
i += 1
hp_boss -= 1
print("你击中了狂风,狂风剩余血量", hp_boss)
if hp_boss == 0:
print("小墨,恭喜你,击败了狂风!")
else:
print("请使用数字键1攻击!")
```
程序运行结果如下:
```
欢迎来到墨家村,如今是妖兽的地盘
历经九死一生,你来到了boss狂风的老巢
狂风血量: 10 ,准备开始战斗!!
快输入数字1攻击他!1
你击中了狂风,狂风剩余血量 9
快输入数字1攻击他!2
请使用数字键1攻击!
快输入数字1攻击他!3
请使用数字键1攻击!
快输入数字1攻击他!4
请使用数字键1攻击!
快输入数字1攻击他!1
你击中了狂风,狂风剩余血量 8
快输入数字1攻击他!1
你击中了狂风,狂风剩余血量 7
快输入数字1攻击他!1
你击中了狂风,狂风剩余血量 6
快输入数字1攻击他!1
你击中了狂风,狂风剩余血量 5
快输入数字1攻击他!1
你击中了狂风,狂风剩余血量 4
快输入数字1攻击他!1
你击中了狂风,狂风剩余血量 3
快输入数字1攻击他!1
你击中了狂风,狂风剩余血量 2
快输入数字1攻击他!1
你击中了狂风,狂风剩余血量 1
快输入数字1攻击他!1
你击中了狂风,狂风剩余血量 0
小墨,恭喜你,击败了狂风!
```
### 5.2.3 小墨历险记3.0版
墨哥哥:现在再升级一版,做小墨历险记的3.0版本。在3.0版本中,我们加入随机攻击功能。增加游戏的不确定性,也就增加了很多的乐趣,锻炼一下你刚学习过的random模块。
小墨:random模块的使用时这样的:
```
import random
# 表示玩家打出的随机伤害,伤害值为3-5之间的数,包括3和5
attack_player = random.randint(3, 5)
```
因为是随机攻击,我们就没办法判断事先需要几次才能打死boss,这就是一个典型的不定次数的循环,可以使用死循环+break解决。
所以,小墨历险记3.0版本改版如下:
```
import random
print("欢迎来到墨家村,如今是妖兽的地盘")
print("历经九死一生,你来到了boss狂风的老巢")
hp_boss = 10
print("狂风血量:", hp_boss, ",准备开始战斗!!")
while True:
input_ni = int(input("快输入数字1攻击他!"))
if input_ni == 1:
# 表示玩家打出的随机伤害,伤害值为3-5之间的数,包括3和5
attack_player = random.randint(3, 5)
hp_boss -= attack_player # boss血量根据随机攻击值扣除
print("你击中了狂风,打出了", attack_player, "的伤害,狂风剩余血量", hp_boss)
if hp_boss <= 0:
print("小墨,恭喜你,击败了狂风!")
break # 结束死循环
else:
print("请使用数字键1攻击!")
```
程序运行效果如下:
```
欢迎来到墨家村,如今是妖兽的地盘
历经九死一生,你来到了boss狂风的老巢
狂风血量: 10 ,准备开始战斗!!
快输入数字1攻击他!1
你击中了狂风,打出了 3 的伤害,狂风剩余血量 7
快输入数字1攻击他!1
你击中了狂风,打出了 3 的伤害,狂风剩余血量 4
快输入数字1攻击他!1
你击中了狂风,打出了 4 的伤害,狂风剩余血量 0
小墨,恭喜你,击败了狂风!
```
### 5.2.4 小墨历险记4.0版
墨哥哥:以上的三个版本,无论怎么打,boss狂风都是死路一条,和上面猜数字游戏一样,如果玩家也随时会死掉,那么游戏就会刺激很多,所以第4个版本你可以加入狂风的反击,boss和小墨各给100血量,两人你打我一下,我打你一下这种回合制攻击,看谁能笑到最后。小墨,你再来实现下吧。
小墨:so easy~
```
import random
print("欢迎来到墨家村,如今是妖兽的地盘")
print("历经九死一生,你来到了boss狂风的老巢")
hp_boss = 100
hp_player = 100
print("狂风血量:", hp_boss, ",准备开始战斗!!")
while True:
input_ni = int(input("快输入数字1攻击他!"))
if input_ni == 1:
# 玩家的随机攻击伤害值
attack_player = random.randint(10, 20)
# boss扣血
hp_boss -= attack_player
print("你击中了狂风,打出了", attack_player, "点的伤害,狂风剩余血量", hp_boss)
if hp_boss > 0: # 判断boss是否已死,血量大于0说明还活着,活着就会反击
# boss的随机反击伤害值
attack_boss = random.randint(10, 20)
# 玩家扣血
hp_player -= attack_boss
print("愤怒的狂风发起了反击,对你造成了%s点伤害,你当前剩余血量%s" % (attack_boss, hp_player))
if hp_player <= 0: # 判断玩家是否已死
print("很遗憾,你未能完成冒险,请休息片刻重新开始。。。")
break
else:
print("小墨,恭喜你,击败了狂风!")
break
else:
print("请使用数字键1攻击!")
```
程序运行效果如下:
```
欢迎来到墨家村,如今是妖兽的地盘
历经九死一生,你来到了boss狂风的老巢
狂风血量: 100 ,准备开始战斗!!
快输入数字1攻击他!1
你击中了狂风,打出了 11 点的伤害,狂风剩余血量 89
愤怒的狂风发起了反击,对你造成了15点伤害,你当前剩余血量85
快输入数字1攻击他!1
你击中了狂风,打出了 11 点的伤害,狂风剩余血量 78
愤怒的狂风发起了反击,对你造成了18点伤害,你当前剩余血量67
快输入数字1攻击他!1
你击中了狂风,打出了 17 点的伤害,狂风剩余血量 61
愤怒的狂风发起了反击,对你造成了15点伤害,你当前剩余血量52
快输入数字1攻击他!1
你击中了狂风,打出了 20 点的伤害,狂风剩余血量 41
愤怒的狂风发起了反击,对你造成了12点伤害,你当前剩余血量40
快输入数字1攻击他!1
你击中了狂风,打出了 10 点的伤害,狂风剩余血量 31
愤怒的狂风发起了反击,对你造成了17点伤害,你当前剩余血量23
快输入数字1攻击他!1
你击中了狂风,打出了 19 点的伤害,狂风剩余血量 12
愤怒的狂风发起了反击,对你造成了12点伤害,你当前剩余血量11
快输入数字1攻击他!1
你击中了狂风,打出了 11 点的伤害,狂风剩余血量 1
愤怒的狂风发起了反击,对你造成了10点伤害,你当前剩余血量1
快输入数字1攻击他!1
你击中了狂风,打出了 17 点的伤害,狂风剩余血量 -16
小墨,恭喜你,击败了狂风!
```
哥哥,这个游戏我玩了好几把,最后都是我把boss打死了,是不是不平衡呀?
墨哥哥:玩家和boss的伤害都是10到20的随机,玩家的优势就在于先发优势。你也可以加大游戏的难度,比如增大boss的随机反击值,置玩家于危险之中。
小墨:嗯好。这样应该就好玩多了。
墨哥哥:嗯。好了小墨,时候也不早了,赶紧收拾下电脑上床休息了。休息好才能学习好。
小墨:好的墨哥哥,晚安啦。
## 5.3 本章小结
墨哥哥总结:本章属于前面章节的综合运用,可以看到只要你愿意动脑去想,愿意动手去做,就能做出很多有趣的内容来。