Esempio n. 1
0
 def __init__(self) -> None:
     self._state = State()
     self._entities = OrderedDict()
     self._rooms = []
     self._objects = []
     self._update()
     self._player_room = None
Esempio n. 2
0
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
Esempio n. 3
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
Esempio n. 4
0
    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])
Esempio n. 5
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
Esempio n. 6
0
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)
Esempio n. 7
0
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
Esempio n. 8
0
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
Esempio n. 9
0
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)"),
    ])
Esempio n. 10
0
    def check_action(self, state: State, action: Action) -> bool:
        """
        Check if an action should be allowed in this state.

        The default implementation disallows actions that would create new facts
        that don't mention any new variables.

        Args:
            state: The current state.
            action: The action being applied.

        Returns:
            Whether that action should be allowed.
        """

        for prop in action.preconditions:
            if not state.is_fact(prop):
                if all(state.has_variable(var) for var in prop.arguments):
                    # Don't allow creating new predicates without any new variables
                    return False

        return True
Esempio n. 11
0
def test_get_reverse_action():
    kb = KnowledgeBase.default()
    for rule in kb.rules.values():
        action = maybe_instantiate_variables(rule,
                                             kb.types.constants_mapping.copy(),
                                             State([]))
        r_action = kb.get_reverse_action(action)

        if rule.name.startswith("eat"):
            assert r_action is None
        else:
            assert r_action is not None

            # Check if that when applying the reverse rule we can re-obtain
            # the previous state.
            state = State(action.preconditions)

            new_state = state.copy()
            assert new_state.apply(action)

            r_state = new_state.copy()
            r_state.apply(r_action)
            assert state == r_state
Esempio n. 12
0
 def __init__(self) -> None:
     """
     Creates an empty world, with a player and an empty inventory.
     """
     self._entities = {}
     self.quests = []
     self.rooms = []
     self.paths = []
     self.globals = {
     }  # global variables hack for entities that must be accessible from anywhere
     self._types_counts = KnowledgeBase.default().types.count(State())
     self.player = self.new(type='P')
     self.inventory = self.new(type='I')
     self.grammar = tw_textlabs.generator.make_grammar()
     self._game = None
     self._distractors_facts = []
Esempio n. 13
0
    def state(self) -> State:
        """ Current state of the world. """
        facts = []
        for room in self.rooms:
            facts += room.facts

        for path in self.paths:
            facts += path.facts

        for g in self.globals.values():
            facts += g.facts

        facts += self.inventory.facts
        facts += self._distractors_facts

        return State(facts)
Esempio n. 14
0
 def __init__(self, surface_generator: SurfaceGenerator) -> None:
     """
     Creates an empty world, with a player and an empty inventory.
     """
     self._entities = {}
     self.quests = []
     self.rooms = []
     self.paths = []
     self._types_counts = KnowledgeBase.default().types.count(State())
     self.player = self.new(type='P')
     self.inventory = self.new(type='I')
     self.sg = surface_generator
     self._game = None
     self._quests_str = []
     self._distractors_facts = []
     # define global op types
     self.globals = {}
     for t in KnowledgeBase.default().types.descendants('toe'):
         op_type = self.new(type=t)
         op_type.add_property('initialized')
         self.globals[t] = op_type
Esempio n. 15
0
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
Esempio n. 16
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
Esempio n. 17
0
    def try_instantiate(self, state: State,
                        partial: _PartialAction) -> Optional[Action]:
        """
        Try to instantiate a partial action, by creating new variables if
        necessary.
        """

        rule, mapping = partial.rule, partial.mapping

        if self.create_variables:
            type_counts = Counter({
                ph.type: len(state.variables_of_type(ph.type))
                for ph in rule.placeholders
            })

        for ph in rule.placeholders:
            if mapping.get(ph) is None:
                var = self.create_variable(state, ph, type_counts)
                if var:
                    mapping[ph] = var
                else:
                    return None

        return rule.instantiate(mapping)
