`
webcode
  • 浏览: 5912550 次
  • 性别: Icon_minigender_1
  • 来自: 上海
文章分类
社区版块
存档分类
最新评论

Learn Python The Hard Way学习(52) - 开始你的web游戏

 
阅读更多
书已经接近尾声了。最后的练习对你是一个挑战。当你完成以后,你就是一个能力不错的 Python 初学者了。你还需要多读一些书,多写一些程序,不过你已经具备进一步学习的技能了。只是时间、动力、以及资源的问题了。

在这个练习中,我们不会去创建一个完整的游戏,但是是我们会为习题 43中的游戏创建一个引擎,让这个游戏能够在浏览器中运行起来。这会涉及到重构习题 43中的游戏,将习题 47中的架构混合进来,添加自动测试代码,最后创建一个可以运行游戏的 web 引擎。

这个练习很庞大。我预测你要花一周到一个月才能完成它。最好的方法每晚上完成一点,在进行下一步之前确认上一步有正确完成。

重构43课的游戏
你已经在两个练习中修改了 gothonweb 项目,这节习题中你会再修改一次。这种修改的叫做“重构”,或者用我喜欢的讲法来说,叫“修补”。重构指的是清理旧代码或者为旧代码添加新功能的过程。你其实已经做过这样的事情了,只不过你不知道而已。这是写软件过程的第二个自然属性。

我们要做的是将习题 47中的房间地图和习题 43中的游戏归并到一起,创建一个新的游戏架构。包含相同的内容,只是结构变化了。

第一步是将 ex47/game.py 的内容复制到 gothonweb/map.py中,然后将 tests/ex47_tests.py 的内容复制到 tests/map_tests.py 中,然后再次运行 nosetests,确认他们还能正常工作。

复制完ex47的代码后,我们要把ex43的地图放进去,我简单的完成了框架,你需要完成整个map.py和map_tests.py文件。

首先,地图的基本结构是这样的:
class Room(object):

    def __init__(self, name, description):
        self.name = name
        self.description = description
        self.paths = {}

    def go(self, direction):
        return self.paths.get(direction, None)

    def add_paths(self, paths):
        self.paths.update(paths)


central_corridor = Room("Central Corridor",
"""
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.
""")


laser_weapon_armory = Room("Laser Weapon Armory",
"""
Lucky for you they made you learn Gothon insults in the academy.
You tell the one Gothon joke you know:
Lbhe zbgure vf fb sng, jura fur fvgf nebhaq gur ubhfr, fur fvgf nebhaq gur ubhfr.
The Gothon stops, tries not to laugh, then busts out laughing and can't move.
While he's laughing you run up and shoot him square in the head
putting him down, then jump through the Weapon Armory door.

You do a dive roll into the Weapon Armory, crouch and scan the room
for more Gothons that might be hiding.  It's dead quiet, too quiet.
You stand up and run to the far side of the room and find the
neutron bomb in its container.  There's a keypad lock on the box
and you need the code to get the bomb out.  If you get the code
wrong 10 times then the lock closes forever and you can't
get the bomb.  The code is 3 digits.
""")


the_bridge = Room("The Bridge",
"""
The container clicks open and the seal breaks, letting gas out.
You grab the neutron bomb and run as fast as you can to the
bridge where you must place it in the right spot.

You burst onto the Bridge with the netron destruct bomb
under your arm and surprise 5 Gothons who are trying to
take control of the ship.  Each of them has an even uglier
clown costume than the last.  They haven't pulled their
weapons out yet, as they see the active bomb under your
arm and don't want to set it off.
""")


escape_pod = Room("Escape Pod",
"""
You point your blaster at the bomb under your arm
and the Gothons put their hands up and start to sweat.
You inch backward to the door, open it, and then carefully
place the bomb on the floor, pointing your blaster at it.
You then jump back through the door, punch the close button
and blast the lock so the Gothons can't get out.
Now that the bomb is placed you run to the escape pod to
get off this tin can.

You rush through the ship desperately trying to make it to
the escape pod before the whole ship explodes.  It seems like
hardly any Gothons are on the ship, so your run is clear of
interference.  You get to the chamber with the escape pods, and
now need to pick one to take.  Some of them could be damaged
but you don't have time to look.  There's 5 pods, which one
do you take?
""")


the_end_winner = Room("The End",
"""
You jump into pod 2 and hit the eject button.
The pod easily slides out into space heading to
the planet below.  As it flies to the planet, you look
back and see your ship implode then explode like a
bright star, taking out the Gothon ship at the same
time.  You won!
""")


the_end_loser = Room("The End",
"""
You jump into a random pod and hit the eject button.
The pod escapes out into the void of space, then
implodes as the hull ruptures, crushing your body
into jam jelly.
"""
)

escape_pod.add_paths({
    '2': the_end_winner,
    '*': the_end_loser
})

generic_death = Room("death", "You died.")

the_bridge.add_paths({
    'throw the bomb': generic_death,
    'slowly place the bomb': escape_pod
})

laser_weapon_armory.add_paths({
    '0132': the_bridge,
    '*': generic_death
})

central_corridor.add_paths({
    'shoot!': generic_death,
    'dodge!': generic_death,
    'tell a joke': laser_weapon_armory
})

START = central_corridor



你会发现下面这些问题:
  1. 在进入房间前,我们要打印一段描述,描述和房间是有关联的,这样地图就不会被打乱。本来是在if语句中的,后面你要修正它。
  2. 最初的游戏能够定义一下东西,比如的键码等,这里用默认值,加分练习中会让你添加。
  3. 我只定义了generic_death为游戏结束, 你需要把最初的版本添加进来。
  4. 我用*表示“catch-all”动作。
下面是自动化测试代码map_testspy:
from nose.tools import *
from gothonweb.map import *

def test_room():
    gold = Room("GoldRoom", 
                """This room has gold in it you can grab. There's a
                door to the north.""")
    assert_equal(gold.name, "GoldRoom")
    assert_equal(gold.paths, {})

def test_room_paths():
    center = Room("Center", "Test room in the center.")
    north = Room("North", "Test room in the north.")
    south = Room("South", "Test room in the south.")

    center.add_paths({'north': north, 'south': south})
    assert_equal(center.go('north'), north)
    assert_equal(center.go('south'), south)
    
def test_map():
    start = Room("Start", "You can go west and down a hole.")
    west = Room("Trees", "There are trees here, you can go east.")
    down = Room("Dungeon", "It's dark down here, you can go up.")

    start.add_paths({'west': west, 'down': down})
    west.add_paths({'east': start})
    down.add_paths({'up': start})

    assert_equal(start.go('west'), west)
    assert_equal(start.go('west').go('east'), start)
    assert_equal(start.go('down').go('up'), start)

def test_gothon_game_map():
    assert_equal(START.go('shoot!'), generic_death)
    assert_equal(START.go('dodge!'), generic_death)

    room = START.go('tell a joke')
    assert_equal(room, laser_weapon_armory)



你的任务是完成地图,让测试通过。包括修正generic_death对象。你要保证程序能正常工作,并且测试也能完成,后面会改变地图,保存测试也能通过。

会话和追踪用户
在用户访问网站的时候,我们能够跟踪一些用户信息。web是无状态的,就是说每个请求和其他请求都独立开的。如果你请求A页面,输入一些数据然后连接到B页面,那么这些数据就会消失。

解决办法就是创建一个数据库,然后使用唯一的数字去保存和追踪浏览器的工作,在lpthw.web中是很简单的工作,下面是一个例子:
import web

web.config.debug = False

urls = (
    "/count", "count",
    "/reset", "reset"
)
app = web.application(urls, locals())
store = web.session.DiskStore('sessions')
session = web.session.Session(app, store, initializer={'count': 0})

class count:
    def GET(self):
        session.count += 1
        return str(session.count)

class reset:
    def GET(self):
        session.kill()
        return ""

if __name__ == "__main__":
    app.run()



要让这个例子工作,你需要建立一个sessions文件夹,session信息保存在这个文件夹中。运行程序,然后访问/count。刷新浏览器就可以看到数字增加,关闭浏览器后计数就不存在了。这就是我们在游戏中要用到的。有办法让浏览器永远记住这个信息,但是开发起来比较困难。如果你访问/reset的话,然后回到count页面,你会发现计数从新开始了,因为我们在reset页面杀掉了session。

你要花点时间了解上面的代码,你也可以打开sessions文件夹里面的文件看看,用下面的方法:
root@he-desktop:~/python/projects/gothonweb# python
Python 2.6.5 (r265:79063, Apr 16 2010, 13:09:56)
[GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pickle
>>> import base64
>>> base64.b64decode(open("sessions/9625c361e5430e79bd57f95747d8340a14c2ec2d").read())
"(dp1\nS'count'\np2\nI5\nsS'ip'\np3\nV127.0.0.1\np4\nsS'session_id'\np5\nS'9625c361e5430e79bd57f95747d8340a14c2ec2d'\np6\ns."
>>> x = base64.b64decode(open("sessions/9625c361e5430e79bd57f95747d8340a14c2ec2d").read())
>>> pickle.loads(x)
{'count': 5, 'ip': u'127.0.0.1', 'session_id': '9625c361e5430e79bd57f95747d8340a14c2ec2d'}
>>>


session其实就是一个数据字典,使用pickle和base64类库写到文件中的。有很多方法都可以写入这些数据,所以我们不需要非常了解他们的工作原理。

创建一个引擎
如果你已经写好地图和测试了,我现在教你写一个简单的游戏引擎去运行房间,从用户那里收集输入,并且追踪用户。我们使用刚刚学的session:
  1. 用新用户开始新游戏。
  2. 为用户呈现房间。
  3. 取得用户输入。
  4. 根据输入运行游戏。
  5. 展示结果,并继续游戏,直到死亡。
把bin/app.py修改成下面这样:
import web
from gothonweb import map

urls = (
  '/game', 'GameEngine',
  '/', 'Index',
)

app = web.application(urls, globals())

# little hack so that debug mode works with sessions
if web.config.get('_session') is None:
    store = web.session.DiskStore('sessions')
    session = web.session.Session(app, store,
                                  initializer={'room': None})
    web.config._session = session
else:
    session = web.config._session

render = web.template.render('templates/', base="layout")


class Index(object):
    def GET(self):
        # this is used to "setup" the session with starting values
        session.room = map.START
        web.seeother("/game")


class GameEngine(object):

    def GET(self):
        if session.room:
            return render.show_room(room=session.room)
        else:
            # why is there here? do you need it?
            return render.you_died()

    def POST(self):
        form = web.input(action=None)

        # there is a bug here, can you fix it?
        if session.room and form.action:
            session.room = session.room.go(form.action)

        web.seeother("/game")

if __name__ == "__main__":
    app.run()



整个游戏引擎就是这个小小的文件。最重要的就是session那几行代码,当你刷新页面时,session就会消失,游戏也不能工作了。

在运行app.py前我们必须改变我的PYTHONPATH环境变量。不知道是什么吗?我知道,它是运行基本python程序必须学习的,不过python程序员就喜欢这样。

在终端中输入:
export PYTHONPATH=$PYTHONPATH:.

如果是window的话输入:
$env:PYTHONPATH="$env:PYTHONPATH;."

如果你在运行python时有import错误,那么你就要运行上面的命令了。

下面你要删除templates/hello_form.html和templates/index.heml,然后建立上面提到的两个模板,这里是templates/show_room.html的代码:
$def with (room)

<h1> $room.name </h1>

<pre>
$room.description
</pre>

$if room.name == "death":
    <p><a href="/">Play Again?</a></p>
$else:
    <p>
    <form action="/game" method="POST">
        - <input type="text" name="action"> <input type="SUBMIT">
    </form>
    </p>



下面是templates/you_diad.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测试你的游戏,由于session的存在你可能只要点击几下游戏,但是基本的测试还是要做的。
  2. 运行游戏前先删除sessions文件夹。
  3. 运行python bin/app.py,测试你的游戏吧。
慢慢修正你的代码,直到游戏可以正常运行。

最后的练习
你是不是觉得一下接收了很多信息?对的,我希望你在学习技能的时候能够自己修改一些东西。完成这个练习后,我将给你最后一个你要自己完成的练习。你会发现你的初始版本比较烂,你的任务就是完善你的游戏:
  1. 修复所有的Bug。
  2. 改进你的自动化测试,让它能够测试所有东西,包括浏览器能测试的。
  3. 让你的HTML看起来更漂亮。
  4. 研究一下登陆系统,让用户能登陆,并且记录他们的高分。
  5. 完善你的游戏地图,做得更大更全。
  6. 给用户一个帮助系统,让用户知道在房间里能干什么
  7. 添加其他你能想到的东东。
  8. 创建多个地图,让用户可以选择哪个地图游戏。
  9. 最后,使用48和49章的知识改进输入系统。你已经有了大部分代码,只要和游戏引擎合并就好。
祝你好运!
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics