def test_is_game_completed(self): game_progress = GameProgression(self.game) for action in self.quest.actions: assert not game_progress.done game_progress.update(action) assert game_progress.done
def _apply_command(command: str, game_progression: GameProgression, inform7: Inform7Game) -> None: """ Apply a text command to a game_progression object. """ valid_commands = inform7.gen_commands_from_actions(game_progression.valid_actions) for action, cmd in zip(game_progression.valid_actions, valid_commands): if command == cmd: game_progression.update(action) return raise ValueError("Not a valid command: {}. Expected: {}".format(command, valid_commands))
def test_winning_policy(self): # Test following the winning policy derived from the quests. game_progress = GameProgression(self.game) for action in game_progress.winning_policy: assert not game_progress.done game_progress.update(action) assert game_progress.done assert game_progress.completed assert not game_progress.failed assert game_progress.winning_policy is None
def reset(self): self._prev_state = None self.state = GameState() self._game_progression = GameProgression(self._game, track_quests=True) self._last_action = None self._previous_winning_policy = None self._current_winning_policy = self._game_progression.winning_policy self._moves = 0 self.state.raw = DEFAULT_OBSERVATION self.state.feedback = DEFAULT_OBSERVATION self._gather_infos() return self.state
def reset(self): self._prev_state = None self.state = GameState() track_quests = (self.infos.intermediate_reward or self.infos.policy_commands) self._game_progression = GameProgression(self._game, track_quests=track_quests) self._last_action = None self._previous_winning_policy = None self._current_winning_policy = self._game_progression.winning_policy self._moves = 0 self.state.raw = DEFAULT_OBSERVATION self.state.feedback = DEFAULT_OBSERVATION self._gather_infos() return self.state
def reset(self): self.state = self._wrapped_env.reset() if not self.tracking: return self.state # State tracking not needed. self._send('tw-trace-actions') # Turn on print for Inform7 action events. track_quests = (self.infos.intermediate_reward or self.infos.policy_commands) self._game_progression = GameProgression(self._game, track_quests=track_quests) self._last_action = None self._previous_winning_policy = None self._current_winning_policy = self._game_progression.winning_policy self._moves = 0 self._gather_infos() return self.state
def init(self, output: str, game: Game, state_tracking: bool = False, compute_intermediate_reward: bool = False): """ Initialize the game state and set tracking parameters. The tracking parameters, state_tracking and compute_intermediate_reward, are computationally expensive, so are disabled by default. :param output: Introduction text displayed when a game starts. :param game: The glulx game to run :param state_tracking: Whether to use state tracking :param compute_intermediate_reward: Whether to compute the intermediate reward """ output = _strip_input_prompt_symbol(output) _, output = _detect_i7_events_debug_tags(output) self._extra_infos, output = _detect_extra_infos(output) super().init(output) self._game = game self._game_progression = GameProgression(game, track_quests=state_tracking) self._inform7 = Inform7Game(game) self._state_tracking = state_tracking self._compute_intermediate_reward = compute_intermediate_reward and len( game.quests) > 0 self._objective = game.objective self._score = 0 self._max_score = self._game.max_score
def test_game_without_a_quest(self): M = GameMaker() room = M.new_room() M.set_player(room) item = M.new(type="o") room.add(item) game = M.build() game_progress = GameProgression(game) assert not game_progress.done # Simulate action that doesn't change the world. action = game_progress.valid_actions[0] game_progress.update(action) assert not game_progress.done
def test_game_with_infinite_max_score(self): M = textworld.GameMaker() museum = M.new_room("Museum") statue = M.new(type="o", name="golden statue") pedestal = M.new(type="s", name="pedestal") pedestal.add(statue) museum.add(pedestal) M.set_player(museum) M.quests = [ Quest(win_events=[ Event(conditions=[M.new_fact('in', statue, M.inventory)]), ], reward=10, optional=True, repeatable=True), Quest(win_events=[ Event(conditions=[M.new_fact('at', statue, museum)]), ], reward=0) ] M.set_walkthrough( ["take golden statue from pedestal", "look", "drop statue"]) game = M.build() assert game.max_score == np.inf inform7 = Inform7Game(game) game_progress = GameProgression(game) assert len(game_progress.quest_progressions) == len(game.quests) # Following the actions associated to the last quest actually corresponds # to solving the whole game. for action in game_progress.winning_policy: assert not game_progress.done game_progress.update(action) assert game_progress.done assert not game_progress.quest_progressions[ 0].completed # Repeatable quests can never be completed. assert game_progress.quest_progressions[ 1].completed # Mandatory quest. # Solve the game while completing the optional quests. game_progress = GameProgression(game) for command in [ "take golden statue from pedestal", "look", "look", "drop golden statue" ]: _apply_command(command, game_progress, inform7) progressions = game_progress.quest_progressions assert not progressions[0].done # Repeatable quests can never be done. assert progressions[ 0].nb_completions == 3 # They could have been completed a number of times, though. assert progressions[1].done assert game_progress.score == 30
def test_completed(self): game = GameProgression(self.game) for action in self.eventA.actions + self.eventC.actions: assert not game.done game.update(action) assert not game.done remaining_actions = self.eventB.actions[1:] # skipping "open door". assert game.winning_policy == remaining_actions for action in self.eventB.actions: assert not game.done game.update(action) assert game.done assert game.completed assert not game.failed assert game.winning_policy is None
def test_cycle_in_winning_policy(self): M = GameMaker() # Create a map. # r0 # | # r1 -- r2 # | | # r3 -- r4 R0 = M.new_room("r0") R1 = M.new_room("r1") R2 = M.new_room("r2") R3 = M.new_room("r3") R4 = M.new_room("r4") M.set_player(R1) M.connect(R0.south, R1.north), M.connect(R1.east, R2.west), M.connect(R3.east, R4.west) M.connect(R1.south, R3.north) M.connect(R2.south, R4.north) carrot = M.new(type='f', name='carrot') R0.add(carrot) apple = M.new(type='f', name='apple') R2.add(apple) commands = ["go north", "take carrot"] M.set_quest_from_commands(commands) game = M.build() inform7 = Inform7Game(game) game_progression = GameProgression(game) _apply_command("go south", game_progression, inform7) expected_commands = ["go north"] + commands winning_commands = inform7.gen_commands_from_actions( game_progression.winning_policy) assert winning_commands == expected_commands, "{} != {}".format( winning_commands, expected_commands) _apply_command("go east", game_progression, inform7) _apply_command("go north", game_progression, inform7) expected_commands = ["go south", "go west", "go north"] + commands winning_commands = inform7.gen_commands_from_actions( game_progression.winning_policy) assert winning_commands == expected_commands, "{} != {}".format( winning_commands, expected_commands) _apply_command("go west", game_progression, inform7) # Found shortcut expected_commands = commands winning_commands = inform7.gen_commands_from_actions( game_progression.winning_policy) assert winning_commands == expected_commands, "{} != {}".format( winning_commands, expected_commands) # Quest where player's has to pick up the carrot first. commands = [ "go east", "take apple", "go west", "go north", "drop apple" ] M.set_quest_from_commands(commands) game = M.build() game_progression = GameProgression(game) _apply_command("go south", game_progression, inform7) expected_commands = ["go north"] + commands winning_commands = inform7.gen_commands_from_actions( game_progression.winning_policy) assert winning_commands == expected_commands, "{} != {}".format( winning_commands, expected_commands) _apply_command("go east", game_progression, inform7) expected_commands = ["go west", "go north"] + commands winning_commands = inform7.gen_commands_from_actions( game_progression.winning_policy) assert winning_commands == expected_commands, "{} != {}".format( winning_commands, expected_commands) _apply_command("go north", game_progression, inform7) # Found shortcut expected_commands = commands[1:] winning_commands = inform7.gen_commands_from_actions( game_progression.winning_policy) assert winning_commands == expected_commands, "{} != {}".format( winning_commands, expected_commands)
class StateTracking(textworld.core.Wrapper): """ Wrapper that enables state tracking for Inform7 games generated by TextWorld. """ @property def tracking(self): return (self.infos.intermediate_reward or self.infos.policy_commands or self.infos.admissible_commands or self.infos.facts or self.infos.last_action) def load(self, gamefile: str) -> None: self._wrapped_env.load(gamefile) self._gamefile = os.path.splitext(gamefile)[0] + ".json" try: self._game = self._wrapped_env._game except AttributeError: if not os.path.isfile(self._gamefile): raise MissingGameInfosError(self) self._game = Game.load(self._gamefile) self._game_progression = None self._inform7 = Inform7Game(self._game) def _gather_infos(self): self.state["_game_progression"] = self._game_progression self.state["_facts"] = list(self._game_progression.state.facts) self.state["won"] = '*** The End ***' in self.state["feedback"] self.state["lost"] = '*** You lost! ***' in self.state["feedback"] self.state["_winning_policy"] = self._current_winning_policy if self.infos.policy_commands: self.state["policy_commands"] = [] if self._current_winning_policy is not None: self.state[ "policy_commands"] = self._inform7.gen_commands_from_actions( self._current_winning_policy) if self.infos.intermediate_reward: self.state["intermediate_reward"] = 0 if self.state["won"]: # The last action led to winning the game. self.state["intermediate_reward"] = 1 elif self.state["lost"]: # The last action led to losing the game. self.state["intermediate_reward"] = -1 elif self._previous_winning_policy is None: self.state["intermediate_reward"] = 0 else: diff = len(self._previous_winning_policy) - len( self._current_winning_policy) self.state["intermediate_reward"] = int(diff > 0) - int( diff < 0) # Sign function. if self.infos.facts: self.state["facts"] = list( map(self._inform7.get_human_readable_fact, self.state["_facts"])) self.state["_last_action"] = self._last_action if self.infos.last_action and self._last_action is not None: self.state[ "last_action"] = self._inform7.get_human_readable_action( self._last_action) self.state["_valid_actions"] = self._game_progression.valid_actions if self.infos.admissible_commands: all_valid_commands = self._inform7.gen_commands_from_actions( self._game_progression.valid_actions) # To guarantee the order from one execution to another, we sort the commands. # Remove any potential duplicate commands (they would lead to the same result anyway). self.state["admissible_commands"] = sorted(set(all_valid_commands)) if self.infos.moves: self.state["moves"] = self._moves def _send(self, command: str) -> str: """ Send a command to the game without affecting the Environment's state. """ return self.unwrapped._send(command) def reset(self): self.state = self._wrapped_env.reset() if not self.tracking: return self.state # State tracking not needed. self._send( 'tw-trace-actions') # Turn on print for Inform7 action events. track_quests = (self.infos.intermediate_reward or self.infos.policy_commands) self._game_progression = GameProgression(self._game, track_quests=track_quests) self._last_action = None self._previous_winning_policy = None self._current_winning_policy = self._game_progression.winning_policy self._moves = 0 self._gather_infos() return self.state def step(self, command: str): self.state, score, done = self._wrapped_env.step(command) if not self.tracking: return self.state, score, done # State tracking not needed. # Detect what events just happened in the game. i7_events, self.state["feedback"] = _detect_i7_events_debug_tags( self.state["feedback"]) if str2bool(os.environ.get("TEXTWORLD_DEBUG", False)): print("[DEBUG] Detected Inform7 events:\n{}\n".format(i7_events)) self._previous_winning_policy = self._current_winning_policy for i7_event in i7_events: valid_actions = self._game_progression.valid_actions self._last_action = self._inform7.detect_action( i7_event, valid_actions) if self._last_action is not None: # An action that affects the state of the game. self._game_progression.update(self._last_action) self._current_winning_policy = self._game_progression.winning_policy self._moves += 1 self._gather_infos() self.state["done"] = self.state["won"] or self.state["lost"] return self.state, score, self.state["done"]
class TextWorldEnv(textworld.Environment): """ Environment for playing games by TextWorld. """ def __init__(self, infos: Optional[EnvInfos] = None) -> None: """ Arguments: infos: Information to be included in the game state. By default, only the game's narrative is included. """ super().__init__(infos) self._gamefile = None self._game = None self._inform7 = None self._last_action = None self._prev_state = None self._previous_winning_policy = None self._current_winning_policy = None self._moves = None self._game_progression = None def load(self, path: str) -> None: self._gamefile = path self._game = textworld.Game.load(self._gamefile) self._game_progression = None self._inform7 = Inform7Game(self._game) def _gather_infos(self): self.state["game"] = self._game self.state["command_templates"] = self._game.command_templates self.state["verbs"] = self._game.verbs self.state["entities"] = self._game.entity_names self.state["objective"] = self._game.objective self.state["max_score"] = self._game.max_score for k, v in self._game.metadata.items(): self.state["extra.{}".format(k)] = v self.state["_game_progression"] = self._game_progression self.state["_facts"] = list(self._game_progression.state.facts) self.state["won"] = self._game_progression.completed self.state["lost"] = self._game_progression.failed self.state["_winning_policy"] = self._current_winning_policy if self.infos.policy_commands: self.state["policy_commands"] = [] if self._game_progression.winning_policy is not None: self.state["policy_commands"] = self._inform7.gen_commands_from_actions(self._current_winning_policy) if self.infos.intermediate_reward: self.state["intermediate_reward"] = 0 if self.state["won"]: # The last action led to winning the game. self.state["intermediate_reward"] = 1 elif self.state["lost"]: # The last action led to losing the game. self.state["intermediate_reward"] = -1 elif self._previous_winning_policy is None: self.state["intermediate_reward"] = 0 else: diff = len(self._previous_winning_policy) - len(self._current_winning_policy) self.state["intermediate_reward"] = int(diff > 0) - int(diff < 0) # Sign function. if self.infos.facts: self.state["facts"] = list(map(self._inform7.get_human_readable_fact, self.state["_facts"])) self.state["last_action"] = None self.state["_last_action"] = self._last_action if self.infos.last_action and self._last_action is not None: self.state["last_action"] = self._inform7.get_human_readable_action(self._last_action) self.state["_valid_actions"] = self._game_progression.valid_actions self.state["_valid_commands"] = self._inform7.gen_commands_from_actions(self._game_progression.valid_actions) # To guarantee the order from one execution to another, we sort the commands. # Remove any potential duplicate commands (they would lead to the same result anyway). self.state["admissible_commands"] = sorted(set(self.state["_valid_commands"])) if self.infos.moves: self.state["moves"] = self._moves def reset(self): self._prev_state = None self.state = GameState() track_quests = (self.infos.intermediate_reward or self.infos.policy_commands) self._game_progression = GameProgression(self._game, track_quests=track_quests) self._last_action = None self._previous_winning_policy = None self._current_winning_policy = self._game_progression.winning_policy self._moves = 0 self.state.raw = DEFAULT_OBSERVATION self.state.feedback = DEFAULT_OBSERVATION self._gather_infos() return self.state def step(self, command: str): command = command.strip() self._prev_state = self.state self.state = GameState() self.state.last_command = command self.state.raw = DEFAULT_OBSERVATION self.state.feedback = DEFAULT_OBSERVATION self._previous_winning_policy = self._current_winning_policy self._last_action = None try: # Find the action corresponding to the command. idx = self._prev_state["_valid_commands"].index(command) self._last_action = self._game_progression.valid_actions[idx] # An action that affects the state of the game. self._game_progression.update(self._last_action) self._current_winning_policy = self._game_progression.winning_policy self._moves += 1 except ValueError: self.state.feedback = "Invalid command." pass # We assume nothing happened in the game. self._gather_infos() self.state["score"] = self._game_progression self.state["done"] = self.state["won"] or self.state["lost"] return self.state, self.state["score"], self.state["done"] def copy(self) -> "TextWorldEnv": """ Return a copy of this environment. It is safe to call `step` and `reset` on the copied environment. .. warning:: The `Game` and `Inform7Game` private objects are *soft* copies. """ env = TextWorldEnv() # Copy core Environment's attributes. env.state = self.state.copy() env.infos = self.infos.copy() env._gamefile = self._gamefile env._game = self._game # Reference env._inform7 = self._inform7 # Reference env._prev_state = self._prev_state.copy() if self._prev_state is not None else None env._last_action = self._last_action env._moves = self._moves if self._previous_winning_policy is not None: env._previous_winning_policy = tuple(self._previous_winning_policy) if self._current_winning_policy is not None: env._current_winning_policy = tuple(self._current_winning_policy) if self._game_progression is not None: env._game_progression = self._game_progression.copy() return env
def test_cycle_in_winning_policy(cls): M = GameMaker() # Create a map. # r0 # | # r1 -- r2 # | | # r3 -- r4 R0 = M.new_room("r0") R1 = M.new_room("r1") R2 = M.new_room("r2") R3 = M.new_room("r3") R4 = M.new_room("r4") M.set_player(R1) M.connect(R0.south, R1.north), M.connect(R1.east, R2.west), M.connect(R3.east, R4.west) M.connect(R1.south, R3.north) M.connect(R2.south, R4.north) carrot = M.new(type='f', name='carrot') R0.add(carrot) apple = M.new(type='f', name='apple') R2.add(apple) commands = ["go north", "take carrot"] M.set_quest_from_commands(commands) game = M.build() game_progression = GameProgression(game) def _apply_command(command, game_progression): valid_commands = gen_commands_from_actions(game_progression.valid_actions, game.infos) for action, cmd in zip(game_progression.valid_actions, valid_commands): if command == cmd: game_progression.update(action) return raise ValueError("Not a valid command: {}. Expected: {}".format(command, valid_commands)) _apply_command("go south", game_progression) expected_commands = ["go north"] + commands winning_commands = gen_commands_from_actions(game_progression.winning_policy, game.infos) assert winning_commands == expected_commands, "{} != {}".format(winning_commands, expected_commands) _apply_command("go east", game_progression) _apply_command("go north", game_progression) expected_commands = ["go south", "go west", "go north"] + commands winning_commands = gen_commands_from_actions(game_progression.winning_policy, game.infos) assert winning_commands == expected_commands, "{} != {}".format(winning_commands, expected_commands) _apply_command("go west", game_progression) # Found shortcut expected_commands = commands winning_commands = gen_commands_from_actions(game_progression.winning_policy, game.infos) assert winning_commands == expected_commands, "{} != {}".format(winning_commands, expected_commands) # Quest where player's has to pick up the carrot first. commands = ["go east", "take apple", "go west", "go north", "drop apple"] M.set_quest_from_commands(commands) game = M.build() game_progression = GameProgression(game) _apply_command("go south", game_progression) expected_commands = ["go north"] + commands winning_commands = gen_commands_from_actions(game_progression.winning_policy, game.infos) assert winning_commands == expected_commands, "{} != {}".format(winning_commands, expected_commands) _apply_command("go east", game_progression) expected_commands = ["go west", "go north"] + commands winning_commands = gen_commands_from_actions(game_progression.winning_policy, game.infos) assert winning_commands == expected_commands, "{} != {}".format(winning_commands, expected_commands) _apply_command("go north", game_progression) # Found shortcut expected_commands = commands[1:] winning_commands = gen_commands_from_actions(game_progression.winning_policy, game.infos) assert winning_commands == expected_commands, "{} != {}".format(winning_commands, expected_commands)
def test_game_with_optional_and_repeatable_quests(self): M = textworld.GameMaker() museum = M.new_room("Museum") weak_statue = M.new(type="o", name="ivory statue") normal_statue = M.new(type="o", name="stone statue") strong_statue = M.new(type="o", name="granite statue") museum.add(weak_statue) museum.add(normal_statue) museum.add(strong_statue) M.set_player(museum) M.quests = [ Quest(win_events=[ Event(conditions=[M.new_fact('in', weak_statue, M.inventory)]), ], reward=-10, optional=True, repeatable=True), Quest(win_events=[ Event( conditions=[M.new_fact('in', normal_statue, M.inventory)]), ], reward=3, optional=True), Quest( win_events=[ Event(conditions=[ M.new_fact('in', strong_statue, M.inventory) ]), ], reward=5, ) ] M.set_walkthrough( ["take ivory", "take stone", "drop ivory", "take granite"]) game = M.build() inform7 = Inform7Game(game) game_progress = GameProgression(game) assert len(game_progress.quest_progressions) == len(game.quests) # Following the actions associated to the last quest actually corresponds # to solving the whole game. for action in game_progress.winning_policy: assert not game_progress.done game_progress.update(action) assert game_progress.done assert not game_progress.quest_progressions[ 0].completed # Optional, negative quest. assert not game_progress.quest_progressions[ 1].completed # Optional, positive quest. assert game_progress.quest_progressions[ 2].completed # Mandatory quest. # Solve the game while completing the optional quests. game_progress = GameProgression(game) for command in [ "take ivory statue", "look", "take stone statue", "drop ivory statue", "take granite statue" ]: _apply_command(command, game_progress, inform7) progressions = game_progress.quest_progressions assert not progressions[0].done # Repeatable quests can never be done. assert progressions[ 0].nb_completions == 3 # They could have been completed a number of times, though. assert progressions[ 1].done # The nonrepeatable-optional quest can be done. assert progressions[2].done assert game.max_score == 8 assert game_progress.score == -22
def test_game_with_multiple_quests(self): M = GameMaker() # The subgoals (needs to be executed in order). commands = [ [ "open wooden door", "go west", "take carrot", "go east", "drop carrot" ], # Now, the player is back in the kitchen and the wooden door is open. ["go west", "take lettuce", "go east", "drop lettuce"], # Now, the player is back in the kitchen, there are a carrot and a lettuce on the floor. [ "take lettuce", "take carrot", "insert carrot into chest", "insert lettuce into chest", "close chest" ] ] # Create a 'bedroom' room. R1 = M.new_room("bedroom") R2 = M.new_room("kitchen") M.set_player(R2) path = M.connect(R1.east, R2.west) path.door = M.new(type='d', name='wooden door') path.door.add_property("closed") carrot = M.new(type='f', name='carrot') lettuce = M.new(type='f', name='lettuce') R1.add(carrot, lettuce) # Add a closed chest in R2. chest = M.new(type='c', name='chest') chest.add_property("open") R2.add(chest) quest1 = M.new_quest_using_commands(commands[0]) quest1.desc = "Fetch the carrot and drop it on the kitchen's ground." quest2 = M.new_quest_using_commands(commands[0] + commands[1]) quest2.desc = "Fetch the lettuce and drop it on the kitchen's ground." quest3 = M.new_quest_using_commands(commands[0] + commands[1] + commands[2]) winning_facts = [ M.new_fact("in", lettuce, chest), M.new_fact("in", carrot, chest), M.new_fact("closed", chest) ] quest3.win_events[0].set_conditions(winning_facts) quest3.desc = "Put the lettuce and the carrot into the chest before closing it." M.quests = [quest1, quest2, quest3] assert len(M.quests) == len(commands) game = M.build() inform7 = Inform7Game(game) game_progress = GameProgression(game) assert len(game_progress.quest_progressions) == len(game.quests) # Following the actions associated to the last quest actually corresponds # to solving the whole game. for action in game_progress.winning_policy: assert not game_progress.done game_progress.update(action) assert game_progress.done assert all(quest_progression.done for quest_progression in game_progress.quest_progressions) # Try solving the game by greedily taking the first action from the current winning policy. game_progress = GameProgression(game) while not game_progress.done: action = game_progress.winning_policy[0] game_progress.update(action) # print(action.name, [c.name for c in game_progress.winning_policy]) # Try solving the second quest (i.e. bringing back the lettuce) first. game_progress = GameProgression(game) for command in [ "open wooden door", "go west", "take lettuce", "go east", "drop lettuce" ]: _apply_command(command, game_progress, inform7) assert not game_progress.quest_progressions[0].done assert game_progress.quest_progressions[1].done for command in ["go west", "take carrot", "go east", "drop carrot"]: _apply_command(command, game_progress, inform7) assert game_progress.quest_progressions[0].done assert game_progress.quest_progressions[1].done for command in [ "take lettuce", "take carrot", "insert carrot into chest", "insert lettuce into chest", "close chest" ]: _apply_command(command, game_progress, inform7) assert game_progress.done # Game is done whenever a quest has failed or is unfinishable. game_progress = GameProgression(game) for command in [ "open wooden door", "go west", "take carrot", "eat carrot" ]: assert not game_progress.done _apply_command(command, game_progress, inform7) assert game_progress.done
def _apply_command(command: str, game_progression: GameProgression, inform7: Inform7Game) -> None: """ Apply a text command to a game_progression object. """ action = _find_action(command, game_progression.valid_actions, inform7) game_progression.update(action)
def test_failed(self): game = GameProgression(self.game) action = self.eating_tomato.actions[0] game.update(action) assert not game.done assert not game.completed assert not game.failed assert game.winning_policy is not None game = GameProgression(self.game) action = self.eating_pepper.actions[0] game.update(action) assert not game.completed assert game.failed assert game.done assert game.winning_policy is None game = GameProgression(self.game) for action in self.eating_carrot.actions: assert not game.done game.update(action) assert game.done assert not game.completed assert game.failed assert game.winning_policy is None game = GameProgression(self.game) for action in self.eating_lettuce.actions: assert not game.done game.update(action) assert game.done assert not game.completed assert game.failed assert game.winning_policy is None # Completing QuestA but failing quest B. game = GameProgression(self.game) for action in self.eventA.actions: assert not game.done game.update(action) assert not game.done game = GameProgression(self.game) for action in self.eating_lettuce.actions: assert not game.done game.update(action) assert game.done assert not game.completed assert game.failed assert game.winning_policy is None