Exercise 52. The Start of Your Web Game

We’re coming to the end of the book, and in this exercise I’m going to really challenge you. When you’re done, you’ll be a reasonably competent Python beginner. You’ll still need to go through a few more books and write a couple more projects, but you’ll have the skills to complete them. The only obstacles will be time, motivation, and resources.

In this exercise, we won’t make a complete game, but instead we’ll make an “engine” that can run the game from Exercise 47 in the browser. This will involve refactoring Exercise 43, mixing in the structure from Exercise 47, adding automated tests, and finally creating a web engine that can run the games.

This exercise will be huge, and I predict you could spend anywhere from a week to months on it before moving on. It’s best to attack it in little chunks and do a bit a night, taking your time to make everything work before moving on.

Refactoring the Exercise 43 Game

You’ve been altering the gothonweb project for two exercises, and you’ll do it one more time in this exercise. The skill you’re learning is called “refactoring,” or as I like to call it, “fixing stuff.” Refactoring is a term programmers use to describe the process of taking old code and changing it to have new features or just to clean it up. You’ve been doing this without even knowing it, as it’s second nature to building software.

What you’ll do in this part is take the ideas from Exercise 47 of a testable “map” of Rooms and the game from Exercise 43 and combine them together to create a new game structure. It will have the same content, just “refactored” to have a better structure.

The first step is to grab the code from ex47/game.py, copy it to gothonweb/planisphere.py, copy the tests/ex47_tests.py file to tests/planisphere_tests.py, and run nosetests again to make sure it keeps working. The word “planisphere” is just a synonym for “map,” which avoids Python’s built-in map function. The thesaurus is your friend.


Warning!

From now on I won’t show you the output of a test run. Just assume that you should be doing it and it’ll look like the preceding unless you have an error.


Once you have the code from Exercise 47 copied over, it’s time to refactor it to have the Exercise 43 map in it. I’m going to start off by laying down the basic structure, and then you’ll have an assignment to make the planisphere.py file and the planisphere_tests.py file complete.

Lay out the basic structure of the map using the Room class as it is now:

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

You’ll notice that there are a couple of problems with our Room class and this map:

1. We have to put the text that was in the if-else clauses that got printed before entering a room as part of each room. This means you can’t shuffle the planisphere around, which would be nice. You’ll be fixing that up in this exercise.

2. There are parts in the original game where we ran code that determined things like the bomb’s keypad code or the right pod. In this game we just pick some defaults and go with it, but later you’ll be given Study Drills to make this work again.

3. I’ve just made a generic_death ending for all of the bad decisions, which you’ll have to finish for me. You’ll need to go back through and add in all the original endings and make sure they work.

4. I’ve got a new kind of transition labeled "*" that will be used for a “catch-all” action in the engine.

Once you’ve got that basically written out, here’s the new automated test, tests/planisphere_test.py, that you should have to get yourself started:

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)

Your task in this part of the exercise is to complete the map and make the automated test completely validate the whole map. This includes fixing all the generic_death objects to be real endings. Make sure this works really well and that your test is as complete as possible because we’ll be changing this map later, and you’ll use the tests to make sure it keeps working.

Creating an Engine

You should have your game map working and a good unit test for it. I now want you to make a simple little game engine that will run the rooms, collect input from the player, and keep track of where a player is in the game. We’ll be using the sessions you just learned to make a simple game engine that will do the following:

1. Start a new game for new users.

2. Present the room to the user.

3. Take input from the user.

4. Run user input through the game.

5. Display the results and keep going until the user dies.

To do this, you’re going to take the trusty app.py you’ve been hacking on and create a fully working, session-based game engine. The catch is I’m going to make a very simple one with basic HTML files, and it’ll be up to you to complete it. Here’s the base engine:

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 values
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(room)
33                else:
34                    session['room_name'] = planisphere.name_room(next_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()

There are even more new things in this script, but amazingly it’s an entire web-based game engine in a small file. Before you run app.py you need to change your PYTHONPATH environment variable. Don’t know what that is? I know, it’s kind of dumb, but you have to learn what this is to run even basic Python programs: that’s how Python people like things.

In your Terminal, type:

export PYTHONPATH=$PYTHONPATH:.

On Windows PowerShell do:

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

You should only have to do it once per shell session, but if you get an import error, then you probably need to do this or you did it wrong.

You should next delete templates/hello_form.html and templates/index.html and create the two templates mentioned in the preceding code. Here’s a very simple 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 %}

That is the template to show a room as you travel through the game. Next you need one to tell someone they died in the case that they got to the end of the map on accident, which is templates/you_died.html:

you_died.html


<h1>You Died!</h1>

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

With those in place, you should now be able to do the following:

1. Get the test tests/app_tests.py working again so that you are testing the game. You won’t be able to do much more than a few clicks in the game because of sessions, but you should be able to do some basics.

2. Run the python3.6 app.py script and test out the game.

You should be able to refresh and fix the game like normal. You should also be able to work with the game HTML and engine until it does all the things you want it to do.

Your Final Exam

Do you feel like this was a huge amount of information thrown at you all at once? Good, I want you to have something to tinker with while you build your skills. To complete this exercise, I’m going to give you a final set of tasks for you to complete on your own. You’ll notice that what you’ve written so far isn’t very well built; it is just a first version of the code. Your job now is to make the game more complete by doing these things:

1. Fix all the bugs I mention in the code and any that I didn’t mention. If you find new bugs, let me know.

2. Improve all of the automated tests so that you test more of the application, and get to a point where you use a test rather than your browser to check the application while you work.

3. Make the HTML look better.

4. Research logins and create a signup system for the application so people can have logins and high scores.

5. Complete the game map, making it as large and feature-complete as possible.

6. Give people a “help” system that lets them ask what they can do at each room in the game.

7. Add any other features you can think of to the game.

8. Create several “maps” and let people choose a game they want to run. Your app.py engine should be able to run any map of rooms you give it, so you can support multiple games.

9. Finally, use what you learned in Exercises 48 and 49 to create a better input processor. You have most of the code necessary; you just need to improve the grammar and hook it up to your input form and the GameEngine.

Good luck!

Common Student Questions

I’m using sessions in my game, and I can’t test it with nosetests. Read the Flask Testing Documentation about “Other Testing Tricks” (http://flask.pocoo.org/docs/0.12/testing/#other-testing-tricks) for information on creating fake sessions inside your tests.

I get an ImportError. It could be one or more of these: wrong directory, wrong Python version, PYTHONPATH not set, no __init__.py file, and/or spelling mistake in import.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.14.168.56