在這節(jié)練習(xí),我想給你介紹一個(gè)使用python創(chuàng)建某類東西的過程,也就是“面向?qū)ο缶幊獭保∣OP)。我把它叫做一個(gè)過程,是因?yàn)槲覍⒔o出一系列按順序進(jìn)行的步驟,但是你也不應(yīng)該死板的遵循這個(gè)步驟,企圖用它解決所有難題。它們對(duì)于許多編程問題只是一個(gè)良好的開頭,而不應(yīng)該被認(rèn)為是解決這些問題的唯一方法。這個(gè)過程只是一個(gè)你可以遵循的方法:
- 寫出或畫出你的問題
- 從1中提煉關(guān)鍵問題并搜索相關(guān)資料
- 為2中的問題創(chuàng)建一個(gè)有層次結(jié)構(gòu)的類和對(duì)象映射
- 編寫類和測試代碼,并保證他們運(yùn)行
- 重復(fù)并精煉
按照這個(gè)順序執(zhí)行流程,叫做“自頂向下”的方式,意思是說,它從非常抽象寬松的想法開始,然后慢慢提煉,直到想法是堅(jiān)實(shí)的東西,然后你再開始編碼。
首先我只是寫出這個(gè)問題,并試圖想出任何我想要的東西就可以了。也許我會(huì)畫一兩個(gè)圖表,或者某種可能的地圖,甚至給自己寫了一系列的電子郵件描述這個(gè)問題。這給了我表達(dá)了問題的關(guān)鍵概念的方法,并探測出我對(duì)于這個(gè)游戲有什么具體的想法和了解。
接下來,我瀏覽這些筆記,圖表以及描述,通過這些記錄我找到我需要的關(guān)鍵問題點(diǎn)。有一個(gè)簡單的技巧:簡單地列出你的筆記和圖表中所有的名詞和動(dòng)詞,然后寫出他們是如何相關(guān)的。這一步其實(shí)也我為我下一步要寫的類、對(duì)象、以及函數(shù)等提供了命名列表。我利用這個(gè)概念列表,研究任何我不明白的地方,如果我需要,我還可以進(jìn)一步完善它們。
一旦我完成這個(gè)列表,我可以創(chuàng)建的一個(gè)簡單的輪廓/樹用來說明這些概念之間的關(guān)系。你還可以對(duì)著你的名詞列表并詢問“這個(gè)名詞和其他的是一個(gè)概念嗎?或者它們有一個(gè)通用的父類嗎,那它們的父類是什么?”持續(xù)檢查,直到你得到一個(gè)有層次結(jié)構(gòu)的類,它應(yīng)該像一棵簡單的樹或者圖表。然后檢查你的動(dòng)詞列表,看它們是否可以作為函數(shù)的名字添加到你的類樹種。
隨著這棵類樹的生成,我坐下來寫一些基本的框架代碼,代碼中只包括剛才提到的類,類中包含的函數(shù)。然后我再寫一個(gè)測試用例,用來檢驗(yàn)我剛才寫的類是正確的。有時(shí)候我可能只需要在開始寫一段測試代碼,但是更多的時(shí)候,我需要寫一段測試,再寫一段代碼,再寫一段測試...直到整個(gè)項(xiàng)目完成。
最后,在我完成更多的工作之前,我周期性的重復(fù)和精煉這個(gè)流程,是他變得清楚和易于理解。如果我在一個(gè)沒有預(yù)料到的地方被一些概念或問題卡住,我會(huì)停下來在這一小部分上投入更大的精力來分析解決,直到我解決了這問題,再繼續(xù)下去。
這節(jié)練習(xí)中,我將通過建造一個(gè)游戲引擎帶大家學(xué)習(xí)這一流程。
我打算制作一個(gè)叫做 "Gothons from Planet Percal #25"的游戲,這個(gè)一個(gè)小型的太空冒險(xiǎn)游戲。
我為這個(gè)游戲?qū)懥艘恍《蚊枋觯?/p>
“外星人乘坐一個(gè)宇宙飛船入侵,我們的英雄需要通過一個(gè)迷宮似的房間擊敗他們,然后他才能逃入一個(gè)逃生艙到達(dá)下面的行星。游戲?qū)⒏褚粋€(gè)有著文本輸出和有趣的死法的Zork或冒險(xiǎn)類型游戲。游戲?qū)ㄒ粋€(gè)引擎運(yùn)行充滿房間或場景的地圖。 當(dāng)玩家進(jìn)入房間,每個(gè)房間將打印自己的描述,然后告訴引擎運(yùn)行下一個(gè)地圖?!?/p>
我先描述每個(gè)場景:
Death: 這是玩家死亡的場景,應(yīng)該是有趣的。
Central Corridor: 這是游戲的起點(diǎn),在這里已經(jīng)有一個(gè)外星人等待英雄的到來,它們需要被一個(gè)玩笑打敗。
Laser Weapon Armory: 這是英雄得到了中子彈獲得的逃生艙之前要炸毀的船。它有一個(gè)需要英雄猜數(shù)的鍵盤。
The Bridge: 和外星人戰(zhàn)斗的另一個(gè)場景,英雄防止炸彈的地方。
Escape Pod: 英雄逃脫的場景,但是需要英雄找對(duì)正確的逃生艙
現(xiàn)在,我可以繪制出它們的地圖,或者對(duì)每個(gè)房間再多寫一些描述,或者其他一些我腦中出現(xiàn)的想法。
現(xiàn)在我已經(jīng)有足夠多的信息還提取出名詞,并分析它們的類結(jié)構(gòu)。首先我列出所有的名詞:
- Alien
- Player
- Ship
- Maze
- Room
- Scene
- Gothon
- Escape Pod
- Planet
- Map
- Engine
- Death
- Central Corridor
- Laser Weapon Armory
- The Bridge
我也可能要列出所有的動(dòng)詞,看它們能否作為函數(shù)的名字,不過現(xiàn)在我要先跳過這一步。
你需要研究所有你現(xiàn)在還不知道的概念。例如,我可能會(huì)玩幾個(gè)這種類型的游戲,并確保知道他們是如何工作的。我可能會(huì)研究船舶和炸彈的設(shè)計(jì)和工作原理。也許我會(huì)研究怎么樣將游戲中的數(shù)據(jù)或狀態(tài)存儲(chǔ)在數(shù)據(jù)庫等一些技術(shù)上的問題。我做完這個(gè)研究之后,我可能會(huì)回到第1步重新開始,根據(jù)新的信息,重寫描述和提取新的概念。
當(dāng)我完成上面的步驟,我通過思考“哪些是類似于其他東西的”,把名詞列表轉(zhuǎn)換成一個(gè)有層次結(jié)構(gòu)的類樹,同樣,我也會(huì)思考,哪些單詞是其他東西的基礎(chǔ)呢?
馬上我發(fā)現(xiàn),"Room" 和 "Scene"對(duì)于我要做的事情來說基本上是一樣的事情。在這個(gè)游戲中,我選擇使用"Scene" 。接下來我發(fā)現(xiàn),所有的特殊房間比如"Central Corridor"基本上也跟"Scene"是一樣的。我發(fā)現(xiàn)"Death"也是一個(gè)"Scene", 由于我選擇了"Scene"而不是"Room",你可以有一個(gè)“死亡場景”,而是不一個(gè)很奇怪的“死亡房間”。"Maze"和"Map"基本一樣,所以我選擇使用"Map",因?yàn)槲腋嗟臅r(shí)候都用它。我不想做一個(gè)戰(zhàn)斗系統(tǒng),所以我會(huì)先忽略"Alien"和 "Player",但是會(huì)把他們保存下來以供以后使用。"Planet"也可能僅僅是另一個(gè)場景,而不是什么具體的事情。
在此之后,我開始創(chuàng)建一個(gè)層次結(jié)構(gòu)的類:
- Map
- Engine
Scene
- Death
- Central Corridor
- Laser Weapon Armory
- The Bridge
- Escape Pod
然后,我會(huì)找出說明中每個(gè)動(dòng)詞都需要什么行動(dòng)。比如,我從說明中得知,我需要一個(gè)方法來"run"這個(gè)引擎,通過地圖獲得下一個(gè)場景"get the next scene",獲得"opening scene",或者"enter"一個(gè)場景。我把這些加到類樹里:
- Map- next_scene- opening_scene
- Engine- play
- Scene- enter Death Central Corridor Laser Weapon Armory The Bridge* Escape Pod
注意一下,我只是把-enter
放到Scene
下面,因?yàn)槲抑浪械膱鼍岸紩?huì)繼承它并重寫它。
當(dāng)我有了這個(gè)類樹和一些功能之后,我在編輯器中打開源文件,并嘗試編寫代碼。通常,我會(huì)復(fù)制粘貼這棵類樹到源文件中,然后編輯它。下面是一個(gè)如何編碼的小例子,文件末尾包含一個(gè)簡單的測試用例:
class Scene(object):
def enter(self):
pass
class Engine(object):
def __init__(self, scene_map):
pass
def play(self):
pass
class Death(Scene):
def enter(self):
pass
class CentralCorridor(Scene):
def enter(self):
pass
class LaserWeaponArmory(Scene):
def enter(self):
pass
class TheBridge(Scene):
def enter(self):
pass
class EscapePod(Scene):
def enter(self):
pass
class Map(object):
def __init__(self, start_scene):
pass
def next_scene(self, scene_name):
pass
def opening_scene(self):
pass
a_map = Map('central_corridor')
a_game = Engine(a_map)
a_game.play()
在這個(gè)文件中,你可以看到我只是復(fù)制了我想要的層次結(jié)構(gòu),然后一點(diǎn)點(diǎn)的補(bǔ)齊代碼再運(yùn)行它,看看它在這個(gè)基本結(jié)構(gòu)中是否運(yùn)行順利。 在這節(jié)練習(xí)后面的部分,你會(huì)填補(bǔ)這段代碼的其余部分,使其正常工作,以配合練習(xí)開頭的游戲描述。
在我提供的流程中,最后一步并不是實(shí)際意義上的一步,而是要做一個(gè)循環(huán)。在編程的世界里,你永遠(yuǎn)做不到一次通過,相反,你退回整個(gè)過程,并再次根據(jù)你從后面的步驟中了解到的信息完善它。有時(shí)候我已經(jīng)到了第3步,但是我發(fā)現(xiàn)我還需要在第1、2步做更多工作,我會(huì)停下來并返回去完善它。有時(shí)候我也會(huì)突然靈光一閃,跳到最后,用我腦子里更好的解決方案編碼實(shí)現(xiàn),但是之后,我仍然會(huì)回去完成前面的步驟,以確保我的工作覆蓋了所有的可能性。
在這一過程的另一個(gè)觀點(diǎn)是,你不是僅在一個(gè)層面上使用這個(gè)流程,當(dāng)你遇到某些特定問題的時(shí)候,你可以在任意一個(gè)層級(jí)上使用該流程。比方說,我不知道如何寫Engine.play
方法,我可以靜下心來用這個(gè)流程只做這一種功能,直到弄清楚這個(gè)方法怎么寫。
因?yàn)檫@個(gè)流程在最抽象的概念(頂部)開始,然后再下降到實(shí)際執(zhí)行過程中,因此這一流程通常標(biāo)示為“自上而下”。我希望你使用這一流程,但你也應(yīng)該知道,還有另一種方式來解決程序中的問題,這種方式是從代碼開始,再“上升”到抽象的概念問題。這種方式被稱為“自下而上”。下面是自下而下方式所遵循的步驟:
- 取一小塊問題,編寫一些代碼,并讓他勉強(qiáng)運(yùn)行
- 完善代碼,將其轉(zhuǎn)換成一些更正式的包含類和自動(dòng)化測試的代碼。
- 提取其中的關(guān)鍵概念,并嘗試找出研究他們。
- 寫出到底發(fā)生了什么的描述。
- 繼續(xù)完善代碼,也可能是把它扔掉,并重新開始。
- 移動(dòng)到其他問題上,重復(fù)步驟。
當(dāng)你需要更優(yōu)質(zhì)的代碼,并在代碼中更自然的思考你要解決的問題時(shí),這種方式更好一些。尤其是當(dāng)你知道小塊的難題,但沒有足夠的信息把握整個(gè)概念的時(shí)候,這個(gè)方式是一個(gè)解決問題很好的辦法。將問題分解成碎片并探索代碼,直到你解決這個(gè)問題。然而,你解決問題的途徑可能是緩慢而曲折的,所以,我的這一流程中也包含后退并反復(fù)研究問題,直到你通過自己所為解決所有難題。
我要告訴你我解決上述問題的最終辦法,但我不希望你只是直接跳到這里并輸入代碼。我希望你能通過我的描述,完成我寫的那個(gè)簡單粗糙的代碼框架,并讓他運(yùn)行起來。當(dāng)你有了自己的解決方案時(shí),你可以回來看看我是怎么解決的。
我準(zhǔn)備把文件ex43.py
拆成小塊,再一一解釋,而不是一次性提供完整的代碼。
from sys import exit
from random import randint
這只是我們游戲需要導(dǎo)入的包,沒什么新奇的。
class Scene(object):
def enter(self):
print "This scene is not yet configured. Subclass it and implement enter()."
exit(1)
正如你在框架代碼里看到的,我創(chuàng)建了基礎(chǔ)類Scene
,它將包含所有scene要做的所有的事。在這段代碼中,它們并沒有做什么,所以這只是給你的一個(gè)如果創(chuàng)建基類的示范。
class Engine(object):
def __init__(self, scene_map):
self.scene_map = scene_map
def play(self):
current_scene = self.scene_map.opening_scene()
last_scene = self.scene_map.next_scene('finished')
while current_scene != last_scene:
next_scene_name = current_scene.enter()
current_scene = self.scene_map.next_scene(next_scene_name)
# be sure to print out the last scene
current_scene.enter()
我同樣創(chuàng)建了類Engine
,并且你能看到我已經(jīng)使用了方法Map.opening_scene
和 Map.next_scene
。因?yàn)樵谖覍?code>Map類之前,做了一點(diǎn)計(jì)劃,我可以假設(shè)我后面會(huì)寫這些,然后提前使用它們。
class Death(Scene):
quips = [
"You died. You kinda suck at this.",
"Your mom would be proud...if she were smarter.",
"Such a luser.",
"I have a small puppy that's better at this."
]
def enter(self):
print Death.quips[randint(0, len(self.quips)-1)]
exit(1)
我的第一個(gè)場景是一個(gè)叫做Death
的場景,它給你展現(xiàn)了你能寫的最簡單的場景。
class CentralCorridor(Scene):
def enter(self):
print "The Gothons of Planet Percal #25 have invaded your ship and destroyed"
print "your entire crew. You are the last surviving member and your last"
print "mission is to get the neutron destruct bomb from the Weapons Armory,"
print "put it in the bridge, and blow the ship up after getting into an "
print "escape pod."
print "\n"
print "You're running down the central corridor to the Weapons Armory when"
print "a Gothon jumps out, red scaly skin, dark grimy teeth, and evil clown costume"
print "flowing around his hate filled body. He's blocking the door to the"
print "Armory and about to pull a weapon to blast you."
action = raw_input("> ")
if action == "shoot!":
print "Quick on the draw you yank out your blaster and fire it at the Gothon."
print "His clown costume is flowing and moving around his body, which throws"
print "off your aim. Your laser hits his costume but misses him entirely. This"
print "completely ruins his brand new costume his mother bought him, which"
print "makes him fly into an insane rage and blast you repeatedly in the face until"
print "you are dead. Then he eats you."
return 'death'
elif action == "dodge!":
print "Like a world class boxer you dodge, weave, slip and slide right"
print "as the Gothon's blaster cranks a laser past your head."
print "In the middle of your artful dodge your foot slips and you"
print "bang your head on the metal wall and pass out."
print "You wake up shortly after only to die as the Gothon stomps on"
print "your head and eats you."
return 'death'
elif action == "tell a joke":
print "Lucky for you they made you learn Gothon insults in the academy."
print "You tell the one Gothon joke you know:"
print "Lbhe zbgure vf fb sng, jura fur fvgf nebhaq gur ubhfr, fur fvgf nebhaq gur ubhfr."
print "The Gothon stops, tries not to laugh, then busts out laughing and can't move."
print "While he's laughing you run up and shoot him square in the head"
print "putting him down, then jump through the Weapon Armory door."
return 'laser_weapon_armory'
else:
print "DOES NOT COMPUTE!"
return 'central_corridor'
接下來,我創(chuàng)建了CentralCorridor
,這是游戲的開始。我在寫Map
之前,為游戲制作了這個(gè)場景,是因?yàn)槲液竺嬉盟鼈儭?/p>
class LaserWeaponArmory(Scene):
def enter(self):
print "You do a dive roll into the Weapon Armory, crouch and scan the room"
print "for more Gothons that might be hiding. It's dead quiet, too quiet."
print "You stand up and run to the far side of the room and find the"
print "neutron bomb in its container. There's a keypad lock on the box"
print "and you need the code to get the bomb out. If you get the code"
print "wrong 10 times then the lock closes forever and you can't"
print "get the bomb. The code is 3 digits."
code = "%d%d%d" % (randint(1,9), randint(1,9), randint(1,9))
guess = raw_input("[keypad]> ")
guesses = 0
while guess != code and guesses < 10:
print "BZZZZEDDD!"
guesses += 1
guess = raw_input("[keypad]> ")
if guess == code:
print "The container clicks open and the seal breaks, letting gas out."
print "You grab the neutron bomb and run as fast as you can to the"
print "bridge where you must place it in the right spot."
return 'the_bridge'
else:
print "The lock buzzes one last time and then you hear a sickening"
print "melting sound as the mechanism is fused together."
print "You decide to sit there, and finally the Gothons blow up the"
print "ship from their ship and you die."
return 'death'
class TheBridge(Scene):
def enter(self):
print "You burst onto the Bridge with the netron destruct bomb"
print "under your arm and surprise 5 Gothons who are trying to"
print "take control of the ship. Each of them has an even uglier"
print "clown costume than the last. They haven't pulled their"
print "weapons out yet, as they see the active bomb under your"
print "arm and don't want to set it off."
action = raw_input("> ")
if action == "throw the bomb":
print "In a panic you throw the bomb at the group of Gothons"
print "and make a leap for the door. Right as you drop it a"
print "Gothon shoots you right in the back killing you."
print "As you die you see another Gothon frantically try to disarm"
print "the bomb. You die knowing they will probably blow up when"
print "it goes off."
return 'death'
elif action == "slowly place the bomb":
print "You point your blaster at the bomb under your arm"
print "and the Gothons put their hands up and start to sweat."
print "You inch backward to the door, open it, and then carefully"
print "place the bomb on the floor, pointing your blaster at it."
print "You then jump back through the door, punch the close button"
print "and blast the lock so the Gothons can't get out."
print "Now that the bomb is placed you run to the escape pod to"
print "get off this tin can."
return 'escape_pod'
else:
print "DOES NOT COMPUTE!"
return "the_bridge"
class EscapePod(Scene):
def enter(self):
print "You rush through the ship desperately trying to make it to"
print "the escape pod before the whole ship explodes. It seems like"
print "hardly any Gothons are on the ship, so your run is clear of"
print "interference. You get to the chamber with the escape pods, and"
print "now need to pick one to take. Some of them could be damaged"
print "but you don't have time to look. There's 5 pods, which one"
print "do you take?"
good_pod = randint(1,5)
guess = raw_input("[pod #]> ")
if int(guess) != good_pod:
print "You jump into pod %s and hit the eject button." % guess
print "The pod escapes out into the void of space, then"
print "implodes as the hull ruptures, crushing your body"
print "into jam jelly."
return 'death'
else:
print "You jump into pod %s and hit the eject button." % guess
print "The pod easily slides out into space heading to"
print "the planet below. As it flies to the planet, you look"
print "back and see your ship implode then explode like a"
print "bright star, taking out the Gothon ship at the same"
print "time. You won!"
return 'finished'
class Finished(Scene):
def enter(self):
print "You won! Good job."
return 'finished'
這是游戲剩下的場景。因?yàn)槲抑牢倚枰麄?,并且已?jīng)想好他們應(yīng)該如何交織在一起,所以我可以直接編碼。
順便說一下,我不是直接輸入這些代碼。記得我說過的嗎,用增量的方法嘗試構(gòu)建所有的代碼,一次只寫一小部分。我只是給你看了最終的結(jié)果。
class Map(object):
scenes = {
'central_corridor': CentralCorridor(),
'laser_weapon_armory': LaserWeaponArmory(),
'the_bridge': TheBridge(),
'escape_pod': EscapePod(),
'death': Death(),
'finished': Finished(),
}
def __init__(self, start_scene):
self.start_scene = start_scene
def next_scene(self, scene_name):
val = Map.scenes.get(scene_name)
return val
def opening_scene(self):
return self.next_scene(self.start_scene)
在這之后,我寫了Map
類,你可以看到它通過名字把所有的場景存在了字典中。我把這個(gè)字典叫做Map.scenes
。這也是為什么map是在scens之后才寫的原因,因?yàn)檫@個(gè)字典需要包含所有已存在的場景。
a_map = Map('central_corridor')
a_game = Engine(a_map)
a_game.play()
最后,我讓我的代碼通過創(chuàng)建一個(gè)Map
,并在調(diào)用play
之前把map交給Engine
,把游戲運(yùn)行起來,
確保你明白這個(gè)游戲,并且首先嘗試自己解決它。如果你被難住了,可以閱讀一小部分我的代碼,然后再嘗試自己搞定它。
當(dāng)我運(yùn)行我的游戲的時(shí)候,我可以看到:
$ python ex43.py
The Gothons of Planet Percal #25 have invaded your ship and destroyed
your entire crew. You are the last surviving member and your last
mission is to get the neutron destruct bomb from the Weapons Armory,
put it in the bridge, and blow the ship up after getting into an
escape pod.
You're running down the central corridor to the Weapons Armory when
a Gothon jumps out, red scaly skin, dark grimy teeth, and evil clown costume
flowing around his hate filled body. He's blocking the door to the
Armory and about to pull a weapon to blast you.
> dodge!
Like a world class boxer you dodge, weave, slip and slide right
as the Gothon's blaster cranks a laser past your head.
In the middle of your artful dodge your foot slips and you
bang your head on the metal wall and pass out.
You wake up shortly after only to die as the Gothon stomps on
your head and eats you.
Your mom would be proud...if she were smarter.
- 修改這個(gè)游戲!你可能不喜歡這個(gè)游戲。讓游戲運(yùn)行起來,然后按照你的喜好修改它。這是你的電腦,你可以用它做你想做的事情
- 代碼中有一個(gè)bug,為什么門鎖了11次?
- 解釋一下返回至下一個(gè)房間的工作原理。
- 增加作弊代碼,這樣你能通過一些更難的房間。我能只在一行上加兩個(gè)單詞做到這些。
- 回到我的描述和分析,嘗試為英雄和他遇見的各種哥頓人創(chuàng)建一個(gè)小型作戰(zhàn)系統(tǒng)。
- 這其實(shí)是一個(gè)小版本的“有限狀態(tài)機(jī)(finite state machine)”,找資料閱讀了解一下,雖然你可能看不懂,但還是找來看看吧。
你可以就像給朋友講述一個(gè)故事一樣,來創(chuàng)建游戲?;蛘吣憧梢圆扇『唵蔚哪阆矚g的書或電影場景。
更多建議: