Source code for quest.dialogue

from quest.helpers import SimpleInkParser

[docs]class Dialogue: """Models a dialogue interaction as a graph of knots. Each knot has a list of content and then a list of choices leading to further knots (or ending the dialogue). where each choice links to another talk turn or ends the dialogue. A Dialogue instance has a "result" property which the game can use to determine what happened. """
[docs] @classmethod def from_ink(cls, ink_path): """Reads talk turns from a YAML file and uses them to initialize a new Dialogue. Note that this is a @classmethod, which means it should be used by the Dialogue class, not an instance of Dialgoue. For example: >>> dialogue = Dialogue.from_ink("examples/dialogue_sample.ink") Arguments: ink_path: A string with the ink filename containing talk turns. talk turns. For an example of the expected format, see `quest/examples/dialogue_sample.ink`. Returns: A newly-created Dialogue instance. """ parser = SimpleInkParser() with open(ink_path) as dialogue_file: knots = parser.parse(dialogue_file) return Dialogue(knots)
def __init__(self, knots): self.knots = knots self.dialogue_vars = None self.run()
[docs] def run(self, start_at="START"): """Starts (or re-starts) a run of the dialogue by setting the current knot to `start_at` and `running` to True. Arguments: start_at: (default "START") Name of knot to start at. """ self.current_knot = start_at self.knots_visited = [self.current_knot] self.running = True
[docs] def get_content(self): """Returns a list of the content for the current knot. """ content = self.knots[self.current_knot]['content'] if self.dialogue_vars: content = self.replace_vars(content) return content
[docs] def get_options(self): """Returns a list of option text for the current knot. """ options = list(self.knots[self.current_knot]['options'].keys()) if self.dialogue_vars: options = self.replace_vars(options) return options
[docs] def choose(self, choice): """Chooses an option and moves to another knot. If that knot is "END", ends the current dialogue run. Arguments: choice (int): The index of the choice to follow. """ choices = list(self.knots[self.current_knot]['options'].values()) if choices[choice] == 'END': self.running = False else: self.current_knot = choices[choice] self.knots_visited.append(self.current_knot)
[docs] def replace_vars(self, contents): """Returns the content items of a knot or option content list with the ink variables replaced with variables from the game's dialogue_vars dictionary. Arguments: content (str): the ink content dialogue_vars (dict): the variables from the game associated with their value """ var_filled_contents = [] for content_item in contents: var_filled_content_item = [] content_item_vars = content_item.split("}") for num, text_with_var in enumerate(content_item_vars): if num == len(content_item_vars)-1: # last item in list, no variables, append whole string var_filled_content_item.append(text_with_var) else: # replace variables and append if text_with_var[-1] == "\\": # found "}" was actually "\}" var_filled_content_item.append(text_with_var) var_filled_content_item.append("}") else: # replace ink variable static_text, ink_var = text_with_var.rsplit("{", 1) var_filled_content_item.append(static_text) try: ink_var_value = self.dialogue_vars[ink_var] except Exception as e: raise ValueError(f"Variable {ink_var} from ink file not found in dialogue_vars dictionary.") var_filled_content_item.append(str(ink_var_value)) content_item_string = "".join(var_filled_content_item) content_item_string = content_item_string.replace("\\{", "{") content_item_string = content_item_string.replace("\\}", "}") var_filled_contents.append(content_item_string) return var_filled_contents
[docs]class DialogueRunner: """Runs a Dialgoue from the command line. This is useful for testing out dialogue. If you have a file called "conversation.ink", here's an example of how to run it: >>> dialogue = Dialogue.from_ink("conversation.ink") >>> runner = DialogueRunner() >>> runner.run(dialogue) """ def run(self, dialogue, start_at="START", dialogue_vars = None): dialogue.run(start_at=start_at) while dialogue.running: print('-' * 80) for content in dialogue.get_content(dialogue_vars): print(content + '\n') for i, option in enumerate(dialogue.get_options(dialogue_vars)): print("{}) {}".format(i, option)) dialogue.choose(self.get_numeric_choice(maximum=i)) def get_numeric_choice(self, maximum=None): choice = input("> ") while not choice.isdigit() and int(choice) <= maximum: print("Please enter a number between 0 and {}.".format(maximum)) choice = input("> ") return int(choice)
if __name__ == '__main__': dialogue = Dialogue.from_ink("examples/dialogue_sample.ink") runner = DialogueRunner() runner.run(dialogue)