def connect(room1: Variable, direction: str, room2: Variable, door: Optional[Variable] = None) -> List[Proposition]: """ Generate predicates that connect two rooms. Args: room1: A room variable. direction: Direction that we need to travel to go from room1 to room2. room2: A room variable. door: The door separating the two rooms. If `None`, there is no door between the rooms. """ r_direction = reverse_direction(direction) + "_of" direction += "_of" facts = [ Proposition(direction, [room2, room1]), Proposition(r_direction, [room1, room2]), Proposition("free", [room1, room2]), Proposition("free", [room2, room1]) ] if door is not None: facts += [ Proposition("link", [room1, door, room2]), Proposition("link", [room2, door, room1]) ] return facts
def test_automatically_positioning_rooms(): P = Variable("P") r1 = Variable("Room1", "r") r2 = Variable("Room2", "r") d = Variable("door", "d") facts = [Proposition("at", [P, r1])] world = World.from_facts(facts) assert len(world.rooms) == 1 assert len(world.find_room_by_id(r1.name).exits) == 0 world.add_fact(Proposition("link", [r1, d, r2])) assert len(world.rooms) == 2 r1_entity = world.find_room_by_id(r1.name) r2_entity = world.find_room_by_id(r2.name) assert len(r1_entity.exits) == 1 assert len(r2_entity.exits) == 1 assert list(r1_entity.exits.keys())[0] == reverse_direction(list(r2_entity.exits.keys())[0])
def make_game(mode: str, options: GameOptions) -> textworld.Game: """ Make a Coin Collector game. Arguments: mode: Mode for the game where * `'simple'`: the distractor rooms are only placed orthogonaly to the chain. This means moving off the optimal path leads immediately to a dead end. * `'random'`: the distractor rooms are randomly place along the chain. This means a player can wander for a while before reaching a dead end. options: For customizing the game generation (see :py:class:`textworld.GameOptions <textworld.generator.game.GameOptions>` for the list of available options). Returns: Generated game. """ if mode == "simple" and float(options.nb_rooms) / options.quest_length > 4: msg = ("Total number of rooms must be less than 4 * `quest_length` " "when distractor mode is 'simple'.") raise ValueError(msg) metadata = {} # Collect infos for reproducibility. metadata["desc"] = "Coin Collector" metadata["mode"] = mode metadata["seeds"] = options.seeds metadata["world_size"] = options.nb_rooms metadata["quest_length"] = options.quest_length rngs = options.rngs rng_map = rngs['map'] rng_grammar = rngs['grammar'] # Generate map. M = textworld.GameMaker() M.grammar = textworld.generator.make_grammar(options.grammar, rng=rng_grammar) rooms = [] walkthrough = [] for i in range(options.quest_length): r = M.new_room() if i >= 1: # Connect it to the previous rooms. free_exits = [k for k, v in rooms[-1].exits.items() if v.dest is None] src_exit = rng_map.choice(free_exits) dest_exit = reverse_direction(src_exit) M.connect(rooms[-1].exits[src_exit], r.exits[dest_exit]) walkthrough.append("go {}".format(src_exit)) rooms.append(r) M.set_player(rooms[0]) # Add object the player has to pick up. obj = M.new(type="o", name="coin") rooms[-1].add(obj) # Add distractor rooms, if needed. chain_of_rooms = list(rooms) while len(rooms) < options.nb_rooms: if mode == "random": src = rng_map.choice(rooms) else: # Add one distractor room per room along the chain. src = chain_of_rooms[len(rooms) % len(chain_of_rooms)] free_exits = [k for k, v in src.exits.items() if v.dest is None] if len(free_exits) == 0: continue dest = M.new_room() src_exit = rng_map.choice(free_exits) dest_exit = reverse_direction(src_exit) M.connect(src.exits[src_exit], dest.exits[dest_exit]) rooms.append(dest) # Generate the quest thats by collecting the coin. walkthrough.append("take coin") # TODO: avoid compiling the game at all (i.e. use the inference engine). M.set_quest_from_commands(walkthrough) game = M.build() game.metadata = metadata mode_choice = 0 if mode == "simple" else 1 uuid = "tw-coin_collector-{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
def _process_rooms(self) -> None: for fact in self.facts: if not self.kb.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 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 Coin Collector game. Arguments: mode: Mode for the game where * `'simple'`: the distractor rooms are only placed orthogonaly to the chain. This means moving off the optimal path leads immediately to a dead end. * `'random'`: the distractor rooms are randomly place along the chain. This means a player can wander for a while before reaching a dead end. n_rooms: Number of rooms in the game. quest_length: Number of rooms in the chain. This also represents the number of commands for the optimal policy. 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. """ if mode == "simple" and float(n_rooms) / quest_length > 4: msg = ("Total number of rooms must be less than 4 * `quest_length` " "when distractor mode is 'simple'.") raise ValueError(msg) # Deal with any missing random seeds. seeds = get_seeds_for_game_generation(seeds) metadata = {} # Collect infos for reproducibility. metadata["desc"] = "Coin Collector" 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_grammar = np.random.RandomState(seeds['seed_grammar']) # Generate map. M = textworld.GameMaker() M.grammar = textworld.generator.make_grammar(flags=grammar_flags, rng=rng_grammar) rooms = [] walkthrough = [] for i in range(quest_length): r = M.new_room() if i >= 1: # Connect it to the previous rooms. free_exits = [ k for k, v in rooms[-1].exits.items() if v.dest is None ] src_exit = rng_map.choice(free_exits) dest_exit = reverse_direction(src_exit) M.connect(rooms[-1].exits[src_exit], r.exits[dest_exit]) walkthrough.append("go {}".format(src_exit)) rooms.append(r) M.set_player(rooms[0]) # Add object the player has to pick up. obj = M.new(type="o", name="coin") rooms[-1].add(obj) # Add distractor rooms, if needed. chain_of_rooms = list(rooms) while len(rooms) < n_rooms: if mode == "random": src = rng_map.choice(rooms) else: # Add one distractor room per room along the chain. src = chain_of_rooms[len(rooms) % len(chain_of_rooms)] free_exits = [k for k, v in src.exits.items() if v.dest is None] if len(free_exits) == 0: continue dest = M.new_room() src_exit = rng_map.choice(free_exits) dest_exit = reverse_direction(src_exit) M.connect(src.exits[src_exit], dest.exits[dest_exit]) rooms.append(dest) # Generate the quest thats by collecting the coin. walkthrough.append("take coin") # TODO: avoid compiling the game at all (i.e. use the inference engine). M.set_quest_from_commands(walkthrough) game = M.build() game.metadata = metadata mode_choice = 0 if mode == "simple" else 1 uuid = "tw-coin_collector-{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