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_parallel_quests(): logic = GameLogic.parse(""" type foo { rules { do_a :: not_a(foo) & $not_c(foo) -> a(foo); do_b :: not_b(foo) & $not_c(foo) -> b(foo); do_c :: $a(foo) & $b(foo) & not_c(foo) -> c(foo); } constraints { a_or_not_a :: a(foo) & not_a(foo) -> fail(); b_or_not_b :: b(foo) & not_b(foo) -> fail(); c_or_not_c :: c(foo) & not_c(foo) -> fail(); } } """) kb = KnowledgeBase(logic, "") state = State([ Proposition.parse("a(foo)"), Proposition.parse("b(foo)"), Proposition.parse("c(foo)"), ]) options = ChainingOptions() options.backward = True options.kb = kb options.max_depth = 3 options.max_breadth = 1 chains = list(get_chains(state, options)) assert len(chains) == 2 options.max_breadth = 2 chains = list(get_chains(state, options)) assert len(chains) == 3 options.min_breadth = 2 chains = list(get_chains(state, options)) assert len(chains) == 1 assert len(chains[0].actions) == 3 assert chains[0].nodes[0].depth == 2 assert chains[0].nodes[0].breadth == 2 assert chains[0].nodes[0].parent == chains[0].nodes[2] assert chains[0].nodes[1].depth == 2 assert chains[0].nodes[1].breadth == 1 assert chains[0].nodes[1].parent == chains[0].nodes[2] assert chains[0].nodes[2].depth == 1 assert chains[0].nodes[2].breadth == 1 assert chains[0].nodes[2].parent == None options.min_breadth = 1 options.create_variables = True chains = list(get_chains(State(), options)) assert len(chains) == 5
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 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 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 check_state(state): fail = Proposition("fail", []) debug = Proposition("debug", []) constraints = state.all_applicable_actions( KnowledgeBase.default().constraints.values()) for constraint in constraints: if state.is_applicable(constraint): # Optimistically delay copying the state copy = state.copy() copy.apply(constraint) if copy.is_fact(fail): return False return True
def add_fact(self, name: str, *entities: List["WorldEntity"]) -> None: """ Adds a fact to this entity. Args: name: The name of the new fact. *entities: A list of entities as arguments to the new fact. """ args = [entity.var for entity in entities] self._facts.append(Proposition(name, args))
def new_fact(self, name: str, *entities: List["WorldEntity"]) -> None: """ Create new fact. Args: name: The name of the new fact. *entities: A list of entities as arguments to the new fact. """ args = [entity.var for entity in entities] return Proposition(name, args)
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 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 check_state(self, state: State) -> bool: """Check that a state satisfies the constraints.""" fail = Proposition("fail", []) constraints = state.all_applicable_actions(self.constraints) for constraint in constraints: copy = state.apply_on_copy(constraint) if copy and copy.is_fact(fail): return False return True
def test_is_sequence_applicable(): state = State([ Proposition.parse("at(P, r_1: r)"), Proposition.parse("empty(r_2: r)"), Proposition.parse("empty(r_3: r)"), ]) assert state.is_sequence_applicable([ Action.parse("go :: at(P, r_1: r) & empty(r_2: r) -> at(P, r_2: r) & empty(r_1: r)"), Action.parse("go :: at(P, r_2: r) & empty(r_3: r) -> at(P, r_3: r) & empty(r_2: r)"), ]) assert not state.is_sequence_applicable([ Action.parse("go :: at(P, r_1: r) & empty(r_2: r) -> at(P, r_2: r) & empty(r_1: r)"), Action.parse("go :: at(P, r_1: r) & empty(r_3: r) -> at(P, r_3: r) & empty(r_1: r)"), ]) assert not state.is_sequence_applicable([ Action.parse("go :: at(P, r_2: r) & empty(r_3: r) -> at(P, r_3: r) & empty(r_2: r)"), Action.parse("go :: at(P, r_3: r) & empty(r_1: r) -> at(P, r_1: r) & empty(r_3: r)"), ])
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 facts(self) -> List[Proposition]: """ Facts related to this path. Returns: The facts that make up this path. """ facts = [] facts.append( Proposition("{}_of".format(self.src_exit), [self.dest.var, self.src.var])) facts.append( Proposition("{}_of".format(self.dest_exit), [self.src.var, self.dest.var])) if self.door is None or self.door.has_property("open"): facts.append(Proposition("free", [self.src.var, self.dest.var])) facts.append(Proposition("free", [self.dest.var, self.src.var])) if self.door is not None: facts.extend(self.door.facts) facts.append( Proposition("link", [self.src.var, self.door.var, self.dest.var])) facts.append( Proposition("link", [self.dest.var, self.door.var, self.src.var])) return facts
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 connect(room1: Variable, direction: str, room2: Variable, door: Optional[Variable] = None) -> List[Proposition]: """ Generate predicates that connect two rooms. Args: room1: A room variable. direction: Direction that we need to travel to go from room1 to room2. room2: A room variable. door: The door separating the two rooms. If `None`, there is no door between the rooms. """ r_direction = reverse_direction(direction) + "_of" direction += "_of" facts = [ Proposition(direction, [room2, room1]), Proposition(r_direction, [room1, room2]), Proposition("free", [room1, room2]), Proposition("free", [room2, room1]) ] if door is not None: facts += [ Proposition("link", [room1, door, room2]), Proposition("link", [room2, door, room1]) ] return facts
def test_all_instantiations(): state = State([ Proposition.parse("at(P, kitchen: r)"), Proposition.parse("in(key: o, kitchen: r)"), Proposition.parse("in(egg: o, kitchen: r)"), Proposition.parse("in(book: o, study: r)"), Proposition.parse("in(book: o, study: r)"), Proposition.parse("in(map: o, I)"), ]) take = Rule.parse("take :: $at(P, r) & in(o, r) -> in(o, I)") actions = set(state.all_instantiations(take)) assert actions == { Action.parse("take :: $at(P, kitchen: r) & in(key: o, kitchen: r) -> in(key: o, I)"), Action.parse("take :: $at(P, kitchen: r) & in(egg: o, kitchen: r) -> in(egg: o, I)"), } drop = take.inverse(name="drop") actions = set(state.all_instantiations(drop)) assert actions == { Action.parse("drop :: $at(P, kitchen: r) & in(map: o, I) -> in(map: o, kitchen: r)"), } state.apply(*actions) actions = set(state.all_instantiations(drop)) assert len(actions) == 0 # The state is no longer aware of the I variable, so there are no solutions actions = set(state.all_instantiations(take)) assert len(actions) == 0
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 get_failing_constraints(state): fail = Proposition("fail", []) failed_constraints = [] constraints = state.all_applicable_actions( KnowledgeBase.default().constraints.values()) for constraint in constraints: if state.is_applicable(constraint): # Optimistically delay copying the state copy = state.copy() copy.apply(constraint) if copy.is_fact(fail): failed_constraints.append(constraint) return failed_constraints
def set_player_room(self, start_room: Union[None, WorldRoom, str] = None) -> None: if start_room is None: if len(self.rooms) == 0: start_room = WorldRoom("r_0", "r") else: start_room = self.rooms[0] elif start_room in self._entities: start_room = self._entities[start_room] elif isinstance(start_room, Variable) and start_room.name in self._entities: start_room = self._entities[start_room.name] else: raise ValueError("Unknown room: {}".format(start_room)) self.add_fact(Proposition("at", [self.player, start_room]))
def set_conditions(self, conditions: Iterable[Proposition]) -> Action: """ Set the triggering conditions for this event. Args: conditions: Set of propositions which need to be all true in order for this event to get triggered. Returns: Action that can only be applied when all conditions are statisfied. """ if not conditions: if len(self.actions) == 0: raise UnderspecifiedEventError() # The default winning conditions are the postconditions of the # last action in the quest. conditions = self.actions[-1].postconditions variables = sorted(set([v for c in conditions for v in c.arguments])) event = Proposition("event", arguments=variables) self.condition = Action("trigger", preconditions=conditions, postconditions=list(conditions) + [event]) return self.condition
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_propositions(): state = build_state() for prop in state.facts: data = prop.serialize() loaded_prop = Proposition.deserialize(data) assert loaded_prop == prop
def build_state(door_state="open"): # Set up a world with two rooms and a few objecs. 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(door_state, [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 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_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_parallel_quests_navigation(): logic = GameLogic.parse(""" type P { } type I { } type r { rules { move :: at(P, r) & $free(r, r') -> at(P, r'); } constraints { atat :: at(P, r) & at(P, r') -> fail(); } } type o { rules { take :: $at(P, r) & at(o, r) -> in(o, I); } constraints { inat :: in(o, I) & at(o, r) -> fail(); } } type flour : o { } type eggs : o { } type cake { rules { bake :: in(flour, I) & in(eggs, I) -> in(cake, I) & in(flour, cake) & in(eggs, cake); } constraints { inincake :: in(o, I) & in(o, cake) -> fail(); atincake :: at(o, r) & in(o, cake) -> fail(); } } """) kb = KnowledgeBase(logic, "") state = State([ Proposition.parse("at(P, r3: r)"), Proposition.parse("free(r2: r, r3: r)"), Proposition.parse("free(r1: r, r2: r)"), ]) bake = [kb.logic.rules["bake"]] non_bake = [r for r in kb.logic.rules.values() if r.name != "bake"] options = ChainingOptions() options.backward = True options.create_variables = True options.min_depth = 3 options.max_depth = 3 options.min_breadth = 2 options.max_breadth = 2 options.kb = kb options.rules_per_depth = [bake, non_bake, non_bake] options.restricted_types = {"P", "r"} chains = list(get_chains(state, options)) assert len(chains) == 2