Example #1
0
def test_get_new():
    rng = np.random.RandomState(1234)
    types_counts = {t: rng.randint(2, 10) for t in data.get_types()}
    orig_types_counts = deepcopy(types_counts)
    for t in data.get_types():
        name = get_new(t, types_counts)
        splits = name.split("_")
        assert splits[0] == t
        assert int(splits[1]) == orig_types_counts[t]
        assert types_counts[t] == orig_types_counts[t] + 1
Example #2
0
    def add(self, *entities: List["WorldEntity"]) -> None:
        """ Add children to this entity. """
        if data.get_types().is_descendant_of(self.type, "r"):
            name = "at"
        elif data.get_types().is_descendant_of(self.type, ["c", "I"]):
            name = "in"
        elif data.get_types().is_descendant_of(self.type, "s"):
            name = "on"
        else:
            raise ValueError("Unexpected type {}".format(self.type))

        for entity in entities:
            self.add_fact(name, entity, self)
            self.content.append(entity)
Example #3
0
    def door(self, door: WorldEntity) -> None:
        if door is not None and not data.get_types().is_descendant_of(
                door.type, "d"):
            msg = "Expecting a WorldEntity of 'door' type."
            raise TypeError(msg)

        self._door = door
Example #4
0
    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 data.get_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)
        else:
            entity = WorldEntity(var, name, desc)

        self._entities[var_id] = entity
        return entity
Example #5
0
def test_serialization_deserialization():
    rule = data.get_rules()["go/east"]
    mapping = {
        Placeholder("r'"): Variable("room1", "r"),
        Placeholder("r"): Variable("room2"),
    }
    mapping.update(data.get_types().constants_mapping)
    action = rule.instantiate(mapping)
    infos = action.serialize()
    action2 = Action.deserialize(infos)
    assert action == action2
Example #6
0
 def __init__(self):
     self.backward = False
     self.min_depth = 1
     self.max_depth = 1
     self.min_breadth = 1
     self.max_breadth = 1
     self.subquests = False
     self.create_variables = False
     self.fixed_mapping = data.get_types().constants_mapping
     self.rng = None
     self.logic = data.get_logic()
     self.rules_per_depth = []
     self.restricted_types = frozenset()
Example #7
0
def maybe_instantiate_variables(rule, mapping, state, max_types_counts=None):
    types_counts = data.get_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)
Example #8
0
def test_get_reverse_rules(verbose=False):
    for rule in data.get_rules().values():
        r_rule = data.get_reverse_rules(rule)

        if verbose:
            print(rule, r_rule)

        if rule.name.startswith("eat"):
            assert r_rule is None
        else:
            # Check if that when applying the reverse rule we can reobtain
            # the previous state.
            action = maybe_instantiate_variables(
                rule,
                data.get_types().constants_mapping.copy(), State([]))
            state = State(action.preconditions)

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

            assert r_rule is not None
            actions = list(
                new_state.all_applicable_actions(
                    [r_rule],
                    data.get_types().constants_mapping))
            if len(actions) != 1:
                print(actions)
                print(r_rule)
                print(new_state)
                print(
                    list(
                        new_state.all_instantiations(
                            r_rule,
                            data.get_types().constants_mapping)))
                assert len(actions) == 1
            r_state = new_state.copy()
            r_state.apply(actions[0])
            assert state == r_state
Example #9
0
    def _process_objects(self) -> None:
        for fact in self.facts:
            if data.get_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 in ["in", "on", "at"]:
                holder = self._get_entity(fact.arguments[1])
                holder.content.append(obj)

                if fact.arguments[0].type == "P":
                    self._player_room = holder
Example #10
0
def expand_clean_replace(symbol, grammar, obj, game_infos):
    """ Return a cleaned/keyword replaced symbol. """
    obj_infos = game_infos[obj.id]
    phrase = grammar.expand(symbol)
    phrase = phrase.replace("(obj)", obj_infos.id)
    phrase = phrase.replace("(name)", obj_infos.name)
    phrase = phrase.replace("(name-n)", obj_infos.noun if obj_infos.adj is not None else obj_infos.name)
    phrase = phrase.replace("(name-adj)", obj_infos.adj if obj_infos.adj is not None else grammar.expand("#ordinary_adj#"))
    if obj.type != "":
        phrase = phrase.replace("(name-t)", data.get_types().get_description(obj.type))
    else:
        assert False, "Does this even happen?"

    return fix_determinant(phrase)
Example #11
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._types_counts = data.get_types().count(State())
     self.player = self.new(type='P')
     self.inventory = self.new(type='I')
     self.grammar = textworld.generator.make_grammar()
     self._game = None
     self._distractors_facts = []
Example #12
0
def _get_all_assignments(state,
                         rules,
                         partial=False,
                         constrained_types=None,
                         backward=False):
    assignments = []
    for rule in rules:
        if backward:
            rule = rule.inverse()
        for mapping in state.all_assignments(
                rule,
                data.get_types().constants_mapping, partial,
                constrained_types):
            assignments.append((rule, mapping))

    # Keep everything in a deterministic order
    return sorted(assignments, key=_assignment_sort_key)
Example #13
0
def assign_description_to_object(obj, grammar, game_infos):
    """
    Assign a descripton to an object.
    """
    if game_infos[obj.id].desc is not None:
        return  # Already have a description.

    # Update the object description
    desc_tag = "#({})_desc#".format(obj.type)
    game_infos[obj.id].desc = ""
    if grammar.has_tag(desc_tag):
        game_infos[obj.id].desc = expand_clean_replace(desc_tag, grammar, obj,
                                                       game_infos)

    # If we have an openable object, append an additional description
    if data.get_types().is_descendant_of(obj.type, ["c", "d"]):
        game_infos[obj.id].desc += grammar.expand(" #openable_desc#")
Example #14
0
 def __init__(self,
              world: World,
              grammar: Optional[Grammar] = None,
              quests: Optional[List[Quest]] = None) -> None:
     """
     Args:
         world: The world to use for the game.
         quests: The quests to done in the game.
         grammar: The grammar to control the text generation.
     """
     self.world = world
     self.state = world.state.copy()  # Current state of the game.
     self.grammar = grammar
     self.quests = [] if quests is None else quests
     self.metadata = {}
     self._infos = self._build_infos()
     self._rules = data.get_rules()
     self._types = data.get_types()
Example #15
0
    def collect(self, game):
        self.n_games += 1

        # Collect distribution of nb. of commands.
        update_bincount(self.dist_quest_count, len(game.quests))

        # Collect distribution of command's types.
        for quest in game.quests:
            actions = quest.actions
            self.quests.add(quest.desc)
            update_bincount(self.dist_quest_length_count, len(actions))

            for action in actions:
                action_name = action.name
                if self.group_actions:
                    action_name = action_name.split("-")[0].split("/")[0]

                self.dist_cmd_type[action_name] += 1

            self.dist_final_cmd_type[action_name] += 1

        # Collect distribution of object's types.
        dist_obj_type = defaultdict(lambda: 0)
        interactable_objects = game.world.objects
        inventory = game.world.get_objects_in_inventory()

        for obj in interactable_objects:
            self.objects.add(game.infos[obj.id].name)
            dist_obj_type[obj.type] += 1

        nb_objects = 0
        for type_ in data.get_types():
            if type_ in ["I", "P", "t", "r"]:
                continue

            count = dist_obj_type[type_]
            nb_objects += count
            self.dist_obj_type[type_] += count
            update_bincount(self.dist_obj_type_count[type_], count)

        update_bincount(self.dist_obj_count, nb_objects)
        update_bincount(self.dist_inventory_size, len(inventory))
Example #16
0
def make_game(mode: str, options: GameOptions) -> textworld.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:`textworld.GameOptions <textworld.generator.game.GameOptions>`
            for the list of available options).

    Returns:
        Generated game.
    """
    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
    metadata["grammar_flags"] = options.grammar.encode()

    rngs = options.rngs
    rng_map = rngs['seed_map']
    rng_objects = rngs['seed_objects']
    rng_quest = rngs['seed_quest']
    rng_grammar = rngs['seed_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_ = textworld.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 = data.get_types().count(world.state)
    obj_type = data.get_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 = data.get_types().count(world.state)
    objects = []
    distractor_types = uniquify(['c', 's'] +
                                data.get_types().descendants('c') +
                                data.get_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 = data.get_types().count(world.state)
    obj_type = data.get_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 = [data.get_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 = textworld.generator.sample_quest(world.state, options.chaining)

    # Add objects needed for the quest.
    world.state = chain[0].state
    quest = Quest([c.action for c in chain])
    quest.set_failing_conditions([Proposition("in", [wrong_obj, world.inventory])])

    grammar = textworld.generator.make_grammar(options.grammar, rng=rng_grammar)
    game = textworld.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
Example #17
0
def make_game(mode: str, n_rooms: int, quest_length: int,
              grammar_flags: Mapping = {},
              seeds: Optional[Union[int, Dict[str, int]]] = None
              ) -> textworld.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.
        n_rooms: Number of rooms in the game.
        quest_length: How far from the player the object to find should ideally
                      be placed.
        grammar_flags: Options for the grammar controlling the text generation
                       process.
        seeds: Seeds for the different generation processes.

               * If `None`, seeds will be sampled from
                 :py:data:`textworld.g_rng <textworld.utils.g_rng>`.
               * If `int`, it acts as a seed for a random generator that will be
                 used to sample the other seeds.
               * If dict, the following keys can be set:

                 * `'seed_map'`: control the map generation;
                 * `'seed_objects'`: control the type of objects and their
                   location;
                 * `'seed_quest'`: control the quest generation;
                 * `'seed_grammar'`: control the text generation.

                 For any key missing, a random number gets assigned (sampled
                 from :py:data:`textworld.g_rng <textworld.utils.g_rng>`).

    Returns:
        Generated game.
    """
    # Deal with any missing random seeds.
    seeds = get_seeds_for_game_generation(seeds)

    metadata = {}  # Collect infos for reproducibility.
    metadata["desc"] = "Treasure Hunter"
    metadata["mode"] = mode
    metadata["seeds"] = seeds
    metadata["world_size"] = n_rooms
    metadata["quest_length"] = quest_length
    metadata["grammar_flags"] = grammar_flags

    rng_map = np.random.RandomState(seeds['seed_map'])
    rng_objects = np.random.RandomState(seeds['seed_objects'])
    rng_quest = np.random.RandomState(seeds['seed_quest'])
    rng_grammar = np.random.RandomState(seeds['seed_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_ = textworld.generator.make_map(n_rooms=n_rooms, rng=rng_map,
                                        possible_door_states=door_states)
    assert len(map_.nodes()) == n_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 = data.get_types().count(world.state)
    obj_type = data.get_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 = data.get_types().count(world.state)
    objects = []
    distractor_types = uniquify(['c', 's'] +
                                data.get_types().descendants('c') +
                                data.get_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 = data.get_types().count(world.state)
    obj_type = data.get_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).
    rules_per_depth = {0: data.get_rules().get_matching("take.*")}
    exceptions = ["r", "c", "s", "d"] if mode == "easy" else ["r"]
    chain = textworld.generator.sample_quest(world.state, rng_quest,
                                             max_depth=quest_length,
                                             allow_partial_match=False,
                                             exceptions=exceptions,
                                             rules_per_depth=rules_per_depth,
                                             nb_retry=5,
                                             backward=True)

    # Add objects needed for the quest.
    world.state = chain[0].state
    quest = Quest([c.action for c in chain])
    quest.set_failing_conditions([Proposition("in", [wrong_obj, world.inventory])])

    grammar = textworld.generator.make_grammar(flags=grammar_flags,
                                               rng=rng_grammar)
    game = textworld.generator.make_game_with(world, [quest], grammar)
    game.metadata = metadata
    mode_choice = modes.index(mode)
    uuid = "tw-treasure_hunter-{specs}-{flags}-{seeds}"
    uuid = uuid.format(specs=encode_seeds((mode_choice, n_rooms, quest_length)),
                       flags=encode_flags(grammar_flags),
                       seeds=encode_seeds([seeds[k] for k in sorted(seeds)]))
    game.metadata["uuid"] = uuid
    return game
Example #18
0
    def _process_rooms(self) -> None:
        for fact in self.facts:
            if not data.get_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))
