练习 52. 创建你的 web 游戏
在本节练习中,我们不会去创建一个完整的游戏,而是要为《练习 47》中的游戏创建一个“引擎(engine)”,让这个游戏能够在浏览器中运行起来。这会涉及到将《习题 43》中的游戏“重构(refactor)”,将《习题 47》中的架构混合进来,添加自动测试代码,最后创建一个可以运行游戏的 web 引擎。
这个练习会非常庞大。我预测你要花一周到一个月时间才能完成它。你最好一点一点来,每天晚上完成一点,在进行下一步之前确保上一步已经正确完成。
你已经在两个练习中修改了 gothonweb 项目,这节习题中你会再修改一次。这种修改的技术叫做“重构(refactoring)”,或者用我喜欢的讲法来说,叫“修修补补(fixing stuff)”。重构是一个编程术语,它指的是清理旧代码或者为旧代码添加新功能的过程。你其实已经做过这样的事情了,只不过不知道这个术语而已。这是写软件过程的第二个自然属性。
你在本节中要做的,是将《习题 47》中的可以测试的房间地图,以及《习题 43》中的游戏这两样东西归并到一起,创建一个新的游戏架构。游戏的内容不会发生变化,只不过我们会通过“重构”让它有一个更好的架构而已。
第一步是将 的内容复制到 gothonweb/planisphere.py
中,然后将 tests/ex47_tests.py
的内容复制到 tests/planisphere_tests.py
中,然后再次运行 nosetests,确保他们还能正常工作。“planisphere”这个词是地图的同义词,用这个名字是为了避免 Python 内置的 map 函数。同义词典(Thesaurus)是个好东西,要善于利用它。
当你把《练习 47》的代码复制好之后,你就该开始重构它了,让它包含《习题 43》中的地图。我一开始会把基本架构为你准备好,然后你需要去完成 planisphere.py
和 planisphere_tests.py
这两个文件里边的内容。
首先要做的是使用 Room 类来构建基本的地图架构:
planisphere.py
你会发现我们的 Room 类和地图有一些问题:
我们必须把放在 if-else 语句中的文本在进入一个房间之前打印出来,作为每个房间的一部分。这就意味着你不能把 planisphere 打乱,这很好。你要在这个练习中慢慢修复它。
原版游戏中我们使用了专门的代码来生成一些内容,例如炸弹的激活键码,舰舱的选择等,这次我们做游戏时就先使用默认值好了,不过后面的附加练习里,我会要求你把这些功能再加到游戏中。
我添加了一种新的转换模式,以"*"为标记,用来在游戏引擎中实现“catch-all”动作。
等你把上面的代码基本写好以后,接下来就是引导你继续写下去的自动测试的内容 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)
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)
你在这部分练习中的任务是完成这个地图,并且让自动测试可以完整地检查过整个地图。这包括将所有的 generic_death
对象修正为游戏中实际的失败结尾。让你的代码成功运行起来,并让你的测试越全面越好。后面我们会对地图做一些修改,到时候这些测试将保证修改后的代码还可以正常工作。
你应该让你的游戏地图正常运行,并对它进行良好的单元测试。我现在想让你做一个简单的小游戏引擎,它将运行房间、收集来自玩家的输入,并跟踪玩家在游戏中的位置。我们将使用你刚刚学会的会话来创建一个简单的游戏引擎,这个引擎会做这些事情:
为新用户开启一个新游戏。
为用户展示房间。
从用户获取输入。
通过游戏运行用户的输入。
呈现结果,并继续运行,直至用户挂掉。
要做到这些,你需要使用你一直在写的可靠的 app.py,来创建一个运行良好的、基于会话的游戏引擎。问题是,我需要做一个非常简单的基本 HTML 文件,它将由你来完成它。这是基础引擎:
这个脚本中有更多的新东西,但神奇的是,这个小文件是一个完全基于 web 的游戏引擎。在运行 app.py
之前,需要更改 PYTHONPATH 环境变量。不知道那是什么?我知道这有点枯燥,但你必须学习这是什么来运行基本的 Python 程序,没办法,用 Python 的人就喜欢这样。
在你的终端输入:
export PYTHONPATH=$PYTHONPATH:.
在 Windows 的 PowerShell 中输入:
你只要针对每一个命令行会话界面输入一次就可以了,不过如果你运行 Python 代码时看到了 import error,或者你输入错误,那就需要再去执行一下上面的命令。
接下来你需要删掉 templates/hello_form.html
和 templates/index.html
,并创建两个前面代码中提到的模板。这是一个非常简单的 :
show_room.html
{% extends "layout.html" %}
<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 %}
这是在游戏中显示房间的模板。接下来你需要一个模板来告诉用户他们已经死了,以防他们意外地去到地图的结尾,也就是 templates/you_die .html:
you_died.html
这些都弄好了之后,你可以这样做:
- 让
tests/app_tests.py
再次运行来测试这个游戏。因为有会话,所以你只需要在游戏里点几下就行。不过,你应该能做一些基本操作。 - 运行
python3.6 app.py
脚本来玩一下这个游戏。你需要和往常一样刷新和修正你的游戏,慢慢修改游戏的 HTML 文件和引擎,直到你实现游戏需要的所有功能为止。
你有没有觉着我一下子给了你超多的信息呢?那就对了,我想要你在学习技能的同时可以有一些可以用来鼓捣的东西。为了完成这节习题,我会给你最后一套需要你自己完成的练习。你应该注意到,到目前为止你写的游戏并不是很好,这只是你的第一版代码而已。你现在的任务是让游戏更加完善,实现下面的这些功能:
- 修正代码中所有我提到和没提到的 bug,如果你发现了新的 bug,可以告诉我。
- 改进所有的自动测试,让你可以测试更多的内容,直到你可以不用浏览器就能测到所有的内容为止。
- 让 HTML 页面看上去更美观一些。
- 研究一下网页登录系统,为这个程序创建一个登录界面,这样人们就可以登录这个游戏,并且可以保存游戏高分。
- 完成游戏地图,尽可能地把游戏做大,功能做全。
- 给用户一个“帮助系统”,让他们可以查询每个房间里可以执行哪些命令。
- 为你的游戏添加任何你能想到的新功能。
- 创建多个地图,让用户可以选择他们想要玩的一张来进行游戏。你的
app.py
应该可以运行提供给它的任意的地图,这样你的引擎就可以支持多个不同的游戏。
我在游戏中用了 session,但不能用 nosetests 测试。 阅读 Flask 测试文档(Flask Testing Documentation)中的“其他测试技巧”(Other Testing Tricks),了解关于在游戏中创建“假会话”(fake sessions)的信息。