def test_get_visible_objects_in(): P = Variable("P") room = Variable("room", "r") chest = Variable("chest", "c") obj = Variable("obj", "o") # Closed chest. facts = [ Proposition("at", [P, room]), Proposition("at", [chest, room]), Proposition("in", [obj, chest]), Proposition("closed", [chest]) ] world = World.from_facts(facts) objects = world.get_visible_objects_in(world.player_room) assert obj in world.objects assert obj not in objects # Open chest. facts = [ Proposition("at", [P, room]), Proposition("at", [chest, room]), Proposition("in", [obj, chest]), Proposition("open", [chest]) ] world = World.from_facts(facts) objects = world.get_visible_objects_in(world.player_room) assert obj in world.objects assert obj in objects
def build_state(locked_door=False): # Set up a world with two rooms and a few objecs. P = Variable("P") I = Variable("I") bedroom = Variable("bedroom", "r") kitchen = Variable("kitchen", "r") rusty_key = Variable("rusty key", "k") small_key = Variable("small key", "k") wooden_door = Variable("wooden door", "d") chest = Variable("chest", "c") cabinet = Variable("cabinet", "c") robe = Variable("robe", "o") state = State([ Proposition("at", [P, bedroom]), Proposition("south_of", [kitchen, bedroom]), Proposition("north_of", [bedroom, kitchen]), Proposition("link", [bedroom, wooden_door, kitchen]), Proposition("link", [kitchen, wooden_door, bedroom]), Proposition("locked" if locked_door else "closed", [wooden_door]), Proposition("in", [rusty_key, I]), Proposition("match", [rusty_key, chest]), Proposition("locked", [chest]), Proposition("at", [chest, kitchen]), Proposition("in", [small_key, chest]), Proposition("match", [small_key, cabinet]), Proposition("locked", [cabinet]), Proposition("at", [cabinet, bedroom]), Proposition("in", [robe, cabinet]), ]) return state
def test_going_through_door(): P = Variable("P", "P") room = Variable("room", "r") kitchen = Variable("kitchen", "r") state = State() state.add_facts([ Proposition("at", [P, room]), Proposition("north_of", [kitchen, room]), Proposition("free", [kitchen, room]), Proposition("free", [room, kitchen]), Proposition("south_of", [room, kitchen]) ]) options = ChainingOptions() options.backward = True options.max_depth = 3 options.subquests = True options.create_variables = True options.rules_per_depth = [ [KnowledgeBase.default().rules["take/c"], KnowledgeBase.default().rules["take/s"]], KnowledgeBase.default().rules.get_matching("go.*"), [KnowledgeBase.default().rules["open/d"]], ] chains = list(get_chains(state, options)) assert len(chains) == 18
def test_state(): state = State() P = Variable.parse("P") kitchen = Variable.parse("kitchen: r") study = Variable.parse("study: r") stove = Variable.parse("stove: o") at_kitchen = Proposition.parse("at(P, kitchen: r)") in_kitchen = Proposition.parse("in(stove: o, kitchen: r)") at_study = Proposition.parse("at(P, study: r)") assert not state.is_fact(at_kitchen) assert not state.is_fact(in_kitchen) assert not state.is_fact(at_study) assert len(state.variables) == 0 assert len(state.variables_of_type("P")) == 0 assert len(state.variables_of_type("r")) == 0 assert len(state.variables_of_type("o")) == 0 state.add_fact(at_kitchen) state.add_fact(in_kitchen) assert state.is_fact(at_kitchen) assert state.is_fact(in_kitchen) assert not state.is_fact(at_study) assert set(state.variables) == {P, kitchen, stove} assert state.variables_of_type("P") == {P} assert state.variables_of_type("r") == {kitchen} assert state.variables_of_type("o") == {stove} state.remove_fact(at_kitchen) assert not state.is_fact(at_kitchen) assert state.is_fact(in_kitchen) assert not state.is_fact(at_study) assert set(state.variables) == {kitchen, stove} assert len(state.variables_of_type("P")) == 0 assert state.variables_of_type("r") == {kitchen} assert state.variables_of_type("o") == {stove} state.remove_fact(in_kitchen) assert not state.is_fact(at_kitchen) assert not state.is_fact(in_kitchen) assert not state.is_fact(at_study) assert len(state.variables) == 0 assert len(state.variables_of_type("P")) == 0 assert len(state.variables_of_type("r")) == 0 assert len(state.variables_of_type("o")) == 0 state.add_fact(at_study) assert not state.is_fact(at_kitchen) assert not state.is_fact(in_kitchen) assert state.is_fact(at_study) assert set(state.variables) == {P, study} assert state.variables_of_type("P") == {P} assert state.variables_of_type("r") == {study} assert len(state.variables_of_type("o")) == 0
def test_serialization_deserialization(): rule = KnowledgeBase.default().rules["go/east"] mapping = { Placeholder("r'"): Variable("room1", "r"), Placeholder("r"): Variable("room2"), } mapping.update(KnowledgeBase.default().types.constants_mapping) action = rule.instantiate(mapping) infos = action.serialize() action2 = Action.deserialize(infos) assert action == action2
def test_get_objects_in_inventory(): P = Variable("P") I = Variable("I") room = Variable("room", "r") obj = Variable("obj", "o") # Closed chest. facts = [Proposition("at", [P, room]), Proposition("in", [obj, I])] world = World.from_facts(facts) objects = world.get_objects_in_inventory() assert obj in world.objects assert obj in objects
def create_variable(self, state, ph, type_counts): """Create a new variable of the given type.""" count = type_counts[ph.type] if not self.options.check_new_variable(state, ph.type, count): return None name = "{}_{}".format(ph.type, count) var = Variable(name, ph.type) while state.has_variable(var): name += "'" var = Variable(name, ph.type) type_counts[ph.type] += 1 return var
def new(self, type: str, name: Optional[str] = None, desc: Optional[str] = None) -> Union[WorldEntity, WorldRoom]: """ Creates new entity given its type. Args: type: The type of the entity. name: The name of the entity. desc: The description of the entity. Returns: The newly created entity. * If the `type` is `'r'`, then a `WorldRoom` object is returned. * Otherwise, a `WorldEntity` is returned. """ var_id = type if not KnowledgeBase.default().types.is_constant(type): var_id = get_new(type, self._types_counts) var = Variable(var_id, type) if type == "r": entity = WorldRoom(var, name, desc) self.rooms.append(entity) elif KnowledgeBase.default().types.is_descendant_of(type, ["g"]): name = var_id desc = "A global variable." entity = WorldEntity(var, name, desc) else: entity = WorldEntity(var, name, desc) self._entities[var_id] = entity return entity
def test_populate_with(): # setup P = Variable('P') I = Variable('I') room = Variable('room', 'r') facts = [Proposition('at', [P, room])] world = World.from_facts(facts) # test obj = Variable('obj', 'o') world.populate_with(objects=[obj]) assert obj in world.objects assert (Proposition('at', [obj, room]) in world.facts or Proposition('in', [obj, I]) in world.facts)
def test_type_hierarchy(): assert list(a.ancestors) == [] assert list(a.supertypes) == [a] assert list(a.descendants) == [b, c, d] assert list(a.subtypes) == [a, b, c, d] assert list(b.ancestors) == [a] assert list(b.supertypes) == [b, a] assert list(b.descendants) == [d] assert list(b.subtypes) == [b, d] assert list(c.ancestors) == [a] assert list(c.supertypes) == [c, a] assert list(c.descendants) == [d] assert list(c.subtypes) == [c, d] assert list(d.ancestors) == [b, c, a] assert list(d.supertypes) == [d, b, c, a] assert list(d.descendants) == [] assert list(d.subtypes) == [d] va = Variable.parse("a") assert va.is_a(a) assert not va.is_a(b) assert not va.is_a(c) assert not va.is_a(d) vb = Variable.parse("b") assert vb.is_a(a) assert vb.is_a(b) assert not vb.is_a(c) assert not vb.is_a(d) vc = Variable.parse("c") assert vc.is_a(a) assert not vc.is_a(b) assert vc.is_a(c) assert not vc.is_a(d) vd = Variable.parse("d") assert vd.is_a(a) assert vd.is_a(b) assert vd.is_a(c) assert vd.is_a(d)
def test_automatically_positioning_rooms(): P = Variable("P") r1 = Variable("Room1", "r") r2 = Variable("Room2", "r") d = Variable("door", "d") facts = [Proposition("at", [P, r1])] world = World.from_facts(facts) assert len(world.rooms) == 1 assert len(world.find_room_by_id(r1.name).exits) == 0 world.add_fact(Proposition("link", [r1, d, r2])) assert len(world.rooms) == 2 r1_entity = world.find_room_by_id(r1.name) r2_entity = world.find_room_by_id(r2.name) assert len(r1_entity.exits) == 1 assert len(r2_entity.exits) == 1 assert list(r1_entity.exits.keys())[0] == reverse_direction( list(r2_entity.exits.keys())[0])
def from_tw_actions(self, actions: Iterable[Action]) -> DiGraph: """ Construct internal graph given a TextWorld Quest. """ # TODO verify validity? from tw_textlabs.generator.quest_generator import convert_to_compact_action G = nx.DiGraph() action_vars = [convert_to_compact_action(a) for a in actions] for action_var in action_vars: action_name = action_var.name if action_name == 'op_o_obtain': # add change state var as arg to this action so it gets processed action_var.vars.insert(0, action_var.change_state_vars[0]) ents = action_var.vars for e in ents: self.init_ent(e) # ignore actions with single arg if len(ents) != 2: continue is_state_change_action = len(action_var.change_state_vars) > 0 if not is_state_change_action: G.add_edge(self.curr_state(ents[0]), self.curr_state(ents[1]), action=action_name) # assuming just one material changes state per action else: assert (len(action_var.change_state_vars) == 1) v = action_var.change_state_vars[0] assert (v == ents[0]) # second argument is the source for new one (such as device) source = self.curr_state(ents[1]) # Add a new state for the device, we assume that each operation # on a material is at a new state. if self.curr_state(v) in G.nodes(): # entity already exists in graph target = self.add_new_state(v) else: # entity doesn't exist yet in graph, connect init node target = self.curr_state(v) # Add 'result' type edge which corresponds to 'obtain' action G.add_edge(source, target, action='obtain') # add Generator dummy node to be source of graph (cosmetic) self.generator = EntityNode(var=Variable(name='START', type=GENERATOR_DUMMY_TYPE), g=0) G.add_node(self.generator) self.G = G materials = self.get_start_material_nodes() for m in materials: self.G.add_edge(self.generator, m, action='take')
def test_count(self): rng = np.random.RandomState(1234) types_counts = {t: rng.randint(2, 10) for t in self.types.variables} state = State() for t in self.types.variables: v = Variable(get_new(t, types_counts), t) state.add_fact(Proposition("dummy", [v])) counts = self.types.count(state) for t in self.types.variables: assert counts[t] == types_counts[t], (counts[t], types_counts[t])
def test_logic_parsing(): P = Variable("P", "P") kitchen = Variable("kitchen", "r") egg = Variable("egg", "f") assert Variable.parse("P") == P assert Variable.parse("kitchen: r") == kitchen at_kitchen = Proposition("at", [P, kitchen]) in_kitchen = Proposition("in", [egg, kitchen]) raw_egg = Proposition("raw", [egg]) cooked_egg = Proposition("cooked", [egg]) assert Proposition.parse("at(P, kitchen: r)") == at_kitchen assert Signature.parse("at(P, r)") == at_kitchen.signature cook_egg = Action("cook", [at_kitchen, in_kitchen, raw_egg], [at_kitchen, in_kitchen, cooked_egg]) assert Action.parse("cook :: $at(P, kitchen: r) & $in(egg: f, kitchen: r) & raw(egg: f) -> cooked(egg: f)") == cook_egg P = Placeholder("P", "P") r = Placeholder("r", "r") d = Placeholder("d", "d") rp = Placeholder("r'", "r") assert Placeholder.parse("P") == P assert Placeholder.parse("r") == r assert Placeholder.parse("d") == d assert Placeholder.parse("r'") == rp at_r = Predicate("at", [P, r]) link = Predicate("link", [r, d, rp]) unlocked = Predicate("unlocked", [d]) at_rp = Predicate("at", [P, rp]) assert Predicate.parse("link(r, d, r')") == link go = Rule("go", [at_r, link, unlocked], [link, unlocked, at_rp]) assert Rule.parse("go :: at(P, r) & $link(r, d, r') & $unlocked(d) -> at(P, r')") == go # Make sure the types match in the whole expression assert_raises(ValueError, Rule.parse, "take :: $at(P, r) & $in(c, r) & in(o: k, c) -> in(o, I)")
def maybe_instantiate_variables(rule, mapping, state, max_types_counts=None): types_counts = KnowledgeBase.default().types.count(state) # Instantiate variables if needed try: for ph in rule.placeholders: if mapping.get(ph) is None: name = get_new(ph.type, types_counts, max_types_counts) mapping[ph] = Variable(name, ph.type) except NotEnoughNounsError: return None return rule.instantiate(mapping)
def test_backward_chaining(): P = Variable("P", "P") room = Variable("room", "r") kitchen = Variable("kitchen", "r") state = State([ Proposition("at", [P, room]), Proposition("north_of", [kitchen, room]), Proposition("south_of", [room, kitchen]), ]) options = ChainingOptions() options.backward = True options.max_depth = 2 options.subquests = True options.create_variables = True options.rules_per_depth = [ [KnowledgeBase.default().rules["take/c"], KnowledgeBase.default().rules["take/s"]], [KnowledgeBase.default().rules["open/c"]], ] options.restricted_types = {"d"} chains = list(get_chains(state, options)) assert len(chains) == 3 options = ChainingOptions() options.backward = True options.max_depth = 3 options.subquests = True options.create_variables = True options.rules_per_depth = [ [KnowledgeBase.default().rules["put"]], [KnowledgeBase.default().rules["go/north"]], [KnowledgeBase.default().rules["take/c"]], ] options.restricted_types = {"d"} chains = list(get_chains(state, options)) assert len(chains) == 3
def from_map(cls, map: networkx.Graph) -> "World": """ Args: map: Graph defining the structure of the world. """ world = cls() names = [ d.get("name", "r_{}".format(i)) for i, (n, d) in enumerate(map.nodes.items()) ] rooms = OrderedDict( (n, Variable(names[i], "r")) for i, n in enumerate(map.nodes())) world.add_facts(graph2state(map, rooms)) return world
def _update(self) -> None: """ Update the internal representation of the world. This method will create new entities based on facts. It should be called whenever backing facts are changed. """ self._entities = OrderedDict() # Clear entities. self.player = self._get_entity(Variable("P")) self.inventory = self._get_entity(Variable("I")) self._player_room = None self._process_rooms() self._process_objects() self._rooms = [ entity for entity in self._entities.values() if isinstance(entity, WorldRoom) ] self._objects = [ entity for entity in self._entities.values() if isinstance(entity, WorldObject) ] self._entities_per_type = defaultdict(list) for entity in self._entities.values(): self._entities_per_type[entity.type].append(entity)
def test_match(): rule = Rule.parse("go :: at(P, r) & $link(r, d, r') & $free(r, r') & $free(r', r) -> at(P, r')") mapping = { Placeholder.parse("P"): Variable.parse("P"), Placeholder.parse("r"): Variable.parse("r1: r"), Placeholder.parse("r'"): Variable.parse("r2: r"), Placeholder.parse("d"): Variable.parse("d"), } action = Action.parse("go :: at(P, r1: r) & $link(r1: r, d, r2: r) & $free(r1: r, r2: r) & $free(r2: r, r1: r) -> at(P, r2: r)") assert rule.match(action) == mapping # Order shouldn't matter action = Action.parse("go :: $link(r1: r, d, r2: r) & $free(r1: r, r2: r) & $free(r2: r, r1: r) & at(P, r1: r) -> at(P, r2: r)") assert rule.match(action) == mapping action = Action.parse("go :: at(P, r1: r) & $link(r1: r, d, r2: r) & $free(r2: r, r1: r) & $free(r1: r, r2: r) -> at(P, r2: r)") assert rule.match(action) == mapping # Predicate matches can't conflict action = Action.parse("go :: at(P, r1: r) & $link(r1: r, d, r2: r) & $free(r2: r, r1: r) & $free(r1: r, r2: r) -> at(P, r3: r)") assert rule.match(action) == None
def test_cannot_automatically_positioning_rooms(): P = Variable("P") r0 = Variable("Room0", "r") r1 = Variable("Room1", "r") r2 = Variable("Room2", "r") r3 = Variable("Room3", "r") r4 = Variable("Room4", "r") r5 = Variable("Room5", "r") d = Variable("door", "d") facts = [Proposition("at", [P, r0])] facts.extend(connect(r0, 'north', r1)) facts.extend(connect(r0, 'east', r2)) facts.extend(connect(r0, 'south', r3)) facts.extend(connect(r0, 'west', r4)) world = World.from_facts(facts) npt.assert_raises(NoFreeExitError, world.add_fact, Proposition("link", [r0, d, r5]))
def __init__(self, vtypes: List[VariableType]): self.variables_types = {vtype.type: vtype for vtype in vtypes} # Make some convenient attributes. self.types = [vt.type for vt in vtypes] self.names = [vt.name for vt in vtypes] self.constants = [t for t in self if self.is_constant(t)] self.variables = [t for t in self if not self.is_constant(t)] self.constants_mapping = { Placeholder(c): Variable(c) for c in self.constants } # Adjust variable type's parent and children references. for vt in vtypes: if vt.parent is not None: vt_parent = self[vt.parent] vt_parent.children.append(vt.type)
def graph2state(G: networkx.Graph, rooms: Dict[str, Variable]) -> List[Proposition]: """ Convert Graph object to a list of `Proposition`. Args: G: Graph defining the structure of the world. rooms: information about the rooms in the world. """ state = [] for src, dest in G.edges(): d = direction(src, dest) d_r = direction(dest, src) e = G[src][dest] room_src = rooms[src] room_dest = rooms[dest] if e["has_door"]: door = Variable(e['door_name'], "d") pred1 = Proposition("{}_of".format(d), [room_dest, room_src]) pred2 = Proposition("{}_of".format(d_r), [room_src, room_dest]) state.append(Proposition(e["door_state"], [door])) state.append(Proposition("link", [room_src, door, room_dest])) state.append(Proposition("link", [room_dest, door, room_src])) if e["door_state"] == "open": state.append(Proposition("free", [room_dest, room_src])) state.append(Proposition("free", [room_src, room_dest])) else: pred1 = Proposition("{}_of".format(d), [room_dest, room_src]) pred2 = Proposition("{}_of".format(d_r), [room_src, room_dest]) state.append(Proposition("free", [room_dest, room_src])) state.append(Proposition("free", [room_src, room_dest])) state.append(pred1) state.append(pred2) return state
def test_used_names_is_updated(verbose=False): # Make generation throughout the framework reproducible. g_rng.set_seed(1234) # Generate a map that's shape in a cross with room0 in the middle. P = Variable('P') r = Variable('r_0', 'r') k1 = Variable('k_1', 'k') k2 = Variable('k_2', 'k') c1 = Variable('c_1', 'c') c2 = Variable('c_2', 'c') facts = [ Proposition('at', [P, r]), Proposition('at', [k1, r]), Proposition('at', [k2, r]), Proposition('at', [c1, r]), Proposition('at', [c2, r]), Proposition('match', [k1, c1]), Proposition('match', [k2, c2]) ] world = World.from_facts(facts) world.set_player_room() # Set start room to the middle one. world.populate_room(10, world.player_room) # Add objects to the starting room. # Generate the world representation. grammar = tw_textlabs.generator.make_grammar({}, rng=np.random.RandomState(42)) for k, v in grammar.grammar.items(): grammar.grammar[k] = v[:2] # Force reusing variables. game = tw_textlabs.generator.make_game_with(world, [], grammar) for entity_infos in game.infos.values(): if entity_infos.name is None: continue assert entity_infos.name in grammar.used_names
def test_make_world_with(): r1 = Variable("r_0", "r") P = Variable('P') world = make_world_with(rooms=[r1]) assert Proposition('at', [P, r1]) in world.facts
def test_variables(): for var in [P, bedroom, robe, counter, chest]: data = var.serialize() loaded_var = Variable.deserialize(data) assert loaded_var == var
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
# Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT license. from tw_textlabs.generator.data import KnowledgeBase from tw_textlabs.generator.vtypes import NotEnoughNounsError, get_new from tw_textlabs.logic import Action, Placeholder, Proposition, Rule, State, Variable P = Variable("P", "P") I = Variable("I", "I") bedroom = Variable("bedroom", "r") kitchen = Variable("kitchen", "r") old_key = Variable("old key", "k") rusty_key = Variable("rusty key", "k") small_key = Variable("small key", "k") wooden_door = Variable("wooden door", "d") glass_door = Variable("glass door", "d") chest = Variable("chest", "c") cabinet = Variable("cabinet", "c") counter = Variable("counter", "s") robe = Variable("robe", "o") def maybe_instantiate_variables(rule, mapping, state, max_types_counts=None): types_counts = KnowledgeBase.default().types.count(state) # Instantiate variables if needed try: for ph in rule.placeholders: if mapping.get(ph) is None: name = get_new(ph.type, types_counts, max_types_counts) mapping[ph] = Variable(name, ph.type)
def test_constraints(): # Declare some variables. P = Variable("P", "P") I = Variable("I", "I") bedroom = Variable("bedroom", "r") kitchen = Variable("kitchen", "r") rusty_key = Variable("rusty key", "k") small_key = Variable("small key", "k") wooden_door = Variable("wooden door", "d") glass_door = Variable("glass door", "d") chest = Variable("chest", "c") cabinet = Variable("cabinet", "c") counter = Variable("counter", "s") robe = Variable("robe", "o") # Make sure the number of basic constraints matches the number # of constraints in constraints.txt basic_constraints = [ k for k in KnowledgeBase.default().constraints.keys() if "-" not in k ] # assert len(basic_constraints) == 32 # Doors can only have one state. door_states = ["open", "closed", "locked"] for door_state in door_states: state = State([Proposition(door_state, [wooden_door])]) assert check_state(state) for door_state2 in door_states: if door_state == door_state2: continue state2 = state.copy() state2.add_fact(Proposition(door_state2, [glass_door])) # New door assert check_state(state2) state2.add_fact(Proposition(door_state2, [wooden_door])) assert not check_state(state2) # Containers can only have one state. container_states = ["open", "closed", "locked"] for container_state in container_states: state = State([Proposition(container_state, [chest])]) assert check_state(state) for container_state2 in container_states: if container_state == container_state2: continue state2 = state.copy() state2.add_fact(Proposition(container_state2, [cabinet])) # New container assert check_state(state2) state2.add_fact(Proposition(container_state2, [chest])) assert not check_state(state2) # A player/supporter/container can only be at one place. for obj in [P, chest, counter]: assert check_state(State([Proposition("at", [obj, kitchen])])) assert check_state(State([Proposition("at", [obj, bedroom])])) assert not check_state( State([ Proposition("at", [obj, kitchen]), Proposition("at", [obj, bedroom]) ])) # An object is either in the player's inventory, in a container or on a supporter obj_locations = [ Proposition("in", [robe, I]), Proposition("in", [robe, chest]), Proposition("on", [robe, counter]) ] for obj_location in obj_locations: assert check_state(State([obj_location])) for obj_location2 in obj_locations: if obj_location == obj_location2: break assert not check_state(State([obj_location, obj_location2 ])), "{}, {}".format( obj_location, obj_location2) # Only one key can match a container and vice-versa. assert check_state(State([Proposition("match", [rusty_key, chest])])) assert not check_state( State([ Proposition("match", [small_key, chest]), Proposition("match", [rusty_key, chest]) ])) assert not check_state( State([ Proposition("match", [small_key, cabinet]), Proposition("match", [small_key, chest]) ])) # Only one key can match a door and vice-versa. assert check_state(State([Proposition("match", [rusty_key, chest])])) assert not check_state( State([ Proposition("match", [small_key, wooden_door]), Proposition("match", [rusty_key, wooden_door]) ])) assert not check_state( State([ Proposition("match", [small_key, glass_door]), Proposition("match", [small_key, wooden_door]) ])) # A door can't be used to link more than two rooms. door = Variable("door", "d") room1 = Variable("room1", "r") room2 = Variable("room2", "r") room3 = Variable("room3", "r") assert not check_state( State([ Proposition("link", [room1, door, room2]), Proposition("link", [room1, door, room3]), ])) door1 = Variable("door1", "d") door2 = Variable("door2", "d") room1 = Variable("room1", "r") room2 = Variable("room2", "r") assert not check_state( State([ Proposition("link", [room1, door1, room2]), Proposition("link", [room1, door2, room2]), ]))
def test_room_connections(): room0 = Variable("room0", "r") room1 = Variable("room1", "r") room2 = Variable("room2", "r") # Only one connection can exist between two rooms. # r1 # | # r0 - r1 state = State([ Proposition("north_of", [room1, room0]), Proposition("south_of", [room0, room1]), Proposition("east_of", [room1, room0]), Proposition("west_of", [room0, room1]) ]) assert not check_state(state) # Non Cartesian layout are allowed. # r1 # | # r0 - r2 - r1 state = State([ Proposition("north_of", [room1, room0]), Proposition("south_of", [room0, room1]), Proposition("east_of", [room2, room0]), Proposition("west_of", [room0, room2]), Proposition("east_of", [room1, room2]), Proposition("west_of", [room2, room1]) ]) assert check_state(state) # A room cannot have more than 4 'link' propositions. room3 = Variable("room3", "r") room4 = Variable("room4", "r") room5 = Variable("room5", "r") door1 = Variable("door1", "d") door2 = Variable("door2", "d") door3 = Variable("door3", "d") door4 = Variable("door4", "d") door5 = Variable("door5", "d") state = State([ Proposition("link", [room0, door1, room1]), Proposition("link", [room0, door2, room2]), Proposition("link", [room0, door3, room3]), Proposition("link", [room0, door4, room4]), Proposition("link", [room0, door5, room5]) ]) assert not check_state(state)
def test_match_complex(): rule = Rule.parse("combine/3 :: $at(P, r) & $correct_location(r) & $in(tool, I) & $in(tool', I) & $in(tool'', I) & in(o, I) & in(o', I) & in(o'', I) & $out(o''') & $used(slot) & used(slot') & used(slot'') -> in(o''', I) & free(slot') & free(slot'')") mapping = { Placeholder.parse("P"): Variable.parse("P"), Placeholder.parse("I"): Variable.parse("I"), Placeholder.parse("r"): Variable.parse("r"), Placeholder.parse("o"): Variable.parse("o1: o"), Placeholder.parse("o'"): Variable.parse("o2: o"), Placeholder.parse("o''"): Variable.parse("o3: o"), Placeholder.parse("o'''"): Variable.parse("o4: o"), Placeholder.parse("tool"): Variable.parse("tool1: tool"), Placeholder.parse("tool'"): Variable.parse("tool2: tool"), Placeholder.parse("tool''"): Variable.parse("tool3: tool"), Placeholder.parse("slot"): Variable.parse("slot1: slot"), Placeholder.parse("slot'"): Variable.parse("slot2: slot"), Placeholder.parse("slot''"): Variable.parse("slot3: slot"), } action = Action.parse("combine/3 :: $at(P, r) & $correct_location(r) & $in(tool1: tool, I) & $in(tool2: tool, I) & $in(tool3: tool, I) & in(o1: o, I) & in(o2: o, I) & in(o3: o, I) & $out(o4: o) & $used(slot1: slot) & used(slot2: slot) & used(slot3: slot) -> in(o4: o, I) & free(slot2: slot) & free(slot3: slot)") for _ in range(10000): assert rule.match(action) == mapping