练习 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.pyplanisphere_tests.py 这两个文件里边的内容。

    首先要做的是使用 Room 类来构建基本的地图架构:

    planisphere.py

    你会发现我们的 Room 类和地图有一些问题:

    • 我们必须把放在 if-else 语句中的文本在进入一个房间之前打印出来,作为每个房间的一部分。这就意味着你不能把 planisphere 打乱,这很好。你要在这个练习中慢慢修复它。

    • 原版游戏中我们使用了专门的代码来生成一些内容,例如炸弹的激活键码,舰舱的选择等,这次我们做游戏时就先使用默认值好了,不过后面的附加练习里,我会要求你把这些功能再加到游戏中。

    • 我添加了一种新的转换模式,以"*"为标记,用来在游戏引擎中实现“catch-all”动作。

    等你把上面的代码基本写好以后,接下来就是引导你继续写下去的自动测试的内容 tests/planisphere_test.py

    planisphere_tests.py

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

    你在这部分练习中的任务是完成这个地图,并且让自动测试可以完整地检查过整个地图。这包括将所有的 generic_death 对象修正为游戏中实际的失败结尾。让你的代码成功运行起来,并让你的测试越全面越好。后面我们会对地图做一些修改,到时候这些测试将保证修改后的代码还可以正常工作。

    你应该让你的游戏地图正常运行,并对它进行良好的单元测试。我现在想让你做一个简单的小游戏引擎,它将运行房间、收集来自玩家的输入,并跟踪玩家在游戏中的位置。我们将使用你刚刚学会的会话来创建一个简单的游戏引擎,这个引擎会做这些事情:

    • 为新用户开启一个新游戏。

    • 为用户展示房间。

    • 从用户获取输入。

    • 通过游戏运行用户的输入。

    • 呈现结果,并继续运行,直至用户挂掉。

    要做到这些,你需要使用你一直在写的可靠的 app.py,来创建一个运行良好的、基于会话的游戏引擎。问题是,我需要做一个非常简单的基本 HTML 文件,它将由你来完成它。这是基础引擎:

    这个脚本中有更多的新东西,但神奇的是,这个小文件是一个完全基于 web 的游戏引擎。在运行 app.py 之前,需要更改 PYTHONPATH 环境变量。不知道那是什么?我知道这有点枯燥,但你必须学习这是什么来运行基本的 Python 程序,没办法,用 Python 的人就喜欢这样。

    在你的终端输入:

    1. export PYTHONPATH=$PYTHONPATH:.

    在 Windows 的 PowerShell 中输入:

    你只要针对每一个命令行会话界面输入一次就可以了,不过如果你运行 Python 代码时看到了 import error,或者你输入错误,那就需要再去执行一下上面的命令。

    接下来你需要删掉 templates/hello_form.htmltemplates/index.html,并创建两个前面代码中提到的模板。这是一个非常简单的 :

    show_room.html

      1. {% extends "layout.html" %}
      2. <h1> {{ room.name }} </h1>
      3. <pre>
      4. {{ room.description }}
      5. </pre>
      6. {% if room.name in ["death", "The End"] %}
      7. <p><a href="/">Play Again?</a></p>
      8. {% else %}
      9. <p>
      10. <form action="/game" method="POST">
      11. - <input type="text" name="action"> <input type="SUBMIT">
      12. </form>
      13. </p>
      14. {% endif %}
      15. {% 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)的信息。