Example #19
0
def load_state(world: World,
               game_infos: Optional[Dict[str, EntityInfo]] = None,
               action: Optional[Action] = None,
               format: str = 'png',
               limit_player_view: bool = False) -> dict:
    """
    Generates serialization of game state.

    :param world: The current state of the world to visualize.
    :param game_infos: The mapping needed to get objects names.
    :param action: If provided, highlight the world changes made by that action.
    :param format: The graph output format (gv, svg, png...)
    :param limit_player_view: Whether to limit the player's view (defaults to false)
    :return: The graph generated from this World
    """

    if world.player_room is None:
        room = world.rooms[0]
    else:
        room = world.player_room

    edges = []
    nodes = sorted([room.name for room in world.rooms])
    pos = {room.name: (0, 0)}

    def used_pos():
        pos_along_edges = []
        for e in edges:
            A, B = pos[e[0]], pos[e[1]]
            if A[0] == B[0]:  # Y-axis edge.
                for i in range(A[1], B[1], np.sign(B[1] - A[1])):
                    pos_along_edges.append((A[0], i))
            else:  # X-axis edge.
                for i in range(A[0], B[0], np.sign(B[0] - A[0])):
                    pos_along_edges.append((i, A[1]))

        return list(pos.values()) + pos_along_edges

    openset = [room]
    closedset = set()

    # temp_viz(nodes, edges, pos, color=[world.player_room.name])
    while len(openset) > 0:
        room = openset.pop(0)
        closedset.add(room)

        for exit, target in room.exits.items():
            if target in openset or target in closedset:
                continue

            openset.append(target)

            src_pos = np.array(pos[room.name])
            if exit == "north":
                target_pos = tuple(src_pos + (0, 1))
                if target_pos in used_pos():
                    for n, p in pos.items():
                        if p[1] <= src_pos[1]:
                            pos[n] = (p[0], p[1] - 1)

                pos[target.name] = (pos[room.name][0], pos[room.name][1] + 1)

            elif exit == "south":
                target_pos = tuple(src_pos + (0, -1))
                if target_pos in used_pos():
                    for n, p in pos.items():
                        if p[1] >= src_pos[1]:
                            pos[n] = (p[0], p[1] + 1)

                pos[target.name] = (pos[room.name][0], pos[room.name][1] - 1)

            elif exit == "east":
                target_pos = tuple(src_pos + (1, 0))
                if target_pos in used_pos():
                    for n, p in pos.items():
                        if p[0] <= src_pos[0]:
                            pos[n] = (p[0] - 1, p[1])

                pos[target.name] = (pos[room.name][0] + 1, pos[room.name][1])

            elif exit == "west":
                target_pos = tuple(src_pos + (-1, 0))
                if target_pos in used_pos():
                    for n, p in pos.items():
                        if p[0] >= src_pos[0]:
                            pos[n] = (p[0] + 1, p[1])

                pos[target.name] = (pos[room.name][0] - 1, pos[room.name][1])

            edges.append((room.name, target.name, room.doors.get(exit)))
            # temp_viz(nodes, edges, pos, color=[world.player_room.name])

    pos = {game_infos[k].name: v for k, v in pos.items()}

    rooms = {}
    player_room = world.player_room
    if game_infos is None:
        new_game = Game(world, [])
        game_infos = new_game.infos
        game_infos["objective"] = new_game.quests[0].desc
        for k, v in game_infos.items():
            if v.name is None:
                v.name = k

    for room in world.rooms:
        rooms[room.id] = GraphRoom(game_infos[room.id].name, room)

    result = {}
    # Objective
    if "objective" in game_infos:
        result["objective"] = game_infos["objective"]

    # Objects
    all_items = {}
    inventory_items = []
    objects = world.objects
    # if limit_player_view:
    #     objects = world.get_visible_objects_in(world.player_room)
    #     objects += world.get_objects_in_inventory()

    # add all items first, in case properties are "out of order"
    for obj in objects:
        cur_item = GraphItem(obj.type, game_infos[obj.id].name)
        cur_item.portable = data.get_types().is_descendant_of(
            cur_item.type, "o")
        all_items[obj.id] = cur_item

    for obj in sorted(objects, key=lambda obj: obj.name):
        cur_item = all_items[obj.id]
        for attribute in obj.get_attributes():
            if action and attribute in action.added:
                cur_item.highlight = True

            if attribute.name == 'in':
                # add object to inventory
                if attribute.arguments[-1].type == 'I':
                    inventory_items.append(cur_item)
                elif attribute.arguments[0].name == obj.id:
                    # add object to containers if same object
                    all_items[attribute.arguments[1].name].add_content(
                        cur_item)
                else:
                    print('DEBUG: Skipping attribute %s for object %s' %
                          (attribute, obj.id))

            elif attribute.name == 'at':
                # add object to room
                if attribute.arguments[-1].type == 'r':
                    rooms[attribute.arguments[1].name].add_item(cur_item)
            elif attribute.name == 'on':
                # add object to supporters
                all_items[attribute.arguments[1].name].add_content(cur_item)
            elif attribute.name == 'open':
                cur_item.set_open_closed_locked('open')
            elif attribute.name == 'closed':
                cur_item.set_open_closed_locked('closed')
            elif attribute.name == 'locked':
                cur_item.set_open_closed_locked('locked')
                if not limit_player_view:
                    cur_item.infos = " (locked)"
            elif attribute.name == 'match':
                if not limit_player_view:
                    cur_item.infos = " (for {})".format(
                        game_infos[attribute.arguments[-1].name].name)
            else:
                cur_item.add_unknown_predicate(attribute)

    for room in rooms.values():
        room.position = pos[room.name]

    result["rooms"] = []
    for room in rooms.values():
        room.items = [item.to_dict() for item in room.items]
        temp = room.base_room.serialize()
        temp["attributes"] = [
            a.serialize() for a in room.base_room.get_attributes()
        ]
        room.base_room = temp
        result["rooms"].append(room.__dict__)

    def _get_door(door):
        if door is None:
            return None

        return all_items[door.name].__dict__

    result["connections"] = [{
        "src": game_infos[e[0]].name,
        "dest": game_infos[e[1]].name,
        'door': _get_door(e[2])
    } for e in edges]
    result["inventory"] = [inv.__dict__ for inv in inventory_items]

    return result
Example #20
0
def generate_instruction(action, grammar, game_infos, world, counts):
    """
    Generate text instruction for a specific action.
    """
    # Get the more precise command tag.
    cmd_tag = "#{}#".format(action.name)
    if not grammar.has_tag(cmd_tag):
        cmd_tag = "#{}#".format(action.name.split("-")[0])

        if not grammar.has_tag(cmd_tag):
            cmd_tag = "#{}#".format(action.name.split("-")[0].split("/")[0])

    separator_tag = "#action_separator_{}#".format(action.name)
    if not grammar.has_tag(separator_tag):
        separator_tag = "#action_separator_{}#".format(action.name.split("-")[0])

        if not grammar.has_tag(separator_tag):
            separator_tag = "#action_separator_{}#".format(action.name.split("-")[0].split("/")[0])

    if not grammar.has_tag(separator_tag):
        separator_tag = "#action_separator#"

    if not grammar.has_tag(separator_tag):
        separator = ""
    else:
        separator = grammar.expand(separator_tag)

    desc = grammar.expand(cmd_tag)

    # We generate a custom mapping.
    mapping = OrderedDict()
    if isinstance(action, MergeAction):
        action_mapping = action.mapping
    else:
        action_mapping = data.get_rules()[action.name].match(action)

    for ph, var in action_mapping.items():
        if var.type == "r":

            # We can use a simple description for the room
            r = world.find_room_by_id(var.name)  # Match on 'name'
            if r is None:
                mapping[ph.name] = ''
            else:
                mapping[ph.name] = game_infos[r.id].name
        elif var.type in ["P", "I"]:
            continue
        else:
            # We want a more complex description for the objects
            obj = world.find_object_by_id(var.name)
            obj_infos = game_infos[obj.id]

            if grammar.flags.ambiguous_instructions:
                assert False, "not tested"
                choices = []

                for t in ["adj", "noun", "type"]:
                    if counts[t][getattr(obj_infos, t)] <= 1:
                        if t == "noun":
                            choices.append(getattr(obj_infos, t))
                        elif t == "type":
                            choices.append(data.get_types().get_description(getattr(obj_infos, t)))
                        else:
                            # For adj, we pick an abstraction on the type
                            atype = data.get_types().get_description(grammar.rng.choice(data.get_types().get_ancestors(obj.type)))
                            choices.append("{} {}".format(getattr(obj_infos, t), atype))

                # If we have no possibilities, use the name (ie. prioritize abstractions)
                if len(choices) == 0:
                    choices.append(obj_infos.name)

                mapping[ph.name] = grammar.rng.choice(choices)
            else:
                mapping[ph.name] = obj_infos.name

    # Replace the keyword with one of the possibilities
    for keyword in re.findall(r'[(]\S*[)]', desc + separator):
        for key in keyword[1:-1].split("|"):
            if key in mapping:
                desc = desc.replace(keyword, mapping[key])
                separator = separator.replace(keyword, mapping[key])

    return desc, separator
