# Okay, so we're trying to make something where:
# 1. the player can play it, and it's a bit like a Ren'Py game.
# 2. an author can write it, and they (mostly?) write a data structure,
# not code.
#
# So we need a (terrible) little game to work with, as an example.
#
# Here's a description.
# On the first screen, you see some text about the game,
# and some text about two possible protagonists, Red and Blue,
# and you're offered a choice of who you want to play as.
# Regardless who you choose to play as, you go to the second screen.
# On the second screen, you see some text describing a generic crisis,
# and you're offered some choices of your response.
# There are four conceptually possible choices at this step.
# One is "clearly bad", and always available.
# One is an action that Red can do, only available if you chose to play as Red.
# One is an action that Blue can do, only available if you chose to play as
# Blue.
# One is a "kick the can down the road" option, and always available.
# If you choose the bad choice, you go to a "lose" ending screen.
# If you choose an action that only your character can do, you go to a "win"
# ending screen.
# If you choose to "kick the can down the road", you get another screen,
# and another choice.
# This time, there is only a clearly bad option and a mediocre option,
# and both lead to ending screens.
# From all the ending screens, you can start a new game.
#
# How might we express this example as a data structure?
#
# We could represent all the screens as a single dictionary.
# Then each screen has a description and some choices.
# Each choice has some consequences.
#
# When the modeling question gets tough, we "kick the can",
# by delegating to our future selves and saying that we will
# write some functions or classes that will build the
# objects that we want our author to use.
#
screens = {
'starting': {
'description': "This is an example game. Would you like to play as Red or as Blue?",
'choices': [
{ 'text': 'Play as Red', 'consequences': [ Set('protagonist', 'red'), Goto('crisis') ] },
{ 'text': 'Play as Blue', 'consequences': [ Set('protagonist', 'blue'), Goto('crisis') ] },
]
},
'crisis': {
'description': "A crisis has occurred. What do you do?",
'choices': [
{ 'text': 'Succumb', 'consequences': [ End('You succumb ignominiously.') ] },
{ 'text': 'Use my red personality', 'requires': [ Equal('protagonist', 'red') ],
'consequences': [ End('Your reddish personality averts the crisis!') ] },
{ 'text': 'Use my blue hair', 'requires': [ Equal('protagonist', 'blue') ],
'consequences': [ End('Your blueish hair averts the crisis!') ] },
{ 'text': 'Dilly-dally and delay', 'consequences': [ Goto('followon') ] },
]
},
'followon': {
'description': "The crisis has worsened. What do you do?",
'choices': [
{ 'text': 'Succumb', 'consequences': [ End('You succumb ignominiously.') ] },
{ 'text': 'Recover', 'consequences': [ End('You avert the crisis, but some cookies crumbled.') ] },
]
}
}
# Anyway, let's try writing a story engine that consumes this data structure.
def story_engine(story):
vars = { 'current_screen': 'starting', 'done': False }
while not vars['done']:
screen = screens[vars['current_screen']]
print screen['description']
choices = screen['choices']
for i in range(0, len(choices)):
choice = choices[i]
if choice_is_ok(choice, vars):
print "%d. %s" % (i, choice['text'])
# TODO: What if the player types a number that isn't on the list?
# TODO: What if the player types something that isn't a number?
# TODO: What if the list of choices is empty?
choice = int(raw_input("? "))
for consequence in screen['choices'][choice]['consequences']:
consequence.Run(vars)
# This "kicks the can" by requiring a function 'choice_is_ok",
# that deals with whether or not a choice is actually allowed.
# But that's not too hard:
def choice_is_ok(choice, vars):
if 'requires' not in choice:
return True
for requirement in choice['requires']:
if requirement.Evaluate(vars) == False:
return False
return True
# And the other things that we postponed are not too hard:
class Set:
def __init__(self, key, value):
self.key = key
self.value = value
def Run(self, vars):
vars[self.key] = self.value
class Goto:
def __init__(self, next_screen):
self.next_screen = next_screen
def Run(self, vars):
vars['current_screen'] = self.next_screen
class End:
def __init__(self, endingtext):
self.endingtext = endingtext
def Run(self, vars):
vars['done'] = True
print self.endingtext
class Equal:
def __init__(self, key, value):
self.key = key
self.value = value
def Evaluate(self, vars):
return vars[self.key] == self.value
# TODO: Add templating, so that the author can write a story that has
# lines like "%(loveinterest)s eyes you with %(emotion)s".
if __name__ == "__main__":
story_engine(screens)