Skip to content

Instantly share code, notes, and snippets.

@WilliamBundy
Created November 1, 2015 04:59
Show Gist options
  • Save WilliamBundy/ae62a2cad73a4504049e to your computer and use it in GitHub Desktop.
Save WilliamBundy/ae62a2cad73a4504049e to your computer and use it in GitHub Desktop.
#!/usr/env python3
game = """
Quick--do you have an apple?
{switch}
{choice 1 "Yeah, I do"}
{choice 2 "*pats pockets* Nope"}
{case 1} {do "set({'apple'}, 'player', 'items')"} {goto "You run into John Appleseed"}
{case 2} {goto "You run into John Appleseed"}
{label "You run into John Appleseed"}
{addactor $playerName align=right}
{addactor John align=left}
Hello
Can you help?
{switch}
{choice 1 "I can help"}
{choice 2 "I can't help"}
{case 1} {goto "That's great!"}
{case 2} {goto "Are you sure?"}
if you see this, there's a problem
{label "Are you sure?"}
{switch}
{choice 1 "I'm sure"}
{choice 2 "I'm not sure"}
{case 1} Oh, sorry. I won't bother you further. {close}
{case 2} {goto "That's great!"}
{label "That's great!"}
I need an apple.
{if 'apple' in get('player', 'items')}
{actor $playerName} Here you go.
{actor John} Thanks!
{do scene['actor'] = None; get('player')['gold'] += 10}
He gives you 10 gold.
{close}
{end}
Can you get one for me?
{switch}
{choice 1 Yes}
{choice 2 No}
{case 1} Thanks; good luck! {close}
{case 2} It's okay, don't worry about it. {close}
"""
OpenTag = "{"
CloseTag = "}"
class DialogueTree:
def __init__(self, raw):
self.raw = raw
self.width = 40
self.tags = {}
self.tagindex = 0
self.parse()
self.actions = {}
def action_addactor(state, tag):
name = tag["contents"][0]
if name.startswith("$"):
name = state["variables"][name[1:]]
actor = {"name": name, "align": "left"}
for item in tag["contents"][1:]:
if "=" in item:
words = item.split("=")
lenw = len(words)
if lenw == 2:
actor[words[0]] = words[1]
elif lenw >= 3:
actor[words[0]] = words[1:]
state["actors"][name] = actor
state["actor"] = actor
self.actions["addactor"] = action_addactor
def action_setactor(state, tag):
name = tag["contents"][0]
if name.startswith("$"):
name = state["variables"][name[1:]]
state["actor"] = state["actors"][name]
self.actions["actor"] = action_setactor
def action_close(scene, tag):
scene["variables"]["quit"] = True
self.actions["close"] = action_close
def action_switch(scene, tag):
choices = []
while True:
scene["i"] += 1
token = self.get_token(scene)
if token.startswith("$"):
tag = self.tags[token[1:]]
if tag["action"] != "choice":
break
else:
choices.append(self.tags[self.parsed[scene["i"]][1:]])
trying = True
value = -1
while trying:
for choice in choices:
parts = choice["contents"]
print(" {0}. {1}".format(str(parts[0]), parts[1]))
line = input("Choose a number: ")
try:
value = int(line)
trying = False
except ValueError:
print("That's not a valid choice")
continue
# now, jump to [case value]
while True:
if self.get_token(scene).startswith("$case"):
if int(self.tags[self.get_token(scene)[1:]]["contents"][0]) == value:
scene["i"] -= 1
#print(self.get_token(scene))
return
scene["i"] += 1
#this'll fail eventually if it can't find the case
self.actions["switch"] = action_switch
def action_label(scene, tag):
print(self.format_line(tag["contents"], scene))
self.actions["label"] = action_label
def action_goto(scene, tag):
scene["i"] += 1
looped = False
while True:
token = self.get_token(scene)
if token.startswith("$label"):
localtag = self.tags[token[1:]]
if localtag["contents"] == tag["contents"]:
scene["i"] -= 2
return
scene["i"] += 1
if scene["i"] > len(self.parsed):
if looped:
print("Error, could not find goto:", tag["contents"])
return
scene["i"] = 0
looped = True
self.actions["goto"] = action_goto
def action_if(scene, tag):
def get(*args):
last = scene["variables"]
for arg in args[:-1]:
last = last[arg]
return last[args[-1]]
def set(v, *args):
last = scene["variables"]
for arg in args[:-1]:
last = last[arg]
last[args[-1]] = v
val = eval(" ".join(tag["contents"]))
if val:
return
else:
while True:
token = self.get_token(scene)
if token.startswith("$else"):
return
elif token.startswith("$end"):
return
scene["i"] += 1
self.actions["if"] = action_if
def action_do(scene, tag):
def get(*args):
last = scene["variables"]
for arg in args[:-1]:
last = last[arg]
return last[args[-1]]
def set(v, *args):
last = scene["variables"]
for arg in args[:-1]:
last = last[arg]
last[args[-1]] = v
exec(" ".join(tag["contents"]))
self.actions["do"] = action_do
def get_token(self, scene):
return self.parsed[scene["i"]]
def parse(self):
lines = self.raw.split("\n")
parsed = []
mode = "text"
tag = None
string = None
for line in lines:
words = line.split(" ")
for word in words:
word = word.strip()
if tag == None:
if word.startswith(OpenTag):
tag = {}
tag["action"] = word[1:] if not word.endswith(CloseTag) else word[1:-1]
tag["action"] = tag["action"].lower()
tag["id"] = "{0}_{1}".format(tag["action"], self.tagindex)
parsed.append("$" +tag["id"])
self.tagindex += 1
if word.endswith(CloseTag):
self.tags[tag["id"]] = tag
tag = None
else:
tag["contents"] = []
else:
parsed.append(word)
else:
if word.endswith(CloseTag):
if string != None:
if word[:-1].endswith("\""):
string = "{0} {1}".format(string, word[:-2])
else:
#it's just part of the string
string = "{0} {1}".format(string, word)
continue
tag["contents"].append(string)
string = None
else:
tag["contents"].append(word[:-1])
self.tags[tag["id"]] = tag
tag = None
elif word.startswith("\""):
#it's a string -- could be the start, or
#something like "My name ends with a space "
#
if string == None:
string = word[1:] if not word.endswith("\"") else word[1:-1]
if word.endswith("\""):
tag["contents"].append(string)
string = None
elif word.endswith("\""):
tag["contents"].append(string + word[:-1])
string = None
elif string != None:
if word.endswith("\""):
tag["contents"].append("{0} {1}".format(string, word[:-1]))
string = None
else:
string = "{0} {1}".format(string, word)
else:
tag["contents"].append(word)
parsed.append("\n")
self.parsed = parsed
#print(" ".join(parsed))
def format_line(self, line, scene):
chars = " ".join(line)
if "actor" in scene and scene["actor"]:
chars = "{0}: {1}".format(scene["actor"]["name"], chars)
if scene["actor"]["align"] == "right":
chars = "{0}{1}".format(" " * (self.width - len(chars)), chars)
elif scene["actor"]["align"] == "center":
chars = "{0}{1}".format(" " * ((self.width - len(chars)) / 2), chars)
return chars
def interpret(self, variables):
if self.parsed == None: self.parse()
scene = {}
scene["variables"] = variables
scene["actors"] = {}
line = []
scene["i"] = 0
while scene["i"] < len(self.parsed):
token = self.parsed[scene["i"]]
if "quit" in scene["variables"] and scene["variables"]["quit"]:
print(self.format_line(line, scene))
break
if token == "":
scene["i"] += 1
continue
elif token == "\n":
if len(line) > 0:
print(self.format_line(line, scene))
line = []
elif token.startswith("$"):
tag = self.tags[token[1:]]
if tag["action"] in self.actions.keys():
self.actions[tag["action"]](scene, tag)
#print(tag)
else:
line.append(token)
scene["i"] += 1
if __name__ == "__main__":
tree = DialogueTree(game)
#print(" ".join(tree.parsed))
tree.interpret({"playerName": "You", "npc": "John", "player": {"gold": 0, "items": ["apple"]}})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment