class LevelUp(): def __init__(self, code): self.lines = code.splitlines() self.line_num = 0 # Get name from first line (optional) try: name_line = re.match("Name\s?:\s?(.+)", self.lines[0], re.I) name = name_line.groups(0)[0] self.lines = self.lines[1:] except IndexError: name = "Hero" # Get class try: class_line = re.match("Class\s?:\s?(.+)", self.lines[0], re.I) hero_class = class_line.groups(0)[0].title() self.lines = self.lines[1:] except IndexError: exit("Error: Missing or invalid class line") self.hero = Hero(name, hero_class) self.labels = {} self.quest = None self.input = None self.expected = None self.output = None self.quest_line_num = None self.getch = _Getch() def run(self): while self.line_num < len(self.lines): self.execute_line(self.lines[self.line_num]) self.line_num += 1 def execute_line(self, line): line = line.strip() if not line or line[0] == '#': return line_types = [["^(\w+):$", self.set_label, []], ["^jmp (\w+)$", self.jump_to_label, [Skills.JMP]], ["^jez (\w+)\s+(\w+)$", self.jump_to_label, [Skills.JEZ]], ["^inc (\w+)$", self.hero.increment, [Skills.INCREMENT]], ["^dec (\w+)$", self.hero.decrement, [Skills.DECREMENT]], ["^pch (\w+)$", self.print_char, [Skills.PRINT_CHAR]], ["^ich (\w+)$", self.input_char, [Skills.INPUT_CHAR]], ["^add (\w+) (\w+) (\w+)$", self.hero.add, [Skills.ADDITION]], ["^Begin quest: '([\w ]+)'$", self.begin_quest, []], ["^Claim reward$", self.claim_reward, []]] for regex, func, prereqs in line_types: match = re.match(regex, line, re.I) if match: for skill in prereqs: if skill not in self.hero.skills: exit("Error: need skill '{}' for line '{}'!".format(skill.value, line)) func(*match.groups()) return exit("Error: Invalid line '{}'".format(line)) def set_label(self, label): self.labels[label] = self.line_num def jump_to_label(self, *args): label = args[-1] if len(args) < 2 or self.hero.get_elem(args[0]) == 0: if label not in self.labels: try: label_match = re.match("(\w+):", self.lines[self.line_num]) while not label_match: self.line_num += 1 label_match = re.match("(\w+):", self.lines[self.line_num]) except IndexError: exit("Error: Invalid label {}".format(label)) else: self.line_num = self.labels[label] def input_char(self, name): if self.quest is None: if sys.stdin.isatty(): # Console char = getch() if ord(char) == 3: exit("^C") else: char = sys.stdin.read(1) else: if not self.input: exit("Error: Read too much input during quest") char = self.input[0] self.input = self.input[1:] self.hero.input_char(name, char) def print_char(self, name): if self.quest is None: print(self.hero.print_char(name), end="") else: self.output.append(self.hero.print_char(name)) def begin_quest(self, quest_name): quest_name = quest_name.lower() if quest_name in self.hero.completed_quests: self.line_num = self.hero.completed_quests[quest_name] return quest = {q.name.lower(): q for q in [QuestAddition]}[quest_name] if self.quest is not None and not isinstance(self.quest, quest): exit("Error: Cannot be part of two quests at once") self.quest = quest print("Initiated quest '{}'!".format(self.quest.name), file=sys.stderr) self.input, self.expected = next(self.quest.cases) self.output = [] self.quest_line_num = self.line_num def claim_reward(self): if self.quest is None: exit("Error: Cannot claim reward while not in quest") self.output = "".join(self.output) if self.output != self.expected: exit("Error: Failed quest '{}'.\nExpected: {}\nActual: {}" .format(self.quest.name, self.expected, self.output)) try: self.input, self.expected = next(self.quest.cases) self.output = [] self.line_num = self.quest_line_num except StopIteration: print("Completed quest '{}'!".format(self.quest.name), file=sys.stderr) self.hero.complete_quest(self.quest) self.quest = None self.input = None self.expected = None self.output = None self.quest_line_num = None