Example #21
0
def assign_description_to_room(room, game, grammar):
    """
    Assign a descripton to a room.
    """
    # Add the decorative text
    room_desc = expand_clean_replace("#dec#\n\n", grammar, room, game.infos)

    # Convert the objects into groupings based on adj/noun/type
    objs = [o for o in room.content if data.get_types().is_descendant_of(o.type, data.get_types().CLASS_HOLDER)]
    groups = OrderedDict()
    groups["adj"] = OrderedDict()
    groups["noun"] = OrderedDict()

    for obj in objs:
        obj_infos = game.infos[obj.id]
        adj, noun = obj_infos.adj, obj_infos.noun

        # get all grouped adjectives and nouns
        groups['adj'][adj] = list(filter(lambda x: game.infos[x.id].adj == adj, objs))
        groups['noun'][noun] = list(filter(lambda x: game.infos[x.id].noun == noun, objs))

    # Generate the room description, prioritizing group descriptions where possible
    ignore = []
    for obj in objs:
        if obj.id in ignore:
            continue  # Skip that object.

        obj_infos = game.infos[obj.id]
        adj, noun = obj_infos.adj, obj_infos.noun

        if grammar.flags.blend_descriptions:
            found = False
            for type in ["noun", "adj"]:
                group_filt = []
                if getattr(obj_infos, type) != "":
                    group_filt = list(filter(lambda x: x.id not in ignore, groups[type][getattr(obj_infos, type)]))

                if len(group_filt) > 1:
                    found = True
                    desc = replace_num(grammar.expand("#room_desc_group#"), len(group_filt))

                    if type == "noun":
                        desc = desc.replace("(val)", "{}s".format(getattr(obj_infos, type)))
                        desc = desc.replace("(name)", obj_list_to_prop_string(group_filt, "adj", game.infos, det_type="one"))
                    elif type == "adj":
                        _adj = getattr(obj_infos, type) if getattr(obj_infos, type) is not None else ""
                        desc = desc.replace("(val)", "{}things".format(_adj))
                        desc = desc.replace("(name)", obj_list_to_prop_string(group_filt, "noun", game.infos))

                    for o2 in group_filt:
                        ignore.append(o2.id)
                        if data.get_types().is_descendant_of(o2.type, data.get_types().CLASS_HOLDER):
                            for vtype in [o2.type] + data.get_types().get_ancestors(o2.type):
                                tag = "#room_desc_({})_multi_{}#".format(vtype, "adj" if type == "noun" else "noun")
                                if grammar.has_tag(tag):
                                    desc += expand_clean_replace(" " + tag, grammar, o2, game.infos)
                                    break

                    room_desc += " {}".format(fix_determinant(desc))
                    break

            if found:
                continue

        if obj.type not in ["P", "I", "d"]:
            for vtype in [obj.type] + data.get_types().get_ancestors(obj.type):
                tag = "#room_desc_({})#".format(vtype)
                if grammar.has_tag(tag):
                    room_desc += expand_clean_replace(" " + tag, grammar, obj, game.infos)
                    break

    room_desc += "\n\n"

    # Look for potential exit directions.
    exits_with_open_door = []
    exits_with_closed_door = []
    exits_without_door = []
    for dir_ in sorted(room.exits.keys()):
        if dir_ in room.doors:
            door_obj = room.doors[dir_]
            attributes_names = [attr.name for attr in door_obj.get_attributes()]
            if "open" in attributes_names:
                exits_with_open_door.append((dir_, door_obj))
            else:
                exits_with_closed_door.append((dir_, door_obj))
        else:
            exits_without_door.append(dir_)

    exits_desc = []
    # Describing exits with door.
    if grammar.flags.blend_descriptions and len(exits_with_closed_door) > 1:
        dirs, door_objs = zip(*exits_with_closed_door)
        e_desc = grammar.expand("#room_desc_doors_closed#")
        e_desc = replace_num(e_desc, len(door_objs))
        e_desc = e_desc.replace("(dir)", list_to_string(dirs, False))
        e_desc = clean_replace_objs(grammar, e_desc, door_objs, game.infos)
        e_desc = repl_sing_plur(e_desc, len(door_objs))
        exits_desc.append(e_desc)

    else:
        for dir_, door_obj in exits_with_closed_door:
            d_desc = expand_clean_replace(" #room_desc_(d)#", grammar, door_obj, game.infos)
            d_desc = d_desc.replace("(dir)", dir_)
            exits_desc.append(d_desc)

    if grammar.flags.blend_descriptions and len(exits_with_open_door) > 1:
        dirs, door_objs = zip(*exits_with_open_door)
        e_desc = grammar.expand("#room_desc_doors_open#")
        e_desc = replace_num(e_desc, len(door_objs))
        e_desc = e_desc.replace("(dir)", list_to_string(dirs, False))
        e_desc = clean_replace_objs(grammar, e_desc, door_objs, game.infos)
        e_desc = repl_sing_plur(e_desc, len(door_objs))
        exits_desc.append(e_desc)

    else:
        for dir_, door_obj in exits_with_open_door:
            d_desc = expand_clean_replace(" #room_desc_(d)#", grammar, door_obj, game.infos)
            d_desc = d_desc.replace("(dir)", dir_)
            exits_desc.append(d_desc)

    # Describing exits without door.
    if grammar.flags.blend_descriptions and len(exits_without_door) > 1:
        e_desc = grammar.expand("#room_desc_exits#").replace("(dir)", list_to_string(exits_without_door, False))
        e_desc = repl_sing_plur(e_desc, len(exits_without_door))
        exits_desc.append(e_desc)
    else:
        for dir_ in exits_without_door:
            e_desc = grammar.expand("#room_desc_(dir)#").replace("(dir)", dir_)
            exits_desc.append(e_desc)

    room_desc += " ".join(exits_desc)

    # Finally, set the description
    return fix_determinant(room_desc)
