def get_win_conditions(self, chain: Chain) -> Collection[Proposition]: """ Given a chain of actions comprising a quest, return the set of propositions which must hold in a winning state. By default this will be the post-conditions of the final action. Parameters ---------- chain: Chain of actions leading to goal state. Returns ------- win_conditions: Set of propositions which must hold to end the quest succesfully. """ win_conditions = set() # add post-conditions from last action post_props = set(chain.actions[-1].postconditions) win_conditions.update(post_props) return Event(conditions=win_conditions)
def get_win_conditions(self, chain: Chain) -> Collection[Proposition]: """ Given a chain of actions comprising a quest, return the set of propositions which must hold in a winning state. Parameters ---------- chain: Chain of actions leading to goal state. Returns ------- win_conditions: Set of propositions which must hold to end the quest succesfully. """ win_condition_type = self.options.win_condition win_conditions = set() final_state = chain.final_state pg = ProcessGraph() pg.from_tw_actions(chain.actions) if win_condition_type in [WinConditionType.OPS, WinConditionType.ALL]: # require all operations to have the correct inputs processed_props = set([ prop for prop in final_state.facts if prop.name == 'processed' ]) component_props = set([ prop for prop in final_state.facts if prop.name == 'component' ]) win_conditions.update(processed_props) win_conditions.update(component_props) # require all operations to be set to correct type op_type_props = set([ prop for prop in final_state.facts if prop.name == 'tlq_op_type' ]) win_conditions.update(op_type_props) # precedence propositions enforcing minimal ordering restraints between ops tG = nx.algorithms.dag.transitive_closure(pg.G) op_nodes = [ n for n in tG.nodes() if KnowledgeBase.default().types.is_descendant_of( n.var.type, ["op"]) ] op_sg = nx.algorithms.dag.transitive_reduction( tG.subgraph(op_nodes)) for e in op_sg.edges(): op_1_node, op_2_node = e prec_prop = Proposition('preceeds', [op_1_node.var, op_2_node.var]) win_conditions.update({prec_prop}) if win_condition_type in [WinConditionType.ARGS, WinConditionType.ALL]: # require all descriptions to be set correctly desc_props = set([ prop for prop in final_state.facts if prop.name == 'describes' ]) win_conditions.update(desc_props) # add post-conditions from last action post_props = set(chain.actions[-1].postconditions) win_conditions.update(post_props) return Event(conditions=win_conditions)
def test_serialization(self): data = self.event.serialize() event = Event.deserialize(data) assert event == self.event
def setUpClass(cls): M = GameMaker() # 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) R1.add(lettuce) # Add a closed chest in R2. chest = M.new(type='c', name='chest') chest.add_property("open") R2.add(chest) # The goals commands = [ "open wooden door", "go west", "take carrot", "go east", "drop carrot" ] cls.eventA = M.new_event_using_commands(commands) commands = [ "open wooden door", "go west", "take lettuce", "go east", "insert lettuce into chest" ] cls.eventB = M.new_event_using_commands(commands) cls.losing_eventA = Event(conditions={M.new_fact("eaten", carrot)}) cls.losing_eventB = Event(conditions={M.new_fact("eaten", lettuce)}) cls.questA = Quest(win_events=[cls.eventA], fail_events=[cls.losing_eventA]) cls.questB = Quest(win_events=[cls.eventB], fail_events=[cls.losing_eventB]) cls.questC = Quest(win_events=[], fail_events=[cls.losing_eventA, cls.losing_eventB]) commands = [ "open wooden door", "go west", "take lettuce", "go east", "insert lettuce into chest" ] cls.questC = M.new_quest_using_commands(commands) commands = ["open wooden door", "go west", "take carrot", "eat carrot"] cls.eating_carrot = M.new_event_using_commands(commands) commands = [ "open wooden door", "go west", "take lettuce", "eat lettuce" ] cls.eating_lettuce = M.new_event_using_commands(commands) commands = [ "open wooden door", "go west", "take lettuce", "go east", "insert lettuce into chest" ] M.quests = [cls.questA, cls.questB] cls.game = M.build()
def make_game(mode: str, options: GameOptions) -> tw_textlabs.Game: """ Make a Treasure Hunter game. Arguments: mode: Mode for the game where * `'easy'`: rooms are all empty except where the two objects are placed. Also, connections between rooms have no door. * `'medium'`: adding closed doors and containers that might need to be open in order to find the object. * `'hard'`: adding locked doors and containers (necessary keys will in the inventory) that might need to be unlocked (and open) in order to find the object. options: For customizing the game generation (see :py:class:`tw_textlabs.GameOptions <tw_textlabs.generator.game.GameOptions>` for the list of available options). Returns: Generated game. """ kb = KnowledgeBase.default() metadata = {} # Collect infos for reproducibility. metadata["desc"] = "Treasure Hunter" metadata["mode"] = mode metadata["seeds"] = options.seeds metadata["world_size"] = options.nb_rooms metadata["quest_length"] = options.quest_length rngs = options.rngs rng_map = rngs['map'] rng_objects = rngs['objects'] rng_quest = rngs['quest'] rng_grammar = rngs['grammar'] modes = ["easy", "medium", "hard"] if mode == "easy": door_states = None n_distractors = 0 elif mode == "medium": door_states = ["open", "closed"] n_distractors = 10 elif mode == "hard": door_states = ["open", "closed", "locked"] n_distractors = 20 # Generate map. map_ = tw_textlabs.generator.make_map(n_rooms=options.nb_rooms, rng=rng_map, possible_door_states=door_states) assert len(map_.nodes()) == options.nb_rooms world = World.from_map(map_) # Randomly place the player. starting_room = None if len(world.rooms) > 1: starting_room = rng_map.choice(world.rooms) world.set_player_room(starting_room) # Add object the player has to pick up. types_counts = kb.types.count(world.state) obj_type = kb.types.sample(parent_type='o', rng=rng_objects, include_parent=True) var_id = get_new(obj_type, types_counts) right_obj = Variable(var_id, obj_type) world.add_fact(Proposition("in", [right_obj, world.inventory])) # Add containers and supporters to the world. types_counts = kb.types.count(world.state) objects = [] distractor_types = uniquify(['c', 's'] + kb.types.descendants('c') + kb.types.descendants('s')) for i in range(n_distractors): obj_type = rng_objects.choice(distractor_types) var_id = get_new(obj_type, types_counts) # This update the types_counts. objects.append(Variable(var_id, obj_type)) world.populate_with(objects, rng=rng_objects) # Add object the player should not pick up. types_counts = kb.types.count(world.state) obj_type = kb.types.sample(parent_type='o', rng=rng_objects, include_parent=True) var_id = get_new(obj_type, types_counts) wrong_obj = Variable(var_id, obj_type) # Place it anywhere in the world. world.populate_with([wrong_obj], rng=rng_objects) # Generate a quest that finishes by taking something (i.e. the right # object since it's the only one in the inventory). options.chaining.rules_per_depth = [kb.rules.get_matching("take.*")] options.chaining.backward = True options.chaining.rng = rng_quest #options.chaining.restricted_types = exceptions #exceptions = ["r", "c", "s", "d"] if mode == "easy" else ["r"] chain = tw_textlabs.generator.sample_quest(world.state, options.chaining) # Add objects needed for the quest. world.state = chain.initial_state event = Event(chain.actions) quest = Quest(win_events=[event], fail_events=[Event(conditions={Proposition("in", [wrong_obj, world.inventory])})]) grammar = tw_textlabs.generator.make_grammar(options.grammar, rng=rng_grammar) game = tw_textlabs.generator.make_game_with(world, [quest], grammar) game.metadata = metadata mode_choice = modes.index(mode) uuid = "tw-treasure_hunter-{specs}-{grammar}-{seeds}" uuid = uuid.format(specs=encode_seeds((mode_choice, options.nb_rooms, options.quest_length)), grammar=options.grammar.uuid, seeds=encode_seeds([options.seeds[k] for k in sorted(options.seeds)])) game.metadata["uuid"] = uuid return game