練習(xí) 52 - 創(chuàng)建你的 web 游戲 - 笨辦法學(xué)Python3

練習(xí) 52. 創(chuàng)建你的 web 游戲

這本書馬上就要結(jié)束了助琐。這節(jié)練習(xí)對你來說是個真正的挑戰(zhàn)肥惭。當(dāng)你完成以后,你就可以算是一個能力不錯的 Python 初學(xué)者了忍饰。為了進一步學(xué)習(xí),你還需要多讀一些書寺庄,多寫一些程序艾蓝,不過你已經(jīng)具備進一步學(xué)習(xí)的能力了。接下來的學(xué)習(xí)就只是時間斗塘、動力赢织、以及資源的問題了。

在本節(jié)練習(xí)中馍盟,我們不會去創(chuàng)建一個完整的游戲于置,而是要為《練習(xí) 47》中的游戲創(chuàng)建一個“引擎(engine)”,讓這個游戲能夠在瀏覽器中運行起來贞岭。這會涉及到將《習(xí)題 43》中的游戲“重構(gòu)(refactor)”八毯,將《習(xí)題 47》中的架構(gòu)混合進來,添加自動測試代碼瞄桨,最后創(chuàng)建一個可以運行游戲的 web 引擎话速。

這個練習(xí)會非常龐大。我預(yù)測你要花一周到一個月時間才能完成它芯侥。你最好一點一點來泊交,每天晚上完成一點,在進行下一步之前確保上一步已經(jīng)正確完成。

重構(gòu)《練習(xí) 43》的游戲

你已經(jīng)在兩個練習(xí)中修改了 gothonweb 項目活合,這節(jié)習(xí)題中你會再修改一次。這種修改的技術(shù)叫做“重構(gòu)(refactoring)”物赶,或者用我喜歡的講法來說白指,叫“修修補補(fixing stuff)”。重構(gòu)是一個編程術(shù)語酵紫,它指的是清理舊代碼或者為舊代碼添加新功能的過程告嘲。你其實已經(jīng)做過這樣的事情了,只不過不知道這個術(shù)語而已奖地。這是寫軟件過程的第二個自然屬性橄唬。

你在本節(jié)中要做的,是將《習(xí)題 47》中的可以測試的房間地圖参歹,以及《習(xí)題 43》中的游戲這兩樣?xùn)|西歸并到一起仰楚,創(chuàng)建一個新的游戲架構(gòu)。游戲的內(nèi)容不會發(fā)生變化犬庇,只不過我們會通過“重構(gòu)”讓它有一個更好的架構(gòu)而已僧界。

第一步是將 ex47/game.py 的內(nèi)容復(fù)制到 gothonweb/planisphere.py 中,然后將 tests/ex47_tests.py 的內(nèi)容復(fù)制到 tests/planisphere_tests.py 中臭挽,然后再次運行 nosetests捂襟,確保他們還能正常工作』斗澹“planisphere”這個詞是地圖的同義詞葬荷,用這個名字是為了避免 Python 內(nèi)置的 map 函數(shù)。同義詞典(Thesaurus)是個好東西纽帖,要善于利用它宠漩。

警告!
從現(xiàn)在開始抛计,我不會再向你展示我運行測試的輸出結(jié)果了哄孤。我假設(shè)你會自己去做測試,所以測試是個前提吹截,除非你遇到了錯誤瘦陈。

當(dāng)你把《練習(xí) 47》的代碼復(fù)制好之后,你就該開始重構(gòu)它了波俄,讓它包含《習(xí)題 43》中的地圖晨逝。我一開始會把基本架構(gòu)為你準(zhǔn)備好,然后你需要去完成 planisphere.pyplanisphere_tests.py 這兩個文件里邊的內(nèi)容懦铺。

首先要做的是使用 Room 類來構(gòu)建基本的地圖架構(gòu):

planisphere.py

1   class Room(object): 
2
3       def __init__(self, name, description):
4           self.name = name
5           self.description = description
6           self.paths = {} 
7
8       def go(self, direction):
9           return self.paths.get(direction, None) 
10
11      def add_paths(self, paths):
12          self.paths.update(paths) 
13
14
15  central_corridor = Room("Central Corridor", 
16  """
17  The Gothons of Planet Percal #25 have invaded your ship and destroyed 
18  your entire crew. You are the last surviving member and your last 
19  mission is to get the neutron destruct bomb from the Weapons Armory, put
20  it in the bridge, and blow the ship up after getting into an escape pod.
21
22  You're running down the central corridor to the Weapons Armory when a 
23  Gothon jumps out, red scaly skin, dark grimy teeth, and evil clown 
24  costume flowing around his hate filled body. He's blocking the door to
25  the Armory and about to pull a weapon to blast you. 
26  """)
27
28
29  laser_weapon_armory = Room("Laser Weapon Armory", 
30  """
31  Lucky for you they made you learn Gothon insults in the academy. You 
32  tell the one Gothon joke you know: Lbhe zbgure vf fb sng, jura fur fvgf 
33  nebhaq gur ubhfr, fur fvgf nebhaq gur ubhfr. The Gothon stops, tries 
34  not to laugh, then busts out laughing and can't move. While he's 
35  laughing you run up and shoot him square in the head putting him down, 
36  then jump through the Weapon Armory door. 
37
38  You do a dive roll into the Weapon Armory, crouch and scan the room for 
39  more Gothons that might be hiding. It's dead quiet, too quiet. You 
40  stand up and run to the far side of the room and find the neutron bomb 
41  in its container. There's a keypad lock on the box and you need the 
42  code to get the bomb out. If you get the code wrong 10 times then the
43  lock closes forever and you can't get the bomb. The code is 3 digits. 
44  """)
45
46
47  the_bridge = Room("The Bridge", 
48  """
49  The container clicks open and the seal breaks, letting gas out. You 
50  grab the neutron bomb and run as fast as you can to the bridge where you
51  must place it in the right spot. 
52
53  You burst onto the Bridge with the netron destruct bomb under your arm 
54  and surprise 5 Gothons who are trying to take control of the ship. Each 
55  of them has an even uglier clown costume than the last. They haven't 
56  pulled their weapons out yet, as they see the active bomb under your arm 
57  and don't want to set it off. 
58  """)
59
60
61  escape_pod = Room("Escape Pod", 
62  """
63  You point your blaster at the bomb under your arm and the Gothons put 
64  their hands up and start to sweat. You inch backward to the door, open 
65  it, and then carefully place the bomb on the floor, pointing your 
66  blaster at it. You then jump back through the door, punch the close 
67  button and blast the lock so the Gothons can't get out. Now that the 
68  bomb is placed you run to the escape pod to get off this tin can. 
69
70  You rush through the ship desperately trying to make it to the escape 
71  pod before the whole ship explodes. It seems like hardly any Gothons 
72  are on the ship, so your run is clear of interference. You get to the 
73  chamber with the escape pods, and now need to pick one to take. Some of 
74  them could be damaged but you don't have time to look. There's 5 pods, 
75  which one do you take? 
76  """)
77
78
79  the_end_winner = Room("The End", 
80  """
81  You jump into pod 2 and hit the eject button. The pod easily slides out 
82  into space heading to the planet below. As it flies to the planet, you 
83  look back and see your ship implode then explode like a bright star, 
84  taking out the Gothon ship at the same time. You won! 
85  """)
86
87
88  the_end_loser = Room("The End", 
89  """
90  You jump into a random pod and hit the eject button. The pod escapes 
91  out into the void of space, then implodes as the hull ruptures, crushing 
92  your body into jam jelly. 
93  """
94  )
95
96  escape_pod.add_paths({
97      '2': the_end_winner,
98      '*': the_end_loser 
99  })
100
101 generic_death = Room("death", "You died.") 
102
103 the_bridge.add_paths({
104     'throw the bomb': generic_death,
105     'slowly place the bomb': escape_pod 
106 })
107
108 laser_weapon_armory.add_paths({
109     '0132': the_bridge,
110     '*': generic_death 
111 })
112
113 central_corridor.add_paths({
114     'shoot!': generic_death,
115     'dodge!': generic_death,
116     'tell a joke': laser_weapon_armory 
117 })
118
119 START = 'central_corridor' 
120
121 def load_room(name): 
122     """
123     There is a potential security problem here.
124     Who gets to set name? Can that expose a variable? 
125     """
126     return globals().get(name) 
127
128 def name_room(room): 
129     """
130     Same possible security problem. Can you trust room?
131     What's a better solution than this globals lookup? 
132     """
133     for key, value in globals().items():
134         if value == room:
135             return key

你會發(fā)現(xiàn)我們的 Room 類和地圖有一些問題:

  1. 我們必須把放在 if-else 語句中的文本在進入一個房間之前打印出來捉貌,作為每個房間的一部分。這就意味著你不能把 planisphere 打亂,這很好趁窃。你要在這個練習(xí)中慢慢修復(fù)它牧挣。

  2. 原版游戲中我們使用了專門的代碼來生成一些內(nèi)容,例如炸彈的激活鍵碼醒陆,艦艙的選擇等瀑构,這次我們做游戲時就先使用默認(rèn)值好了,不過后面的附加練習(xí)里刨摩,我會要求你把這些功能再加到游戲中寺晌。

  3. 我為游戲中的所有失敗結(jié)尾寫了一個 generic_death,你需要去補全這個函數(shù)澡刹。你需要把原版游戲中所有的失敗結(jié)尾都加進去呻征,并確保代碼能正確運行。

  4. 我添加了一種新的轉(zhuǎn)換模式罢浇,以"*"為標(biāo)記陆赋,用來在游戲引擎中實現(xiàn)“catch-all”動作。

等你把上面的代碼基本寫好以后己莺,接下來就是引導(dǎo)你繼續(xù)寫下去的自動測試的內(nèi)容 tests/planisphere_test.py

planisphere_tests.py

1   from nose.tools import *
2   from gothonweb.planisphere import * 
3
4   def test_room():
5       gold = Room("GoldRoom",
6       """This room has gold in it you can grab. There's a 
7       door to the north.""")
8       assert_equal(gold.name, "GoldRoom")
9       assert_equal(gold.paths, {}) 
10
11  def test_room_paths():
12      center = Room("Center", "Test room in the center.")
13      north = Room("North", "Test room in the north.")
14      south = Room("South", "Test room in the south.") 
15
16      center.add_paths({'north': north, 'south': south})
17      assert_equal(center.go('north'), north)
18      assert_equal(center.go('south'), south) 
19
20  def test_map():
21      start = Room("Start", "You can go west and down a hole."
22      west = Room("Trees", "There are trees here, you can go east.")
23      down = Room("Dungeon", "It's dark down here, you can go up.")
24
25      start.add_paths({'west': west, 'down': down})
26      west.add_paths({'east': start})
27      down.add_paths({'up': start}) 
28
29      assert_equal(start.go('west'), west)
30      assert_equal(start.go('west').go('east'), start)
31      assert_equal(start.go('down').go('up'), start) 
32
33  def test_gothon_game_map():
34      start_room = load_room(START)
35      assert_equal(start_room.go('shoot!'), generic_death)
36      assert_equal(start_room.go('dodge!'), generic_death) 
37
38      room = start_room.go('tell a joke')
39      assert_equal(room, laser_weapon_armory)

你在這部分練習(xí)中的任務(wù)是完成這個地圖奏甫,并且讓自動測試可以完整地檢查過整個地圖。這包括將所有的 generic_death 對象修正為游戲中實際的失敗結(jié)尾凌受。讓你的代碼成功運行起來阵子,并讓你的測試越全面越好。后面我們會對地圖做一些修改胜蛉,到時候這些測試將保證修改后的代碼還可以正常工作挠进。

創(chuàng)建一個引擎

你應(yīng)該讓你的游戲地圖正常運行,并對它進行良好的單元測試誊册。我現(xiàn)在想讓你做一個簡單的小游戲引擎领突,它將運行房間、收集來自玩家的輸入案怯,并跟蹤玩家在游戲中的位置君旦。我們將使用你剛剛學(xué)會的會話來創(chuàng)建一個簡單的游戲引擎,這個引擎會做這些事情:

  1. 為新用戶開啟一個新游戲嘲碱。

  2. 為用戶展示房間金砍。

  3. 從用戶獲取輸入。

  4. 通過游戲運行用戶的輸入麦锯。

  5. 呈現(xiàn)結(jié)果卜录,并繼續(xù)運行轰绵,直至用戶掛掉。

要做到這些惧蛹,你需要使用你一直在寫的可靠的 app.py稽屏,來創(chuàng)建一個運行良好的、基于會話的游戲引擎。問題是,我需要做一個非常簡單的基本 HTML 文件澎羞,它將由你來完成它。這是基礎(chǔ)引擎:

app.py

1   from flask import Flask, session, redirect, url_for, escape, request
2   from flask import render_template
3   from gothonweb import planisphere 
4
5   app = Flask(__name__) 
6
7   @app.route("/")
8   def index():
9       # this is used to "setup" the session with starting value
10      session['room_name'] = planisphere.START
11      return redirect(url_for("game")) 
12
13  @app.route("/game", methods=['GET', 'POST'])
14  def game():
15      room_name = session.get('room_name')
16
17      if request.method == "GET":
18          if room_name:
19              room = planisphere.load_room(room_name)
20              return render_template("show_room.html", room=room)
21          else:
22              # why is there here? do you need it?'
23              return render_template("you_died.html")
24      else:
25          action = request.form.get('action') 
26
27          if room_name and action:
28              room = planisphere.load_room(room_name)
29              next_room = room.go(action) 
30
31              if not next_room:
32                  session['room_name'] = planisphere.name_room
33              else:
34                  session['room_name'] = planisphere.name_room
35
36          return redirect(url_for("game")) 
37
38
39  # YOU SHOULD CHANGE THIS IF YOU PUT ON THE INTERNET
40  app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT' 
41
42  if __name__ == "__main__":
43      app.run()

這個腳本中有更多的新東西敛苇,但神奇的是煤痕,這個小文件是一個完全基于 web 的游戲引擎。在運行 app.py 之前接谨,需要更改 PYTHONPATH 環(huán)境變量。不知道那是什么塘匣?我知道這有點枯燥脓豪,但你必須學(xué)習(xí)這是什么來運行基本的 Python 程序,沒辦法忌卤,用 Python 的人就喜歡這樣扫夜。

在你的終端輸入:

export PYTHONPATH=$PYTHONPATH:.

在 Windows 的 PowerShell 中輸入:

$env:PYTHONPATH = "$env:PYTHONPATH;."

你只要針對每一個命令行會話界面輸入一次就可以了,不過如果你運行 Python 代碼時看到了 import error驰徊,或者你輸入錯誤笤闯,那就需要再去執(zhí)行一下上面的命令。

接下來你需要刪掉 templates/hello_form.htmltemplates/index.html棍厂,并創(chuàng)建兩個前面代碼中提到的模板颗味。這是一個非常簡單的 templates/show_room.html

show_room.html

{% extends "layout.html" %}

{% block content %}

<h1> {{ room.name }}    </h1>

<pre>
{{ room.description }}
</pre>

{% if room.name in ["death", "The End"] %}
    <p><a href="/">Play Again?</a></p>
{% else %}
    <p>
    <form action="/game" method="POST">
        - <input type="text" name="action"> <input type="SUBMIT">
    </form>
    </p>
{% endif %}

{% endblock %}

這是在游戲中顯示房間的模板。接下來你需要一個模板來告訴用戶他們已經(jīng)死了牺弹,以防他們意外地去到地圖的結(jié)尾浦马,也就是 templates/you_die .html:

you_died.html

<h1>You Died!</h1>

<p>Looks like you bit the dust.</p>
<p><a href="/">Play Again</a></p>

這些都弄好了之后,你可以這樣做:

  1. tests/app_tests.py 再次運行來測試這個游戲张漂。因為有會話晶默,所以你只需要在游戲里點幾下就行。不過航攒,你應(yīng)該能做一些基本操作磺陡。
  2. 運行 python3.6 app.py 腳本來玩一下這個游戲。

你需要和往常一樣刷新和修正你的游戲漠畜,慢慢修改游戲的 HTML 文件和引擎币他,直到你實現(xiàn)游戲需要的所有功能為止。

你的期末考試

你有沒有覺著我一下子給了你超多的信息呢盆驹?那就對了圆丹,我想要你在學(xué)習(xí)技能的同時可以有一些可以用來鼓搗的東西。為了完成這節(jié)習(xí)題躯喇,我會給你最后一套需要你自己完成的練習(xí)辫封。你應(yīng)該注意到硝枉,到目前為止你寫的游戲并不是很好,這只是你的第一版代碼而已倦微。你現(xiàn)在的任務(wù)是讓游戲更加完善妻味,實現(xiàn)下面的這些功能:

  1. 修正代碼中所有我提到和沒提到的 bug,如果你發(fā)現(xiàn)了新的 bug欣福,可以告訴我责球。
  2. 改進所有的自動測試,讓你可以測試更多的內(nèi)容拓劝,直到你可以不用瀏覽器就能測到所有的內(nèi)容為止雏逾。
  3. 讓 HTML 頁面看上去更美觀一些。
  4. 研究一下網(wǎng)頁登錄系統(tǒng)郑临,為這個程序創(chuàng)建一個登錄界面栖博,這樣人們就可以登錄這個游戲,并且可以保存游戲高分厢洞。
  5. 完成游戲地圖仇让,盡可能地把游戲做大,功能做全躺翻。
  6. 給用戶一個“幫助系統(tǒng)”丧叽,讓他們可以查詢每個房間里可以執(zhí)行哪些命令。
  7. 為你的游戲添加任何你能想到的新功能公你。
  8. 創(chuàng)建多個地圖踊淳,讓用戶可以選擇他們想要玩的一張來進行游戲。你的 app.py 應(yīng)該可以運行提供給它的任意的地圖陕靠,這樣你的引擎就可以支持多個不同的游戲嚣崭。
  9. 最后,使用你在練習(xí) 48 和 49 中學(xué)到的東西來創(chuàng)建一個更好的輸入處理器懦傍。你手頭已經(jīng)有了大部分必要的代碼雹舀,你只需要改進語法,讓它和你的輸入表單以及游戲引擎掛鉤即可粗俱。

祝你好運说榆!

常見問題

我在游戲中用了 session,但不能用 nosetests 測試寸认。 閱讀 Flask 測試文檔(Flask Testing Documentation)中的“其他測試技巧”(Other Testing Tricks)签财,了解關(guān)于在游戲中創(chuàng)建“假會話”(fake sessions)的信息。

我收到了一個 ImportError偏塞。 可能是以下情況中的一種或幾種: 錯誤的目錄唱蒸,錯誤的 Python 版本,沒有設(shè)置 PYTHON-PATH灸叼,沒有 init.py 文件神汹,以及(或者)import 中存在拼寫錯誤庆捺。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市屁魏,隨后出現(xiàn)的幾起案子滔以,更是在濱河造成了極大的恐慌,老刑警劉巖氓拼,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件你画,死亡現(xiàn)場離奇詭異,居然都是意外死亡桃漾,警方通過查閱死者的電腦和手機坏匪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來撬统,“玉大人剥槐,你說我怎么就攤上這事∠艽荩” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵颅崩,是天一觀的道長几于。 經(jīng)常有香客問我,道長沿后,這世上最難降的妖魔是什么沿彭? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮尖滚,結(jié)果婚禮上喉刘,老公的妹妹穿的比我還像新娘。我一直安慰自己漆弄,他們只是感情好睦裳,可當(dāng)我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著撼唾,像睡著了一般廉邑。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上倒谷,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天蛛蒙,我揣著相機與錄音,去河邊找鬼渤愁。 笑死牵祟,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的抖格。 我是一名探鬼主播诺苹,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼咕晋,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了筝尾?” 一聲冷哼從身側(cè)響起捡需,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎筹淫,沒想到半個月后站辉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡损姜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年饰剥,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片摧阅。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡汰蓉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出棒卷,到底是詐尸還是另有隱情顾孽,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布比规,位于F島的核電站若厚,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蜒什。R本人自食惡果不足惜测秸,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望灾常。 院中可真熱鬧霎冯,春花似錦、人聲如沸钞瀑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽雕什。三九已至关串,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間监徘,已是汗流浹背晋修。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留凰盔,地道東北人墓卦。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像户敬,于是被迫代替她去往敵國和親落剪。 傳聞我的和親對象是個殘疾皇子睁本,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容