Example #22
0
def generate_inform7_source(game, seed=1234, use_i7_description=False):
    var_infos = game.infos
    world = game.world
    quests = game.quests

    source = ""
    source += "When play begins, seed the random-number generator with {}.\n\n".format(
        seed)
    source += define_inform7_kinds()
    # Mention that rooms have a special text attribute called 'internal name'.
    source += "A room has a text called internal name.\n\n"

    # Define custom addons.
    source += data.INFORM7_ADDONS_CODE + "\n"

    # Declare all rooms.
    room_names = [room.id for room in world.rooms]
    source += "The " + " and the ".join(room_names) + " are rooms.\n\n"

    # Process the rooms.
    for room in world.rooms:
        room_infos = var_infos[room.id]
        room_name = room_infos.name

        if not use_i7_description:
            # Describe the room.
            room_desc = room_infos.desc
            source += "The internal name of {} is \"{}\".\n".format(
                room.id, room_name)
            source += "The printed name of {} is \"-= {} =-\".\n".format(
                room.id, str.title(room_name))

            parts = []
            splits = re.split("\[end if\]", room_desc)
            for split in splits:
                part_name = "{} part {}".format(room_name, len(parts))
                text = "The {name} is some text that varies. The {name} is \"{desc}\".\n"
                if split != splits[-1]:
                    split += "[end if]"

                source += text.format(name=part_name, desc=split)
                parts.append(part_name)

            source += "The description of {} is \"{}\".\n".format(
                room.id, "".join("[{}]".format(part) for part in parts))
            source += "\n"

        # List the room's attributes.
        source += gen_source_for_map(room)

    # Declare all objects
    for vtype in data.get_types():
        if vtype in ["P", "I"]:
            continue  # Skip player and inventory.

        entities = world.get_entities_per_type(vtype)
        if len(entities) == 0:
            continue  # No entity of that specific type.

        kind = data.INFORM7_VARIABLES[vtype]
        names = [entity.id for entity in entities]
        source += "The " + " and the ".join(names) + " are {}s.\n".format(kind)
        # All objects are privately-named and we manually define all "Understand as" phrases needed.
        source += "The " + " and the ".join(names) + " are privately-named.\n"

    # Process the objects.
    source += "\n"
    source += gen_source_for_objects(world.objects,
                                     var_infos,
                                     use_i7_description=use_i7_description)
    source += "\n\n"

    # Place the player.
    source += "The player is in {}.\n\n".format(
        var_infos[world.player_room.id].id)

    objective = game.objective
    maximum_score = 0
    for quest_id, quest in enumerate(quests):
        commands = gen_commands_from_actions(quest.actions, var_infos)
        quest.commands = commands
        maximum_score += quest.reward

        quest_completed = textwrap.dedent("""\
        The quest{quest_id} completed is a truth state that varies.
        The quest{quest_id} completed is usually false.
        """)
        source += quest_completed.format(quest_id=quest_id)

        walkthrough = '\nTest quest{} with "{}"\n\n'.format(
            quest_id, " / ".join(commands))
        source += walkthrough

        # Add winning and losing conditions for quest.
        quest_ending_condition = """\
        Every turn:
            if {losing_tests}:
                end the story; [Lost]
            else if quest{quest_id} completed is false and {winning_tests}:
                increase the score by {reward}; [Quest completed]
                Now the quest{quest_id} completed is true.

        """

        winning_tests = gen_source_for_conditions(
            quest.win_action.preconditions)

        losing_tests = "1 is 0 [always false]"
        if quest.fail_action is not None:
            losing_tests = gen_source_for_conditions(
                quest.fail_action.preconditions)

        quest_ending_condition = quest_ending_condition.format(
            losing_tests=losing_tests,
            winning_tests=winning_tests,
            reward=quest.reward,
            quest_id=quest_id)
        source += textwrap.dedent(quest_ending_condition)

    # Enable scoring is at least one quest has nonzero reward.
    if maximum_score != 0:
        source += "Use scoring. The maximum score is {}.\n".format(
            maximum_score)

    # Build test condition for winning the game.
    game_winning_test = "1 is 0 [always false]"
    if len(quests) > 0:
        test_template = "quest{} completed is true"
        game_winning_test = " and ".join(
            test_template.format(i) for i in range(len(quests)))

    # Remove square bracket when printing score increases. Square brackets are conflicting with
    # Inform7's events parser in git_glulx_ml.py.
    # And add winning conditions for the game.
    source += textwrap.dedent("""\
    This is the simpler notify score changes rule:
        If the score is not the last notified score:
            let V be the score - the last notified score;
            say "Your score has just gone up by [V in words] ";
            if V > 1:
                say "points.";
            else:
                say "point.";
            Now the last notified score is the score;
        if {game_winning_test}:
            end the story finally; [Win]

    The simpler notify score changes rule substitutes for the notify score changes rule.

    """.format(game_winning_test=game_winning_test))

    if not use_i7_description:
        # Remove Inform7 listing of nondescript items.
        source += textwrap.dedent("""\
        Rule for listing nondescript items:
            stop.

        """)

    else:
        # List exits in room description
        source += textwrap.dedent("""\
        [Ref: http://dhayton.haverford.edu/wp-content/uploads/Inform-manuals/Rex434.html#e434]
        The initial appearance of a door is usually "Nearby [an item described] leads [if the other side of the item described is visited][direction of the item described from the location] to [the other side][otherwise][direction of the item described from the location][end if]. It is [if open]open[else if closed]closed[otherwise]closed[end if]."

        Direction-relevance relates a door (called X) to a direction (called Y) when the direction of X from the location is Y. The verb to be directionally-relevant to means the direction-relevance relation.

        Understand "[something related by direction-relevance] door" as a door.

        Rule for printing a parser error when the player's command includes "[non-door direction] door":
            say "There is no door in that direction." instead.

        Definition: a direction (called direction D) is non-door:
            let the target be the room-or-door direction D from the location;
            if the target is a door:
                no;
            yes;

        """)

        source += textwrap.dedent("""\
        Definition: a direction (called thataway) is viable if the room thataway from the location is a room and the room-or-door thataway from the location is a room.

        After looking:
            if list of viable directions is not empty:
                say "You can also go [list of viable directions] from here.".

        """)

    # Replace default banner with a greeting message and the quest description.
    source += textwrap.dedent("""\
    Rule for printing the banner text:
        say "{objective}[line break]".

    """.format(objective=objective))

    # Simply display *** The End *** when game ends.
    source += textwrap.dedent("""\
    Include Basic Screen Effects by Emily Short.

    Rule for printing the player's obituary:
        if story has ended finally:
            center "*** The End ***";
        else:
            center "*** You lost! ***";
        say paragraph break;
        let X be the turn count;
        if restrict commands option is true:
            let X be the turn count minus one;
        say "You scored [score] out of a possible [maximum score], in [X] turn(s).";
        [wait for any key;
        stop game abruptly;]
        rule succeeds.

    """)

    # Disable implicitly taking something.
    source += textwrap.dedent("""\
    Rule for implicitly taking something (called target):
        if target is fixed in place:
            say "The [target] is fixed in place.";
        otherwise:
            say "You need to take the [target] first.";
        stop.

    """)

    # Referring to an object by its whole name shouldn't be ambiguous.
    source += textwrap.dedent("""\
    Does the player mean doing something:
        if the noun is not nothing and the second noun is nothing and the player's command matches the text printed name of the noun:
            it is likely;
        if the noun is nothing and the second noun is not nothing and the player's command matches the text printed name of the second noun:
            it is likely;
        if the noun is not nothing and the second noun is not nothing and the player's command matches the text printed name of the noun and the player's command matches the text printed name of the second noun:
            it is very likely.  [Handle action with two arguments.]
    """)

    # Useful for listing room contents with their properties.
    source += textwrap.dedent("""\
    Printing the content of the room is an activity.
    Rule for printing the content of the room:
        let R be the location of the player;
        say "Room contents:[line break]";
        list the contents of R, with newlines, indented, including all contents, with extra indentation.

    """)

    # Useful for listing world contents with their properties.
    source += textwrap.dedent("""\
    Printing the content of the world is an activity.
    Rule for printing the content of the world:
        let L be the list of the rooms;
        say "World: [line break]";
        repeat with R running through L:
            say "  [the internal name of R][line break]";
        repeat with R running through L:
            say "[the internal name of R]:[line break]";
            if the list of things in R is empty:
                say "  nothing[line break]";
            otherwise:
                list the contents of R, with newlines, indented, including all contents, with extra indentation.

    """)

    # Useful for listing inventory contents with their properties.
    source += textwrap.dedent("""\
    Printing the content of the inventory is an activity.
    Rule for printing the content of the inventory:
        say "Inventory:[line break]";
        list the contents of the player, with newlines, indented, giving inventory information, including all contents, with extra indentation.

    """)

    # Useful for listing off-stage contents with their properties.
    source += textwrap.dedent("""\
    Printing the content of nowhere is an activity.
    Rule for printing the content of nowhere:
        say "Nowhere:[line break]";
        let L be the list of the off-stage things;
        repeat with thing running through L:
            say "  [thing][line break]";

    """)

    # Useful for listing things laying on the floor.
    source += textwrap.dedent("""\
    Printing the things on the floor is an activity.
    Rule for printing the things on the floor:
        let R be the location of the player;
        let L be the list of things in R;
        remove yourself from L;
        remove the list of containers from L;
        remove the list of supporters from L;
        remove the list of doors from L;
        if the number of entries in L is greater than 0:
            say "There is [L with indefinite articles] on the floor.";
        
    """)

    # Print properties of objects when listing the inventory contents and the room contents.
    source += textwrap.dedent("""\
    After printing the name of something (called target) while
    printing the content of the room
    or printing the content of the world
    or printing the content of the inventory
    or printing the content of nowhere:
        follow the property-aggregation rules for the target.

    The property-aggregation rules are an object-based rulebook.
    The property-aggregation rulebook has a list of text called the tagline.

    [At the moment, we only support "open/unlocked", "closed/unlocked" and "closed/locked" for doors and containers.]
    [A first property-aggregation rule for an openable open thing (this is the mention open openables rule):
        add "open" to the tagline.

    A property-aggregation rule for an openable closed thing (this is the mention closed openables rule):
        add "closed" to the tagline.

    A property-aggregation rule for an lockable unlocked thing (this is the mention unlocked lockable rule):
        add "unlocked" to the tagline.

    A property-aggregation rule for an lockable locked thing (this is the mention locked lockable rule):
        add "locked" to the tagline.]

    A first property-aggregation rule for an openable lockable open unlocked thing (this is the mention open openables rule):
        add "open" to the tagline.

    A property-aggregation rule for an openable lockable closed unlocked thing (this is the mention closed openables rule):
        add "closed" to the tagline.

    A property-aggregation rule for an openable lockable closed locked thing (this is the mention locked openables rule):
        add "locked" to the tagline.

    A property-aggregation rule for a lockable thing (called the lockable thing) (this is the mention matching key of lockable rule):
        let X be the matching key of the lockable thing;
        if X is not nothing:
            add "match [X]" to the tagline.

    A property-aggregation rule for an edible off-stage thing (this is the mention eaten edible rule):
        add "eaten" to the tagline.

    The last property-aggregation rule (this is the print aggregated properties rule):
        if the number of entries in the tagline is greater than 0:
            say " ([tagline])";
            rule succeeds;
        rule fails;

    """)

    source += textwrap.dedent("""\
    An objective is some text that varies. The objective is "{objective}".
    """.format(objective=objective))

    # Special command to print the objective of the game, if any.
    source += textwrap.dedent("""\
    Printing the objective is an action applying to nothing.
    Carry out printing the objective:
        say "[objective]".

    Understand "goal" as printing the objective.

    """)

    # Customize reporting of the "take" action.
    # Ref: http://inform7.com/learn/man/RB_6_8.html
    source += textwrap.dedent("""\
    The taking action has an object called previous locale (matched as "from").

    Setting action variables for taking: 
        now previous locale is the holder of the noun.

    Report taking something from the location: 
        say "You pick up [the noun] from the ground." instead.

    Report taking something: 
        say "You take [the noun] from [the previous locale]." instead.

    Report dropping something: 
        say "You drop [the noun] on the ground." instead.

    """)

    # Special command to print game state.
    source += textwrap.dedent("""\
    The print state option is a truth state that varies.
    The print state option is usually false.

    Turning on the print state option is an action applying to nothing.
    Carry out turning on the print state option:
        Now the print state option is true.

    Turning off the print state option is an action applying to nothing.
    Carry out turning off the print state option:
        Now the print state option is false.

    Printing the state is an activity.
    Rule for printing the state:
        let R be the location of the player;
        say "Room: [line break] [the internal name of R][line break]";
        [say "[line break]";
        carry out the printing the content of the room activity;]
        say "[line break]";
        carry out the printing the content of the world activity;
        say "[line break]";
        carry out the printing the content of the inventory activity;
        say "[line break]";
        carry out the printing the content of nowhere activity;
        say "[line break]".

    Printing the entire state is an action applying to nothing.
    Carry out printing the entire state:
        say "-=STATE START=-[line break]";
        carry out the printing the state activity;
        say "[line break]Score:[line break] [score]/[maximum score][line break]";
        say "[line break]Objective:[line break] [objective][line break]";
        say "[line break]Inventory description:[line break]";
        say "  You are carrying: [a list of things carried by the player].[line break]";
        say "[line break]Room description:[line break]";
        try looking;
        say "[line break]-=STATE STOP=-";

    When play begins:
        if print state option is true:
            try printing the entire state;

    Every turn:
        if print state option is true:
            try printing the entire state;

    When play ends:
        if print state option is true:
            try printing the entire state;

    After looking:
        carry out the printing the things on the floor activity.

    Understand "print_state" as printing the entire state.
    Understand "enable print state option" as turning on the print state option.
    Understand "disable print state option" as turning off the print state option.

    """)

    # Disable implicitly opening/unlocking door.
    source += textwrap.dedent("""\
    Before going through a closed door (called the blocking door):
        say "You have to open the [blocking door] first.";
        stop.

    Before opening a locked door (called the locked door):
        let X be the matching key of the locked door;
        if X is nothing:
            say "The [locked door] is welded shut.";
        otherwise:
            say "You have to unlock the [locked door] with the [X] first.";
        stop.

    Before opening a locked container (called the locked container):
        let X be the matching key of the locked container;
        if X is nothing:
            say "The [locked container] is welded shut.";
        otherwise:
            say "You have to unlock the [locked container] with the [X] first.";
        stop.

    """)

    # Add new actions.
    source += textwrap.dedent("""\
    Displaying help message is an action applying to nothing.
    Carry out displaying help message:
        say "[fixed letter spacing]Available commands:[line break]";
        say "  look:                                describe the current room[line break]";
        say "  goal:                                print the goal of this game[line break]";
        say "  inventory:                           print player's inventory[line break]";
        say "  go <dir>:                            move the player north, east, south or west[line break]";
        say "  examine <something>:                 examine something more closely[line break]";
        say "  eat <something>:                     eat something edible[line break]";
        say "  open <something>:                    open a door or a container[line break]";
        say "  close <something>:                   close a door or a container[line break]";
        say "  drop <something>:                    drop an object on the floor[line break]";
        say "  take <something>:                    take an object that is on the floor[line break]";
        say "  put <something> on <something>:      place an object on a supporter[line break]";
        say "  take <something> from <something>:   take an object from a container or a supporter[line break]";
        say "  insert <something> into <something>: place an object into a container[line break]";
        say "  lock <something> with <something>:   lock a door or a container with a key[line break]";
        say "  unlock <something> with <something>: unlock a door or a container with a key[line break]";

    Understand "help" as displaying help message.

    """)

    # Disable take/get all.
    source += textwrap.dedent("""\
        Taking all is an action applying to nothing.
        Carry out taking all:
            say "You have to be more specific!".

        Understand "take all" as taking all.
        Understand "get all" as taking all.
        Understand "pick up all" as taking all.

        Understand "take each" as taking all.
        Understand "get each" as taking all.
        Understand "pick up each" as taking all.

        Understand "take everything" as taking all.
        Understand "get everything" as taking all.
        Understand "pick up everything" as taking all.

    """)

    # Special command to restrict possible actions.
    source += textwrap.dedent("""\
    The restrict commands option is a truth state that varies.
    The restrict commands option is usually false.

    Turning on the restrict commands option is an action applying to nothing.
    Carry out turning on the restrict commands option:
        Decrease turn count by 1;
        Now the restrict commands option is true.

    Understand "restrict commands" as turning on the restrict commands option.

    """)

    # If "restrict commands" mode is on, force the player to mention where to
    # take the object from.
    source += textwrap.dedent("""\
    The taking allowed flag is a truth state that varies.
    The taking allowed flag is usually false.

    Before removing something from something:
        now the taking allowed flag is true.

    After removing something from something:
        now the taking allowed flag is false.

    Before taking a thing (called the object) when the object is on a supporter (called the supporter):
        if the restrict commands option is true and taking allowed flag is false:
            say "Can't see any [object] on the floor! Try taking the [object] from the [supporter] instead.";
            rule fails.

    Before of taking a thing (called the object) when the object is in a container (called the container):
        if the restrict commands option is true and taking allowed flag is false:
            say "Can't see any [object] on the floor! Try taking the [object] from the [container] instead.";
            rule fails.

    """)

    # Add dummy object to detect end of the objects tree.
    source += textwrap.dedent("""\
        There is a EndOfObject.

    """)

    # Indent using \t instead of spaces because of Inform6.
    while True:
        last = source
        source = re.sub("(^ *)    ", r"\1\t", source, flags=re.MULTILINE)
        if source == last:
            break

    return source
Example #23
0
    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 = data.get_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 = data.get_types().sample(parent_type='t',
                                                   rng=rng,
                                                   exceptions=["d", "r"],
                                                   include_parent=False,
                                                   probs=object_types_probs)

            if data.get_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 data.get_types().is_descendant_of(obj_holder.type, "s"):
                    state.append(Proposition("on", [obj, obj_holder]))
                elif data.get_types().is_descendant_of(obj_holder.type, "c"):
                    state.append(Proposition("in", [obj, obj_holder]))
                elif data.get_types().is_descendant_of(obj_holder.type, "I"):
                    state.append(Proposition("in", [obj, obj_holder]))
                elif data.get_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 data.get_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 data.get_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
Example #24
0
 def test_objects_types(self):
     expected_types = set(data.get_types().types)
     assert set(self.game.objects_types) == expected_types
Example #25
0
    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 data.get_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 data.get_types().is_descendant_of(obj_holder.type, "s"):
                    state.append(Proposition("on", [obj, obj_holder]))
                elif data.get_types().is_descendant_of(obj_holder.type, "c"):
                    state.append(Proposition("in", [obj, obj_holder]))
                elif data.get_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 data.get_types().is_descendant_of(obj_type, "s"):
                supporter = obj
                state.append(Proposition("at", [supporter, room]))
                objects_holder.append(supporter)

            elif data.get_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