Adventure Game 8: Making a Playable Game

Now we have a working game engine, we need to make the game more interesting.

We can do this in a number of ways.

  1. Think of an interesting setting: in a castle, on a spaceship, in a school for witches and wizards…
  2. What quest are we setting the player? To find the gold? Escape from the space station before it explodes? To find the spy?
  3. How about adding some characters: dragons, vampires, aliens, zombies?
  4. What sort of objects could we use? Magic wands, rayguns, teleporters?
  5. What sort of puzzles and riddles could we add?

Exercise

  1. Think of the setting for your game. What does the player have to do (what’s their quest?)
  2. Draw a map for your game (see lesson 1 for an example) Don’t add too many locations – 16 is more then enough.
  3. Think of the objects the player will have to use and add them to the map.
  4. Make a note of the game play. What will the player have to do to complete the game.
  5. Now modify the code of the game engine to write your own game.
  6. Get your partner to play the game and feedback.
  7. Make any improvements to the game.

Adventure Game 7: Robust Code

We now have a working game, however the code isn’t very robust. Robust code continues to function when errors occur.

Here’s an example of the start of a game:

A clearing in a forest
spanner
lockpick
What now?use spade
What now?use spoon
Traceback (most recent call last):
  File "M:/PycharmProjects/Adventure/ad6.py", line 81, in <module>
    Main()
  File "M:/PycharmProjects/Adventure/ad6.py", line 75, in Main
    if (objects[noun]==99): # check holding object
KeyError: 'spoon'

Process finished with exit code 1

There are two problems here, one minor, one major.

The minor problem occurs when the user types use spade. Nothing happens as the user isn’t holding a spade, but the program doesn’t alert the user to that fact.

The major problem occurs when the user tries to use spoon. At that point the program throws an exception and terminates.

An exception is an error that occurs when the program is running and causes the program to terminate. In this case, the program is trying to look up an object that doesn’t exist. We need to handle this exception.

The following code deals with both the above problems.

elif verb == "use":
            try:
                if (objects[noun]==99): # check holding object
                    use_object(noun)
                else:
                    print("I don't have a", noun)
            except:
                print("I don't know what a", noun, "is")

You will see try except in many pieces of python code. It’s the professional way to write robust code

Here’s the complete code. Copy and paste it into your IDE

places = ["A clearing in a forest", "An old wooden cabin", "A dark cave", "The top of a Hill", "Deep in the Forest", "An Underground Lake", "Caught in the Brambles"]
moves = [{"n": 1, "s": 2, "e":4},     {"s": 0, "e":3},       {"n": 0} ,     {"w": 1, "s":4},    {"n":3,"w":1, "e":6}, {"w": 2},             {"w": 4}]
objects = {"spanner":0,  "lockpick":0, "spade":2}
location = 0

def print_objects():
    for key, val in objects.items():
        if val == location:
            print(key)

def items():
    print("You are carrying: ")
    for key, val in objects.items():
        if val == 99:
            print(key)


def take_object(noun):
    for key, val in objects.items():
        if key == noun and val == location:
            print("Got it!")
            objects[noun] = 99

def drop_object(noun):
    for key, val in objects.items():
        if key ==noun and val == 99:
            print("Dropped ", noun)
            objects[noun] = location

def use_object(noun):
    if noun == "spade" and location == 0:
        objects["gold"] = 0 # create the gold
        print("You dug up some gold!")
    if noun == "spade" and location == 2:
        moves[2] = {"n": 0, "e":5}
        print("You've opened up a tunnel, leading east...")


def Main():
    ans = ""
    global location
    print(places[0])
    print_objects()


    while ans != "bye":
        ans = input("What now?")
        words = ans.split()

        # Check if it's a move
        if len(words) == 1:
            if ans == "items":
                items()
            elif ans == "look":
                print(places[location])
                print_objects()

            elif ans in moves[location]:
                location = moves[location].get(ans)
                print(places[location])
                print_objects()
            else:
                print("I can't move that way")
        else:
            verb = words[0] # e.g. Take or Drop
            noun = words[1] # e.g. hammer or spanner

            if verb == "take":
                take_object(noun)

            elif verb == "drop":
                drop_object(noun)

            elif verb == "use":
                try:
                    if (objects[noun]==99): # check holding object
                        use_object(noun)
                    else:
                        print("I don't have a", noun)
                except:
                    print("I don't know what a", noun, "is")

            else:
                print("I don't understand what you mean")

Main()

Exercise

  1. Copy the above code into your IDE and test it.
  2. Run the code. What happens if you try to take the spade in the clearing in the forest?
  3. Is the behaviour of the code here an error or an exception?
  4. Fix the code.
  5. What errors can occur when a user tries to drop an object? Fix those errors.
  6. What other errors can you find in the code?
  7. Are there any other exceptions you can find in the code?

Adventure Game 6: Using Objects

We’ve set up the basics of the game, but it’s not much fun. All you can do is wander round picking up and dropping objects. We need to allow the user to use the objects. We’re going to add two ways of using objects.

  • If the player uses the spade in the clearing in the forest they will dig up gold.
  • If the player uses the spade in the cave they will open a path to an underground lake.

Note that I’ve added new locations to the map, the same ones we added in Exercise 1

places = ["A clearing in a forest", "An old wooden cabin", "A dark cave", "The top of a Hill", "Deep in the Forest", "An Underground Lake", "Caught in the Brambles"]
moves = [{"n": 1, "s": 2, "e":4},     {"s": 0, "e":3},       {"n": 0} ,     {"w": 1, "s":4},    {"n":3,"w":1, "e":6}, {"w": 2},             {"w": 4}]
objects = {"spanner":0,  "lockpick":0, "spade":2}
location = 0

def print_objects():
    for key, val in objects.items():
        if val == location:
            print(key)

def items():
    print("You are carrying: ")
    for key, val in objects.items():
        if val == 99:
            print(key)


def take_object(noun):
    for key, val in objects.items():
        if key == noun and val == location:
            print("Got it!")
            objects[noun] = 99

def drop_object(noun):
    for key, val in objects.items():
        if key ==noun and val == 99:
            print("Dropped ", noun)
            objects[noun] = location

def use_object(noun):
    if noun == "spade" and location == 0:
        objects["gold"] = 0 # create the gold
        print("You dug up some gold!")
    if noun == "spade" and location == 2:
        moves[2] = {"n": 0, "e":5}
        print("You've opened up a tunnel, leading east...")


def Main():
    ans = ""
    global location
    print(places[0])
    print_objects()


    while ans != "bye":
        ans = input("What now?")
        words = ans.split()

        # Check if it's a move
        if len(words) == 1:
            if ans == "items":
                items()
            elif ans == "look":
                print(places[location])
                print_objects()

            elif ans in moves[location]:
                location = moves[location].get(ans)
                print(places[location])
                print_objects()
            else:
                print("I can't move that way")
        else:
            verb = words[0] # e.g. Take or Drop
            noun = words[1] # e.g. hammer or spanner

            if verb == "take":
                take_object(noun)

            elif verb == "drop":
                drop_object(noun)

            elif verb == "use":
                if (objects[noun]==99): # check holding object
                    use_object(noun)

            else:
                print("I don't understand what you mean")

Main()

Exercise

  1. Copy the code into your IDE. Move around the map to see the new locations
  2. Find the spade and use it to dig up gold in the forest
  3. Now dig in the cave and check to see the path to the lake opens up.
  4. Look at the function use_object(noun). What’s the purpose of the statement objects["gold"] = 0
  5. Look at the moves list (line 2). What has been added to the first dictionary in the list?
  6. Look at moves[2] (remember, this is the third item in the list). At the moment this is {“n”: 0}. What does that mean?
  7. Look at the function use_object(noun). What is the purpose of the line moves[2] = {"n": 0, "e":5} (hint, this is only called if the noun is a spade)
  8. Add the following objects to the map: telescope in the wooden cabin, cutters deep in the forest.
  9. Add code so if the user uses the telescope on top of the hill they see a message written on a sign saying “There is treasure in the forest”
  10. Add code so if the user uses the cutters when caught in the brambles, treasure appears.

Adventure Game 5: Adding New Commands and Tidying Old Ones

We’re going to add two new commands, Look and Items

  • Look will show your current location and objects
  • Items will show the items you’re currently carrying
places = ["A clearing in a forest", "An old wooden cabin", "A dark cave"]
moves = [{"n": 1, "s": 2},           {"s": 0},              {"n": 0}]
objects = {"spanner":0,  "lockpick":0, "spade":2}
location = 0

def print_objects():
    for key, val in objects.items():
        if val == location:
            print(key)

def items():
    print("You are carrying: ")
    for key, val in objects.items():
        if val == 99:
            print(key)


def take_object(noun):
    for key, val in objects.items():
        if key == noun and val == location:
            print("Got it!")
            objects[noun] = 99

def drop_object(noun):
    for key, val in objects.items():
        if key ==noun and val == 99:
            print("Dropped ", noun)
            objects[noun] = location


def Main():
    ans = ""
    global location
    print(places[0])
    print_objects()


    while ans != "bye":
        ans = input("What now?")
        words = ans.split()

        # Check if it's a one word input
        if len(words) == 1:
            if ans == "items":
                items()
            elif ans == "look":
                print(places[location])
                print_objects()
            elif ans in moves[location]:
                location = moves[location].get(ans)
                print(places[location])
                print_objects()
            else:
                print("I can't move that way")
        else:
            verb = words[0] # e.g. Take or Drop
            noun = words[1] # e.g. hammer or spanner

            if verb == "take":
                take_object(noun)

            elif verb == "drop":
                drop_object(noun)

            else:
                print("I don't understand what you mean")

Main()

Exercise

  1. Look at the Main() function. Why is location a global variable?
  2. List the one word commands that are permitted in this program
  3. What does the printobjects() function do?
  4. Look at the items() function. Why does it only print items where val =99?

You’re going to add a help function. If the user enters “help” the program will print the following instructions: “Type n,s,e,w to move north, south, east and west.”

  1. Add an elif statment under the elif ans == “look”: statement to check if the user has entered “help”
  2. Add code to print the help instructions

Adventure Game 4: Structured Programming

The program is becoming very complicated. We need to apply structured programming techniques to make it easier to follow. According to the specification this means:

“Using modularised programming, clear, well documented interfaces (local variables, parameters) and return values.”

Copy the following code into your IDE

places = ["A clearing in a forest", "An old wooden cabin", "A dark cave"]
moves = [{"n": 1, "s": 2},           {"s": 0},              {"n": 0}]
objects = {"spanner":0,  "lockpick":0, "spade":2}
location = 0

def print_objects():
    for key, val in objects.items():
        if val == location:
            print(key)

def take_object(noun):
    for key, val in objects.items():
        if key == noun and val == location:
            print("Got it!")
            objects[noun] = 99

def drop_object(noun):
    for key, val in objects.items():
        if key ==noun and val == 99:
            print("Dropped ", noun)
            objects[noun] = location


def Main():
    ans = ""
    global location
    print(places[0])
    print_objects()


    while ans != "bye":
        ans = input("What now?")
        words = ans.split()

        # Check if it's a move
        if len(words) == 1:            
            if ans in moves[location]:
                location = moves[location].get(ans)
                print(places[location])
                print_objects()
            else:
                print("I can't move that way")
        else:
            verb = words[0] # e.g. Take or Drop
            noun = words[1] # e.g. hammer or spanner

            if verb == "take":
                take_object(noun)

            elif verb == "drop":
                drop_object(noun)

            else:
                print("I don't understand what you mean")

Main()

Exercise

  1. Copy the code into an IDE and run it. Check that it works
  2. Give an example of a local variable in the code.
  3. Give an example of a global variable.
  4. Give an example of a parameter.
  5. How many functions have parameters passed to them?
  6. What are the advantages of the modular approach?
  7. Add a function eat_object(noun). The function will print “I can’t eat” + noun.
  8. Add code to the Main method to call the eat_object() function

Adventure Game 3: Take and Drop Objects

We added objects to the game in the last lesson. Now we’re going to add code to allow the user to take and drop objects

Copy and paste the following code into your IDE

places = ["A clearing in a forest", "An old wooden cabin", "A dark cave"]
moves = [{"n": 1, "s": 2},           {"s": 0},              {"n": 0}]
objects = {"spanner":0,  "lockpick":0, "spade":2}
location = 0

def print_objects():
    for key, val in objects.items():
        if val == location:
            print(key)

def Main():
    ans = ""
    global location
    print(places[0])
    print_objects()


    while ans != "bye":
        ans = input("What now?")
        words = ans.split()

        # Check if it's a move
        if len(words) == 1:
            if ans in moves[location]:
                location = moves[location].get(ans)
                print(places[location])
                print_objects()
            else:
                print("I can't move that way")
        else:
            verb = words[0] # e.g. Take or Drop
            noun = words[1] # e.g. hammer or spanner
            if verb == "take":
                for key, val in objects.items():
                    if key == noun and val == location:
                        print("Got it!")
                        objects[noun] = 99
            if verb == "drop":
                for key, val in objects.items():
                    if key ==noun and val == 99:
                        print("Dropped ", noun)
                        objects[noun] = location


Main()

Exercise

  1. Run the game. Take the spanner and drop it in the cave. Check that everything is working correctly.
  2. Add a spoon to the list of objects. Place the spoon in the cave. Check that you can Take it and Drop it.
  3. What happens if you enter “take elephant?”
  4. Look at the line words = ans.split() in the Main() function. What does it do? (try experimenting with the code in IDLE if you’re unsure)
  5. Look at the if statement in the Main() function: if len(words) == 1: Why was that code included?
  6. Look at the statement if verb == "take": What do key and val mean in the for loop?
  7. What is the purpose of the for loop?
  8. What is the purpose of the line 'objects[noun] == 99'
  9. Look at the if verb = "drop" statement. Why does the for loop check if key = noun and val == 99?

Adventure Game 2: Add Objects

Now we have the locations sorted, we need to add objects to the game. These will be things such as keys and chests. Later on we’ll add code so that the player can use these objects

Note how I’m only using 3 locations while I write this game. It’s always a good idea to keep things simple. Get the mechanics of the game working first, and then expand it.

Copy and paste the code below into your IDE.

places = ["A clearing in a forest", "An old wooden cabin", "A dark cave"]
moves = [{"n": 1, "s": 2},           {"s": 0},              {"n": 0}]
objects = {"spanner":0,  "lockpick":0, "spade":2}
location = 0

def print_objects():
    for key, val in objects.items():
        if val == location:
            print(key)

def Main():
    ans = ""
    global location
    print(places[0])
    print_objects()


    while ans != "bye":
        ans = input("What now?")
        if ans in moves[location]:
            location = moves[location].get(ans)
            print(places[location])
            print_objects()
        else:
            print("I can't move that way")


Main()

Exercise

  1. Look at the objects dictionary `objects = {“spanner”:0, “lockpick”:0, “spade”:2}`. What do the keys in the dictionary represent? (hint: the key is the part before the colon:)
  2. What do the values in the dictionary represent?
  3. Add “rope” to the list of objects. Place it in the cave.
  4. Add “torch” to the list of objects. Place it in the cabin.
  5. Run the code and check the objects are where you think they should be.
  6. Look at the print_objects() function. Explain how it works

Extension

  1. Draw your own map for a game. Implement it. Modify the code so that you are using your map.
  2. Add your own objects to the map.
  3. Add code so that if the user types “l” (for look) the game prints out the current location and a list of objects.

Adventure Game 1

About this game…

  • Moving around three locations

Copy the following code into your IDE and run it.

places = ["A clearing in a forest", "An old wooden cabin", "A dark cave"]
moves = [{"n": 1, "s": 2},           {"s": 0},              {"n": 0}]
location = 0

def Main():
    ans = ""
    global location
    print(places[0])

    while ans != "bye":
        ans = input("What now?")
        if ans in moves[location]:
            location = moves[location].get(ans)
            print(places[location])
        else:
            print("I can't move that way")


Main()

Exercise

  1. How do you exit the game?
  2. What sort of data structure has been used to store the places?
  3. What sort of data structure has been used to store the moves?
  4. What does the variable location do?
  5. Give some valid moves.
  6. How does the code check if a move is valid?
  7. Which line of code sets the new location?
  8. Look at the map below. Add the extra locations to the game.
  9. Run the code and check that it works.

Code is Poetry

Brian Bilston has written a History of Modern Art in Poetry.  I  wondered what it would be like to do something similar in various programming languages.

Here’s the original poem:

Roses are red
Violets are blue
Sugar is sweet
And so are you

Haskell

Here’s the poem constructed using a zip statement in Haskell

Prelude> zip ["roses","violets","sugar","you"]["red","blue","sweet","sweet"]
[("roses","red"),("violets","blue"),("sugar","sweet"),("you","sweet")]

The list produced holds the relationship that sugar is sweet and you are sweet. The comparison between “you” and sugar is not made clear.

Lisp

Here’s the poem stored as an alist in Lisp

(setq poem '(("roses" . "red") ("violets" . "blue") ("sugar" . "sweet")("you" . "sweet")))
(mapcar (lambda (x) (concat (car x) " are " (cdr x))) poem)

I’ve gone one stage further here, using a mapcar function to produce something that looks a little bit more like the original poem, however we’re still missing the connection between “you” and sugar.

("roses are red" "violets are blue" "sugar are sweet" "you are sweet")

Python

Of course, sugar are sweet isn’t right.   Let’s try some Python.

poem = {"roses":"red","violets":"blue","sugar":"sweet","you":"sweet"}

for key, value in poem.items():
    if key == "sugar":
        print(key, "is" ,value)
    else:
        print(key, "are", value)

This output is at least grammatically correct.

roses are red
violets are blue
sugar is sweet
you are sweet

Java

Java can do something similar using a HashMap

Map<String, String> poem = new HashMap<String, String>();

        poem.put("roses", "red");
        poem.put("violets", "blue");
        poem.put("sugar", "sweet");
        poem.put("you", "sweet");

        for (Map.Entry<String, String> entry : poem.entrySet()) {
            if(entry.getKey().equals("sugar")){
                System.out.println(entry.getKey() + " is " + entry.getValue());
            } else{
                System.out.println(entry.getKey() + " are " + entry.getValue());
            }
            
        }

But we’re still no closer to conveying the connection between “you” being sweet, just like sugar is sweet.

Fortunately, Java allows us to use some object oriented design to better convey the meaning of the poem.

In the example below I’ve used an interface to allow sweetness to be applied to both sugar and to the special one to whom the poem refers.  The comparison is at last made clear.  As there can only be one true love, it seemed reasonable to make a singleton class for TheOne, inherited from a regular person.

Run the code and the poem is printed out properly, just like the original.  More importantly though, the concepts to which the poem refers are properly encapsulated and related.

The original poem was only 4 lines long.  My implementation takes 80 lines, but I think you’ll agree I’ve done a rather better job, providing clarity and removing any ambiguity.

public class Love {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        Flower [] rose = new Flower[12]; // 12 roses in a bunch
        Flower [] violet = new Flower[30]; // more violets in bunch
        Sugar sugar = new Sugar();
        TheOne myLove = TheOne.getInstance();  // Singleton class
        // There can only be one true love
        
        rose[0] = new Flower();
        rose[0].setColour("red");  // colour is static so only need
                                    // to instantiate one here
        
        violet[0] = new Flower();
        violet[0].setColour("blue");
        
        System.out.println("Roses are " + rose[0].getColour());
        System.out.println("Violets are " + violet[0].getColour());
        System.out.println(sugar.sweet());
        System.out.println(myLove.sweet());
    }
    
}

class Flower {
    private static String colour;
    
    public void setColour(String colour){
        this.colour = colour;
    }
    
    public String getColour (){
        return colour;
    }
}

class Sugar implements Sweetness {

    @Override
    public String sweet() {
        return "Sugar is sweet";
    }
    
}

class Person {
    public String sweet()
    {
        return "Not sweet";
    }
}

class TheOne extends Person implements Sweetness{
    private static TheOne instance = null;
    
    private TheOne()
    {
        
    }
    
    public static TheOne getInstance()
    {
        if(instance == null)
            instance = new TheOne();
        
        return instance;
    }

    @Override
    public String sweet() {
         return "And so are you";
    }
}

interface Sweetness {
    String sweet();
}

Functions and Parameters (Level 6)

Sample Code

def hello(name):
    answer = "Hello " + name
    return answer

print(hello("George"))
print(hello("Gill"))
def AreaRect(length, width):
    return length*width

l = int(input("Enter the length"))
w = int(input ("Enter the width"))
print(AreaRect(l, w))
PI = 3.1415

def main():
    radius = 4
    print("The area of a circle radius ", radius, " is ", Area(radius))     


def Area(r):
    return PI*r*r

main()

Exercises

  1. Write a function that accepts a string and returns “Pleased to meet you, ” + string
  2. Write a function that accepts a number and returns “Child” if the number is <18 and “Adult” otherwise
  3. Write a function that accepts a number and returns “Grade A” if the number is >20, “Grade B” if the number is >15, “Grade C” if the number is >10 and “Fail” otherwise.
  4. Write a function that accepts two numbers and returns the average of the numbers
  5. Write a function that accepts three integers and returns the average of the numbers.
  6. Write a function that accepts the length and width of a rectangle and returns the perimeter of the rectangle
  7. Write a function that accepts the base and height of a triangle and returns the area of the triangle
  8. Write a function that accepts a list and returns the sum of the list

Extension

  1. Write a function that returns the hypotenuse of a triangle when the other two sides are int a and int b. (Remember: hypotenuse squared equals a squared plus b squared)
  2. The scalar product of u=(u1,u2,u3) and v=(v1,v2,v3) is defined to be u1v1+u2v2+u3v3. Write a function that accepts two int tuples as parameters and returns an int representing the scalar product of those two tuples
  3. If A = (a1,a2, …an) and B = (b1,b2, …bn) then the vector sum of the two tuples A + B = (a1+b1, a2+b2, … , an+bn). Write a function that accepts two tuples as parameters and returns an array representing the vector sum of those two tuples
  4. The Euclidean distance between two points A = (a1,a2, …an) and B = (b1,b2, …bn) is defined as sqrt((a1-b1)2 + (a2-b2)2 +… + (an-bn)2). Write a function that accepts two int tuples representing A and B as parameters and returns a double representing the Euclidean distance between them.