Skip to content

Instantly share code, notes, and snippets.

@macloo
Created January 30, 2014 01:00
Show Gist options
  • Save macloo/8700645 to your computer and use it in GitHub Desktop.
Save macloo/8700645 to your computer and use it in GitHub Desktop.
Three iterations of a text-based game to learn object-oriented techniques in Python. Based on exercise 43 in Zed Shaw's book. I did 7 distinct iterations but there's no reason to show all of them. Probably this code is bad in many ways. But each one of these runs without errors.
# Exercise 43: Basic Object-Oriented Analysis and Design
# http://learnpythonthehardway.org/book/ex43.html
# STAGE 1 - this works
# only classes Map and Engine are really built
# each Scene child-class can be run
# start w/ skeleton from Zed, then build it out
class Scene(object):
def enter(self):
print self.name # works
print self.descrip # works
# this applies to all classes under Scene, but Zed does it differently
class Engine(object):
def __init__(self, scene_map):
self.scene_map = scene_map
# gets the game's map from instance "mymap," at bottom of this file
def play(self):
current_scene = self.scene_map.opening_scene()
# see the Map object: runs function named opening_scene()
# this runs only once
# this STARTS THE GAME
# this (below) is supposed to be an infinite loop (but mine is not)
print "\n--------"
current_scene.enter()
# use later:
# current_scene = self.scene_map.next_scene(next_scene_name)
class Death(Scene):
# def enter(self):
# pass
name = "You have died!"
descrip = ''' Your spirit leaves swiftly as your body collapses.'''
class CentralCorridor(Scene):
# def enter(self):
# print "You entered Corridor."
name = "Central Corridor"
descrip = ''' A broad passage extends in front of and behind you.'''
class LaserWeaponArmory(Scene):
name = "Laser Weapon Armory"
descrip = ''' Shelves and cases line the walls of this room.
Weapons of every description fill the shelves and cases.
There is a digital keypad set into the wall.'''
class TheBridge(Scene):
name = "The Bridge"
descrip = ''' Clearly this is a central command station of the
spaceship. A wide viewscreen shows the stars against a black
curtain of empty space.'''
class EscapePod(Scene):
name = "Escape Pod"
descrip = ''' Set into the wall are several closed and locked
hatch doors. Each one leads to an escape pod.'''
# Map tells us where we are and where we can go
# it does not make us move - Engine does that
class Map(object):
scenes = {
'death' : Death(),
'corridor' : CentralCorridor(),
'armory' : LaserWeaponArmory(),
'bridge' : TheBridge(),
'pod' : EscapePod()
}
# above is a dictionary that maps all our scene classes to strings
# note, we never have to instantiate those classes (why?)
def __init__(self, start_scene_key):
self.start_scene_key = start_scene_key
# above we make a local var named start_scene_key
# this is a string, same as the arg we passed in ('corridor')
# start_scene_key remains unchanged throughout the game
def next_scene(self, scene_name):
val = Map.scenes.get(scene_name)
# above is how we get value out of the dictionary named scenes
return val
# Zed does not have this return
# this function can be called repeatedly in the game,
# unlike opening_scene, which is called only ONCE
def opening_scene(self):
return self.next_scene(self.start_scene_key)
# this function exists only for starting, using the first
# string we passed in ('corridor')
# it combines the previous 2 functions and is called only once
# (called in Engine)
'''
The three functions above are so circular, they confuse me. But the logic is this: 1) initialize the Map with a scene, passed in as a string, which gets the class name out of the list, "scenes." 2) That first location is used ONLY by the opening_scene() function, which in turn CALLS the middle function, next_scene(). 3) next_scene() is the function that will run again and again, always pulling the class name out of the list, "scenes."
Question: Shouldn't the variable current_scene, which live in Engine, instead be here, in Map?
'''
print "\nType one of the following words: pod, corridor, bridge, armory, death."
seed = raw_input("> ")
# this is for testing only - get the "seed" for the map
mymap = Map(seed) # instantiate a new Map object w/ one arg
mygame = Engine(mymap) # instantiate a new Engine object w/ one arg
mygame.play() # call function from that Engine instance
# Exercise 43: Basic Object-Oriented Analysis and Design
# http://learnpythonthehardway.org/book/ex43.html
# STAGE 3 - this works - no errors
# now I can go from scene to scene & always return to Engine when done
# infinite loop now running in Engine
from sys import exit # for quitting the game
class Scene(object):
def enter(self):
print self.name # works
print self.descrip # works
# this applies to all classes under Scene, but Zed does it differently
# seems to me this is more efficient - every scene prints its name & descrip
class Engine(object):
def __init__(self, scene_map):
self.scene_map = scene_map
# gets the game's map from instance "mymap," at bottom of this file
def play(self):
current_scene = self.scene_map.opening_scene()
# see the Map object: this runs function named opening_scene()
# this runs only once
# this STARTS THE GAME
while True: # infinite loop to run the game
print "\n--------"
current_scene.enter() # from Scene
# note: will throw error if no new scene passed in by next line:
next_scene_name = current_scene.action()
# get the name of the next scene from the action() function that
# runs in the current scene - what it returns
print "Returned to Engine."
# this prints after running the current scene
# it's just a placeholder at this stage
current_scene = self.scene_map.next_scene(next_scene_name)
# here we use that val returned by current scene to go to
# the next scene, running function in Map
'''
Note: as long as every action() in every scene returns a string
that exists in the dictionary "scenes" in Map, the loop above
will run w/o errors. So any sequence in action() that takes you
to another room must end with: return 'sometext'
'''
class Death(Scene):
name = "You have died!"
descrip = ''' Your spirit leaves swiftly as your body collapses.\n'''
def action(self):
exit() # this is Death, so game over
class CentralCorridor(Scene):
name = "Central Corridor"
descrip = ''' A broad passage extends in front of and behind you.
The are doors to your left and right. The is a ladder going up.'''
def action(self):
while True:
response = raw_input("> ")
if "look" in response:
print self.descrip
elif "up" in response:
return 'bridge'
elif response != "":
print "Huh? I didn't understand that."
else:
print "Something went wrong ..."
return 'death'
class LaserWeaponArmory(Scene):
name = "Laser Weapon Armory"
descrip = ''' Shelves and cases line the walls of this room.
Weapons of every description fill the shelves and cases.
There is a digital keypad set into the wall.'''
def action(self):
while True:
response = raw_input("> ")
if "look" in response:
print self.descrip
elif response != "":
print "Huh? I didn't understand that."
else:
print "Something went wrong ..."
return 'death'
class TheBridge(Scene):
name = "The Bridge"
descrip = ''' Clearly this is a central command station of the
spaceship. A wide viewscreen shows the stars against a black
curtain of empty space. There is a ladder going down.'''
def action(self):
while True:
response = raw_input("> ")
if "look" in response:
print self.descrip
elif "down" in response:
return 'corridor'
elif response != "":
print "Huh? I didn't understand that."
else:
print "Something went wrong ..."
return 'death'
class EscapePod(Scene):
name = "Escape Pod"
descrip = ''' Set into the wall are several closed and locked
hatch doors. Each one leads to an escape pod.'''
def action(self):
while True:
response = raw_input("> ")
if "look" in response:
print self.descrip
elif response != "":
print "Huh? I didn't understand that."
else:
print "Something went wrong ..."
return 'death'
# Map tells us where we are and where we can go
# it does not make us move - Engine does that
class Map(object):
scenes = {
'death' : Death(),
'corridor' : CentralCorridor(),
'armory' : LaserWeaponArmory(),
'bridge' : TheBridge(),
'pod' : EscapePod()
}
# above is a dictionary that maps all our scene classes to strings
# note, we never have to instantiate those classes (why?)
def __init__(self, start_scene_key):
self.start_scene_key = start_scene_key
# above we make a local var named start_scene_key
# this is a string, same as the arg we passed in ('corridor')
# start_scene_key remains unchanged throughout the game
def next_scene(self, scene_name):
val = Map.scenes.get(scene_name)
# above is how we get value out of the dictionary named scenes
return val
# Zed does not have this return
# this function can be called repeatedly in the game,
# unlike opening_scene, which is called only ONCE
def opening_scene(self):
return self.next_scene(self.start_scene_key)
# this function exists only for starting, using the first
# string we passed in ('corridor')
# it combines the previous 2 functions and is called only once
# (called in Engine)
print "\nType one of the following words: pod, corridor, bridge, armory, death."
seed = raw_input("> ")
# this is for testing only - get the "seed" for the map
mymap = Map(seed) # instantiate a new Map object w/ one arg
mygame = Engine(mymap) # instantiate a new Engine object w/ one arg
mygame.play() # call function from that Engine instance
# Exercise 43: Basic Object-Oriented Analysis and Design
# http://learnpythonthehardway.org/book/ex43.html
# STAGE 7
# escape pod complete
# added timer to announce impending doom like Star Trek auto-destruct
'''
Aliens have invaded a space ship and our hero has to go through a maze of rooms defeating them so he can escape into an escape pod to the planet below. The game will be more like a Zork or Adventure type game with text outputs ... The game will involve an engine that runs a map full of rooms or scenes. Each room will print its own description when the player enters it and then tell the engine what room to run next out of the map.
'''
from sys import exit # for quitting the game
from random import randint
import time # for the countdown
# I had to make global vars for the bomb because it moves from room to room
# at least I think I had to do it this way
# and the timer also needs to be used in more than one room
bomb_with_hero = False
bomb_armed = False
ticks = time.time()
class Scene(object):
def enter(self):
print self.name # works
print self.descrip # works
# this applies to all classes under Scene, but Zed does it differently
# seems to me this is more efficient - every scene prints its name & descrip
class Engine(object):
def __init__(self, scene_map):
self.scene_map = scene_map
# gets the game's map from instance "mymap," at bottom of this file
def play(self):
current_scene = self.scene_map.opening_scene()
# see the Map object: this runs function named opening_scene()
# this runs only once
# this STARTS THE GAME
while True: # infinite loop to run the game - repeats until exit()
print "\n--------"
current_scene.enter() # from Scene
# note: will throw error if no new scene passed in by next line:
next_scene_name = current_scene.action()
# get the name of the next scene from the action() function that
# runs in the current scene - what it returns
current_scene = self.scene_map.next_scene(next_scene_name)
# here we use that val returned by current scene to go to
# the next scene, running function in Map
class Bomb(object):
def __init__(self):
self.present = True # this allows us to remove the bomb
# changed this to a global variable at top --
# self.armed = False # not set to explode yet
def takebomb(self):
while True:
response = raw_input("> ")
if "case" in response or "open" in response:
print "You open the case and look inside. Yep. It's a bomb!"
print "You close the case. It has a convenient handle for carrying."
elif "take" in response or "pick up" in response:
print "You pick up the case by its handle. It is not too heavy."
print "Thank goodness -- because you are not in the best of shape."
self.present = False
global bomb_with_hero
bomb_with_hero = True # now bomb is being carried
return
elif "arm" in response or "set" in response:
print "I don't think you want to do that yet."
elif "bomb" in response:
print "Do you want to do something with the bomb?"
else:
print "Huh? What?"
def setbomb(self):
print "You set the case down and open it. You see a big switch "
print 'marked "On/Off." It is set to "Off." You flip the switch!'
global bomb_armed
bomb_armed = True
global ticks
ticks = time.time() # this changes ticks to the time when bomb is set
print
print 'The timer now reads "00:00:30."'
print "I think you'd better hurry to the escape pod!"
print 'The timer now reads "00:00:29."'
global bomb_with_hero
bomb_with_hero = False # now bomb is not being carried
return
class Countdown(object):
def announce(self, basetime):
nowticks = time.time()
timeleft = 30 - int(nowticks - basetime)
global bomb_armed
if bomb_armed == True and timeleft > 0:
print 'The ship\'s computer announces: "The explosive device will '
print 'detonate in %d seconds."' % timeleft
elif bomb_armed == True:
print "The ship explodes into a quadrillion pieces! "
print "Your mortal remains are flung to the far corners of the universe!"
# return 'death'
else:
pass
class Alien(object):
def __init__(self):
self.present = True
self.stamina = 10
def report(self, s):
if s > 8:
print "The alien is strong! It resists your pathetic attack!"
elif s > 5:
print "With a loud grunt, the alien stands firm."
elif s > 3:
print "Your attack seems to be having an effect! The alien stumbles!"
elif s > 0:
print "The alien is certain to fall soon! It staggers and reels!"
else:
print "That's it! The alien is finished!"
def fight(self, stam, p): # stamina and present
while p == True:
response = raw_input("> ")
# fight scene
if "hit" in response or "attack" in response:
less = randint(0, stam)
stam -= less # subtract random int from stamina
self.report(stam) # see above
if stam <= 0:
p = False
return p, 'corridor'
# you end up back in corridor even if fight is on bridge
# because I am lazy
else:
pass
elif "fight" in response:
print "Fight how? You have no weapons, silly space traveler!"
else:
print "The alien zaps you with its powerful ray gun!"
return p, 'death' # new, lowered stamina number
class Death(Scene):
name = "You have died!"
descrip = ''' Your spirit leaves swiftly as your body collapses.\n'''
def action(self):
exit() # this is Death, so game over
# the only other exit is in EscapePod()
class CentralCorridor(Scene):
'''
A Gothon (alien) is already standing here. Must be defeated
before continuing.
'''
def __init__(self):
self.alien = Alien()
# initialize the corridor scene with an alien present
name = "Central Corridor"
descrip = ''' A broad passage extends in front of and behind you.
The are doors to your left and right. The is a ladder going up.'''
def action(self):
# -----
# shortcut to pod scene - 3 lines
# global bomb_armed
# bomb_armed = True
# self.alien.present = False
# -----
if self.alien.present:
print " An alien is here."
self.alien.present, location = self.alien.fight(self.alien.stamina, self.alien.present)
# catch the returns from fight() in Alien -
# pass in stamina and present, get out present and scene name
return location
else:
while True:
response = raw_input("> ")
if "look" in response:
print self.descrip
elif "up" in response:
return 'bridge'
elif "right" in response:
return 'armory'
elif "left" in response:
return 'pod'
elif response != "":
print "Huh? I didn't understand that."
else:
print "Something went wrong ..."
return 'death'
class LaserWeaponArmory(Scene): # keypad works!
'''
This is where the hero gets a neutron bomb to blow up the ship before
getting to the escape pod. It has a keypad he has to guess the number for.
'''
def __init__(self):
self.doorlocked = True # self.door.locked threw an error
self.keycode = randint(1, 9) * 111 # 3 of the same number
self.bomb = Bomb()
# initialize the armory scene with door locked and bomb here
name = "Laser Weapon Armory"
descrip = ''' The door to this room is closed and locked.
There is a digital keypad set into the wall.'''
descrip2 = ''' Shelves and cases line the walls of this room.
Weapons of every description fill the shelves and cases. '''
def action(self):
global bomb_with_hero # lets Python know we will use this
if self.doorlocked == True:
self.keypad()
while True: # this works - keypad() returns to here
response = raw_input("> ")
if "look" in response:
print self.descrip
elif "bomb" in response and self.bomb.present:
print "Searching the shelves, you discover a small red case."
print 'On the case is a label: "Neutron Bomb."'
self.bomb.takebomb()
elif "bomb" in response and bomb_with_hero == True:
print "You are carrying the bomb."
elif "leave" in response or "exit" in response:
return 'corridor'
elif response != "":
print "Huh? I didn't understand that."
else:
print "Something went wrong ..."
return 'death'
# this should probably not be infinite - probably should have range instead
# it does not let you out till you get it right
# added exit & leave as options
def keypad(self):
while self.doorlocked == True:
print "The keypad has 9 buttons with numbers from 1 to 9."
print "3 numbers must be entered to unlock the door."
response = raw_input("> ")
if "leave" in response or "exit" in response:
return 'corridor'
elif not response.isdigit() or (int(response) > 999 or int(response) < 100):
print "That is not a suitable number. Try again."
elif int(response) == self.keycode:
self.doorlocked = False
print "The door slides smoothly and quietly open."
self.descrip = self.descrip2 # switches the description text
print self.descrip
elif int(response) > self.keycode:
print "That number is too high."
elif int(response) < self.keycode:
print "That number is too low."
else:
"No good. Try again with 3 numbers."
class TheBridge(Scene):
'''
Another battle scene with a Gothon before the hero can place the bomb here.
'''
def __init__(self):
self.alien = Alien()
# initialize the corridor scene with an alien present
# I can't initialize a bomb because one is not here yet
# exactly the same alien as we saw in corridor
name = "The Bridge"
descrip = ''' Clearly this is a central command station of the
spaceship. A wide viewscreen shows the stars against a black
curtain of empty space. There is a ladder going down.'''
def action(self):
if self.alien.present:
print " Another alien is here!"
self.alien.present, location = self.alien.fight(self.alien.stamina, self.alien.present)
# catch the returns from fight() in Alien - same as fight in
# corridor
return location
else:
while True:
response = raw_input("> ")
if "look" in response:
print self.descrip
elif "down" in response:
return 'corridor'
elif "bomb" in response and bomb_with_hero == True:
self.bomb = Bomb() # create a Bomb object here
self.bomb.setbomb()
elif "bomb" in response and self.bomb.present:
print "That bomb is set to blow! Get out!!"
# the order above is very important
elif response != "":
print "Huh? I didn't understand that."
else:
print "Something went wrong ..."
return 'death'
class EscapePod(Scene):
'''
Where the hero escapes, but only after guessing the right escape pod.
'''
name = "Escape Pod"
descrip = ''' Set into the wall are several closed and locked
hatch doors. Each one leads to an escape pod. The pods are
numbered 1 through 5.'''
def action(self):
podnum = randint(1, 5)
global ticks
self.countdown = Countdown()
while True:
# bomb timer announcement here
self.countdown.announce(ticks)
response = raw_input("> ")
if "look" in response:
print self.descrip
elif "open" in response:
choice = int(raw_input("Which pod? "))
if choice == podnum:
self.pod_eject()
elif choice > 5:
print "There are only 5 pods, silly!"
else:
print "That hatch seems to be jammed."
elif "leave" in response or "exit" in response:
return 'corridor'
elif response != "":
print "Huh? I didn't understand that."
else:
print "Something went wrong ..."
return 'death'
def pod_eject(self):
print "Ejected! You are safe!"
global bomb_armed
if bomb_armed == True:
print "Your ship explodes in a quadrillion pieces, flinging "
print "alien body parts to the far corners of the universe!"
print "Safe in your cozy pod, you fly away to a nice planet.\n"
exit() # only exits in the game besides Death()
else:
print "Um ... did you forget something? The aliens are firing "
print "torpedoes at you from your own ship! Aaaiiieeee --"
print "That is the end of you!\n"
exit() # only exits in the game besides Death()
# Map tells us where we are and where we can go
# it does not make us move - Engine does that
class Map(object):
scenes = {
'death' : Death(),
'corridor' : CentralCorridor(),
'armory' : LaserWeaponArmory(),
'bridge' : TheBridge(),
'pod' : EscapePod()
}
# above is a dictionary that maps all our scene classes to strings
# note, we never have to instantiate those classes (why?)
def __init__(self, start_scene_key):
self.start_scene_key = start_scene_key
# above we make a local var named start_scene_key
# this is a string, same as the arg we passed in ('corridor')
# start_scene_key remains unchanged throughout the game
def next_scene(self, scene_name):
val = Map.scenes.get(scene_name)
# above is how we get value out of the dictionary named scenes
return val
# Zed does not have this return
# this function can be called repeatedly in the game,
# unlike opening_scene, which is called only ONCE
def opening_scene(self):
return self.next_scene(self.start_scene_key)
# this function exists only for starting, using the first
# string we passed in ('corridor')
# it combines the previous 2 functions and is called only once
# (called in Engine)
mymap = Map('corridor') # instantiate a new Map object w/ one arg
mygame = Engine(mymap) # instantiate a new Engine object w/ one arg
mygame.play() # call function from that Engine instance
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment