Created
January 30, 2014 01:00
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 '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