Esempio n. 18
0
 def is_triggering(self, state: State) -> bool:
     """ Check if this event would be triggered in a given state. """
     return state.is_applicable(self.condition)
Esempio n. 19
0
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
Esempio n. 20
0
class World:
    def __init__(self) -> None:
        self._state = State()
        self._entities = OrderedDict()
        self._rooms = []
        self._objects = []
        self._update()
        self._player_room = None

    @classmethod
    def from_facts(cls, facts: List[Proposition]) -> "World":
        world = cls()
        world.add_facts(facts)
        return world

    @classmethod
    def deserialize(cls, serialized_facts: List) -> "World":
        return cls.from_facts(
            [Proposition.deserialize(f) for f in serialized_facts])

    def serialize(self) -> List:
        return [f.serialize() for f in self.facts]

    @classmethod
    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

    @property
    def player_room(self) -> WorldRoom:
        return self._player_room

    @property
    def rooms(self) -> List[WorldRoom]:
        return self._rooms

    @property
    def objects(self) -> List[WorldObject]:
        return self._objects

    @property
    def entities(self) -> ValuesView[WorldEntity]:
        return self._entities.values()

    @property
    def state(self) -> State:
        return self._state

    @state.setter
    def state(self, state: State) -> None:
        self._state = State()
        self.add_facts(state.facts)

    @property
    def facts(self) -> List[Proposition]:
        # Sort the facts for deterministic world generation
        return sorted(self._state.facts)

    def add_fact(self, fact: Proposition) -> None:
        self.add_facts([fact])

    def add_facts(self, facts: List[Proposition]) -> None:
        self._state.add_facts(facts)
        self._update()  # Update the internal representation of the world.

    def _get_entity(self, var: Variable) -> WorldEntity:
        if var.name not in self._entities:
            self._entities[var.name] = WorldEntity.create(var)

        return self._entities[var.name]

    def _get_room(self, var: Variable) -> WorldRoom:
        entity = self._get_entity(var)
        assert isinstance(entity, WorldRoom)
        return entity

    def _get_object(self, var: Variable) -> WorldObject:
        entity = self._get_entity(var)
        assert isinstance(entity, WorldObject)
        return entity

    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 _process_rooms(self) -> None:
        for fact in self.facts:
            if not KnowledgeBase.default().types.is_descendant_of(
                    fact.arguments[0].type, 'r'):
                continue  # Skip non room facts.

            room = self._get_room(fact.arguments[0])
            room.add_related_fact(fact)

            if fact.name.endswith("_of"):
                # Handle room positioning facts.
                exit = reverse_direction(fact.name.split("_of")[0])
                dest = self._get_room(fact.arguments[1])
                dest.add_related_fact(fact)
                assert exit not in room.exits
                room.exits[exit] = dest

        # Handle door link facts.
        for fact in self.facts:
            if fact.name != "link":
                continue

            src = self._get_room(fact.arguments[0])
            door = self._get_object(fact.arguments[1])
            dest = self._get_room(fact.arguments[2])
            door.add_related_fact(fact)
            src.content.append(door)

            exit_found = False
            for exit, room in src.exits.items():
                if dest == room:
                    src.doors[exit] = door
                    exit_found = True
                    break

            if not exit_found:
                # Need to position both rooms w.r.t. each other.
                src_free_exits = [
                    exit for exit in DIRECTIONS if exit not in src.exits
                ]
                for exit in src_free_exits:
                    r_exit = reverse_direction(exit)
                    if r_exit not in dest.exits:
                        src.exits[exit] = dest
                        dest.exits[r_exit] = src
                        src.doors[exit] = door
                        exit_found = True
                        break

            # Relax the Cartesian grid constraint.
            if not exit_found:
                # Need to position both rooms w.r.t. each other.
                src_free_exits = [
                    exit for exit in DIRECTIONS if exit not in src.exits
                ]
                dest_free_exits = [
                    exit for exit in DIRECTIONS if exit not in dest.exits
                ]
                if len(src_free_exits) > 0 and len(dest_free_exits) > 0:
                    exit = src_free_exits[0]
                    r_exit = dest_free_exits[0]
                    src.exits[exit] = dest
                    dest.exits[r_exit] = src
                    src.doors[exit] = door
                    exit_found = True

            if not exit_found:  # If there is still no exit found.
                raise NoFreeExitError("Cannot connect {} and {}.".format(
                    src, dest))

    def _process_objects(self) -> None:
        for fact in self.facts:
            if KnowledgeBase.default().types.is_descendant_of(
                    fact.arguments[0].type, 'r'):
                continue  # Skip room facts.

            obj = self._get_entity(fact.arguments[0])
            obj.add_related_fact(fact)

            if fact.name == "match":
                other_obj = self._get_entity(fact.arguments[1])
                obj.matching_entity_id = fact.arguments[1].name
                other_obj.matching_entity_id = fact.arguments[0].name

            if fact.name in [
                    "in", "on", "at", "potential", "pot_multi_a", "pot_multi_b"
            ]:
                holder = self._get_entity(fact.arguments[1])
                holder.content.append(obj)

                if fact.arguments[0].type == "P":
                    self._player_room = holder

    def get_facts_in_scope(self) -> List[Proposition]:
        facts = []
        facts += [
            fact for exit in self.player_room.exits.values()
            for fact in exit.related_facts
        ]
        facts += [
            fact for door in self.player_room.doors.values()
            for fact in door.related_facts
        ]
        facts += [
            fact for obj in self.get_visible_objects_in(self.player_room)
            for fact in obj.related_facts
        ]
        facts += [
            fact for obj in self.get_objects_in_inventory()
            for fact in obj.related_facts
        ]

        return uniquify(facts)

    def get_visible_objects_in(self, obj: WorldObject) -> List[WorldObject]:
        if "locked" in obj.properties or "closed" in obj.properties:
            return []

        objects = list(obj.content)
        for obj in obj.content:
            objects += self.get_visible_objects_in(obj)

        return objects

    def get_all_objects_in(self, obj: WorldObject) -> List[WorldObject]:
        objects = list(obj.content)
        for obj in obj.content:
            objects += self.get_all_objects_in(obj)

        return objects

    def get_objects_in_inventory(self) -> List[WorldObject]:
        return self.inventory.content

    def get_entities_per_type(self, type: str) -> List[WorldEntity]:
        """ Get all entities of a certain type. """
        return self._entities_per_type.get(type, [])

    def find_object_by_id(self, id: str) -> Optional[WorldObject]:
        return self._entities.get(id)

    def find_room_by_id(self, id: str) -> Optional[WorldRoom]:
        return self._entities.get(id)

    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 populate_room(
        self,
        nb_objects: int,
        room: Variable,
        rng: Optional[RandomState] = None,
        object_types_probs: Optional[Dict[str, float]] = None
    ) -> List[Proposition]:
        rng = g_rng.next() if rng is None else rng
        state = []
        types_counts = KnowledgeBase.default().types.count(self.state)

        inventory = Variable("I", "I")
        objects_holder = [inventory, room]

        locked_or_closed_objects = []
        lockable_objects = []
        for s in self.facts:
            # Look for containers and supporters to put stuff in/on them.
            if s.name == "at" and s.arguments[0].type in [
                    "c", "s"
            ] and s.arguments[1].name == room.name:
                objects_holder.append(s.arguments[0])

            # Look for containers and doors without a matching key.
            if s.name == "at" and s.arguments[0].type in [
                    "c", "d"
            ] and s.arguments[1].name == room.name:
                obj_propositions = [
                    p.name for p in self.facts
                    if s.arguments[0].name in p.names
                ]
                if "match" not in obj_propositions and s.arguments[
                        0] not in lockable_objects:
                    lockable_objects.append(s.arguments[0])

                    if "locked" in obj_propositions or "closed" in obj_propositions:
                        locked_or_closed_objects.append(s.arguments[0])

        object_id = 0
        while object_id < nb_objects:
            if len(locked_or_closed_objects) > 0:
                # Prioritize adding key if there are locked or closed things in the room.
                obj_type = "k"
            else:
                obj_type = KnowledgeBase.default().types.sample(
                    parent_type='t',
                    rng=rng,
                    exceptions=["d", "r"],
                    include_parent=False,
                    probs=object_types_probs)

            if KnowledgeBase.default().types.is_descendant_of(obj_type, "o"):
                obj_name = get_new(obj_type, types_counts)
                obj = Variable(obj_name, obj_type)
                allowed_objects_holder = list(objects_holder)

                if obj_type == "k":
                    if len(locked_or_closed_objects) > 0:
                        # Look for a *locked* container or a door.
                        rng.shuffle(locked_or_closed_objects)
                        locked_or_closed_obj = locked_or_closed_objects.pop()
                        state.append(
                            Proposition("match", [obj, locked_or_closed_obj]))
                        lockable_objects.remove(locked_or_closed_obj)

                        # Do not place the key in its own matching container.
                        if locked_or_closed_obj in allowed_objects_holder:
                            allowed_objects_holder.remove(locked_or_closed_obj)

                    elif len(lockable_objects) > 0:
                        # Look for a container or a door.
                        rng.shuffle(lockable_objects)
                        lockable_obj = lockable_objects.pop()
                        state.append(Proposition("match", [obj, lockable_obj]))
                    else:
                        continue  # Unuseful key is not allowed.

                elif obj_type == "f":
                    # HACK: manually add the edible property to food items.
                    state.append(Proposition("edible", [obj]))

                # Place the object somewhere.
                obj_holder = rng.choice(allowed_objects_holder)
                if KnowledgeBase.default().types.is_descendant_of(
                        obj_holder.type, "s"):
                    state.append(Proposition("on", [obj, obj_holder]))
                elif KnowledgeBase.default().types.is_descendant_of(
                        obj_holder.type, "c"):
                    state.append(Proposition("in", [obj, obj_holder]))
                elif KnowledgeBase.default().types.is_descendant_of(
                        obj_holder.type, "I"):
                    state.append(Proposition("in", [obj, obj_holder]))
                elif KnowledgeBase.default().types.is_descendant_of(
                        obj_holder.type, "r"):
                    state.append(Proposition("at", [obj, obj_holder]))
                else:
                    raise ValueError(
                        "Unknown type for object holder: {}".format(
                            obj_holder))

            elif KnowledgeBase.default().types.is_descendant_of(obj_type, "s"):
                supporter_name = get_new(obj_type, types_counts)
                supporter = Variable(supporter_name, obj_type)
                state.append(Proposition("at", [supporter, room]))
                objects_holder.append(supporter)

            elif KnowledgeBase.default().types.is_descendant_of(obj_type, "c"):
                container_name = get_new(obj_type, types_counts)
                container = Variable(container_name, obj_type)
                state.append(Proposition("at", [container, room]))
                objects_holder.append(container)

                container_state = rng.choice(["open", "closed", "locked"])
                state.append(Proposition(container_state, [container]))

                lockable_objects.append(container)
                if container_state in ["locked", "closed"]:
                    locked_or_closed_objects.append(container)

            else:
                raise ValueError("Unknown object type: {}".format(obj_type))

            object_id += 1

        self.add_facts(state)
        return state

    def populate(
        self,
        nb_objects: int,
        rng: Optional[RandomState] = None,
        object_types_probs: Optional[Dict[str, float]] = None
    ) -> List[Proposition]:
        rng = g_rng.next() if rng is None else rng
        room_names = [room.id for room in self.rooms]
        nb_objects_per_room = {room_name: 0 for room_name in room_names}
        indices = np.arange(len(room_names))
        for _ in range(nb_objects):
            idx = rng.choice(indices)
            nb_objects_per_room[room_names[idx]] += 1

        state = []
        for room in self.rooms:
            state += self.populate_room(nb_objects_per_room[room.id], room,
                                        rng, object_types_probs)

        return state

    def populate_room_with(
            self,
            objects: WorldObject,
            room: WorldRoom,
            rng: Optional[RandomState] = None) -> List[Proposition]:
        rng = g_rng.next() if rng is None else rng
        state = []

        objects_holder = [room]

        locked_or_closed_objects = []
        lockable_objects = []
        for s in self.facts:
            # Look for containers and supporters to put stuff in/on them.
            if s.name == "at" and s.arguments[0].type in [
                    "c", "s"
            ] and s.arguments[1].name == room.name:
                objects_holder.append(s.arguments[0])

            # Look for containers and doors without a matching key.
            if s.name == "at" and s.arguments[0].type in [
                    "c", "d"
            ] and s.arguments[1].name == room.name:
                obj_propositions = [
                    p.name for p in self.facts
                    if s.arguments[0].name in p.names
                ]
                if "match" not in obj_propositions and s.arguments[
                        0] not in lockable_objects:
                    lockable_objects.append(s.arguments[0])

                    if "locked" in obj_propositions or "closed" in obj_propositions:
                        locked_or_closed_objects.append(s.arguments[0])

        remaining_objects_id = list(range(len(objects)))
        rng.shuffle(remaining_objects_id)
        for idx in remaining_objects_id:
            obj = objects[idx]
            obj_type = obj.type

            if KnowledgeBase.default().types.is_descendant_of(obj_type, "o"):
                allowed_objects_holder = list(objects_holder)

                # Place the object somewhere.
                obj_holder = rng.choice(allowed_objects_holder)
                if KnowledgeBase.default().types.is_descendant_of(
                        obj_holder.type, "s"):
                    state.append(Proposition("on", [obj, obj_holder]))
                elif KnowledgeBase.default().types.is_descendant_of(
                        obj_holder.type, "c"):
                    state.append(Proposition("in", [obj, obj_holder]))
                elif KnowledgeBase.default().types.is_descendant_of(
                        obj_holder.type, "r"):
                    state.append(Proposition("at", [obj, obj_holder]))
                else:
                    raise ValueError(
                        "Unknown type for object holder: {}".format(
                            obj_holder))

            elif KnowledgeBase.default().types.is_descendant_of(obj_type, "s"):
                supporter = obj
                state.append(Proposition("at", [supporter, room]))
                objects_holder.append(supporter)

            elif KnowledgeBase.default().types.is_descendant_of(obj_type, "c"):
                container = obj
                state.append(Proposition("at", [container, room]))
                objects_holder.append(container)

                container_state = rng.choice(["open", "closed", "locked"])
                state.append(Proposition(container_state, [container]))

                lockable_objects.append(container)
                if container_state in ["locked", "closed"]:
                    locked_or_closed_objects.append(container)

            else:
                raise ValueError("Unknown object type: {}".format(obj_type))

        self.add_facts(state)
        return state

    def populate_with(self,
                      objects: List[WorldObject],
                      rng: Optional[RandomState] = None) -> List[Proposition]:
        rng = g_rng.next() if rng is None else rng
        room_names = [room.id for room in self.rooms]
        nb_objects_per_room = {room_name: 0 for room_name in room_names}
        indices = np.arange(len(room_names))
        for _ in range(len(objects)):
            idx = rng.choice(indices)
            nb_objects_per_room[room_names[idx]] += 1

        state = []
        for room in self.rooms:
            state += self.populate_room_with(
                objects[:nb_objects_per_room[room.id]], room, rng)
            objects = objects[nb_objects_per_room[room.id]:]

        self.add_facts(state)
        return state

    def __eq__(self, other: Any) -> bool:
        return (isinstance(other, World) and self.state == other.state)

    def __hash__(self) -> int:
        return hash(frozenset(self.facts))
Esempio n. 21
0
 def state(self, state: State) -> None:
     self._state = State()
     self.add_facts(state.facts)
Esempio n. 22
0
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]),
        ]))
Esempio n. 23
0
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