[Toc]
# 第5章 墨哥哥和小墨的一天
現在是周五晚上,閑下來的墨哥哥教小墨做了一款角色扮演類游戲叫《小墨歷險記》。在游戲中,主人公小墨拿著家傳寶刀墨月刀歷經千辛萬苦,最終擊敗boss奪回了墨家村。這樣的游戲是如何一步一步做出來的呢?一起來看看吧。
# [插畫:小墨家,小墨和墨哥哥在聊天]
時間:周五晚上
地點:小墨家
## 5.1 墨哥哥的猜數字游戲
墨哥哥:小墨,Python學的怎么樣了?
小墨:墨哥哥,最近學習了分支結構、循環結構,你看,我還做了個猜數字游戲呢?
墨哥哥:哦,是嗎?我來玩玩:
```
輸入猜的數字:50
小了
輸入猜的數字:75
小了
輸入猜的數字:88
小了
輸入猜的數字:94
小了
輸入猜的數字:97
大了
輸入猜的數字:95
猜對了
```
小墨:墨哥哥你和博士猜的一樣快。嗯……我看看,我好想發現什么規律了。每次都猜總數的中間數字對不對?
墨哥哥:真是聰明的小墨。這個過程就好比給你了一本花名冊,里面有很多名字,名字按照拼音的首字母排序,但是這本書沒有目錄,現在讓你找出張三在第幾頁,你當然可以一頁一頁的翻找,但是太麻煩了。如何才能更快的找出張三來呢?
# [插畫:一本正在翻頁的書,像下圖:]

因為所有名字都是按照拼音的首字母排好序的,我們可以先翻到最中間的一頁,看中間的一頁中的字母是在張三名字首字母“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角色扮演游戲場景]

做游戲之前呢,可以先給游戲起個名字,就叫《小墨歷險記》好了。
最終效果就像這樣:
```
歡迎來到墨家村,如今是妖獸的地盤
歷經九死一生,你來到了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 本章小結
墨哥哥總結:本章屬于前面章節的綜合運用,可以看到只要你愿意動腦去想,愿意動手去做,就能做出很多有趣的內容來。