def uuid(self) -> str: # TODO: generate uuid from chaining options? uuid = "tw-game-{specs}-{grammar}-{seeds}" uuid = uuid.format(specs=encode_seeds((self.nb_rooms, self.nb_objects, self.quest_length, self.quest_breadth)), grammar=self.grammar.uuid, seeds=encode_seeds([self.seeds[k] for k in sorted(self._seeds)])) return uuid
def uuid(self) -> str: # TODO: incomplete, finish this uuid = "tw-game-{specs}-{surface}-{seeds}" uuid = uuid.format( specs=encode_seeds((self.quest_gen_options.max_depth, self.quest_gen_options.max_steps)), surface=self.surface_gen_options.uuid, seeds=encode_seeds([self.seeds[k] for k in sorted(self._seeds)])) return uuid
def _make_game(self, slot_seed: int, attempt_seed: int) -> str: options = LabGameOptions() options.seeds = attempt_seed game = make_game_from_level(self.level, options) hashid = encode_seeds( [self.generator_seed, self.level] + [options.seeds[k] for k in sorted(options.seeds)]) # slot seed in file name so we can reconstruct game collection # map upon loading an existing directory game_name = "{}_{}_{}".format(self.spec.id, hashid, slot_seed) options.path = str(self.output_dir / (game_name + ".ulx")) game_file = tw_textlabs.generator.compile_game(game, options) return game_file
def make_lab_game(options: LabGameOptions) -> Game: """ Make a lab game (map + devices + quest). Arguments: nb_devices: Number of devices in the world. : Options for the grammar. Returns: Generated game. """ max_quest_length = options.quest_gen_options.max_depth surface_gen_options = options.surface_gen_options max_search_steps = options.quest_gen_options.max_steps seeds = options.seeds metadata = {} # Collect infos for reproducibility. metadata["desc"] = "Lab-Game" metadata["mode"] = "custom" metadata["seeds"] = seeds metadata["world_size"] = 1 # Single room always metadata["max_quest_length"] = max_quest_length rng_quest = options.rngs['quest'] sg = SurfaceGenerator(seed=seeds['surface'], surface_gen_options=surface_gen_options) devices = options.lab_config.devices material_states = options.lab_config.material_states min_uses_per_device = options.lab_config.min_uses_per_device max_uses_per_device = options.lab_config.max_uses_per_device device_types = uniquify(KnowledgeBase.default().types.descendants('sa')) M = LabGameMaker(surface_generator=sg) # Create the lab room. lab = M.new_room("lab") # - Describe the room. lab.desc = "The lab is a magical place where you'll learn materials synthesis!" # Used so that Inform7 will be able to reference names of any of the # devices (needed for the single argument hack), but we only want the player # to be able to interact with the chosen ones. limbo = M.new_room("Limbo") limbo.desc = "Storeroom for unused devices." M.set_player(lab) # Add materials to the lab. for mat_state in material_states: mat = M.new_lab_entity('m') mat.add_property(mat_state) lab.add(mat) if options.lab_config.lab_container_available: # Add lab_container to the world. lab_container = M.new_lab_container() lab.add(lab_container) # Add devices to the world. We add all device types but only the available # ones to the lab room for device_type in device_types: device = M.new_lab_entity(device_type) if device_type in devices: lab.add(device) else: limbo.add(device) quest_gen_options = QuestGenerationOptions(max_depth=max_quest_length, max_steps=max_search_steps, win_condition=WinConditionType.ALL, quest_rng=rng_quest, min_uses_per_device=min_uses_per_device, max_uses_per_device=max_uses_per_device ) quest = M.generate_quest_surface_pair(quest_gen_options) game = M.build() game.metadata = metadata # TODO add uuid based on rest of settings uuid = "tw-custom-lab-game-{specs}-{surface_gen}-{seeds}" uuid = uuid.format(specs=encode_seeds((quest_gen_options.max_depth, max_search_steps)), surface_gen=surface_gen_options.uuid, seeds=encode_seeds([seeds[k] for k in sorted(seeds)])) game.metadata["uuid"] = uuid return game
def make_game(mode: str, options: GameOptions) -> tw_textlabs.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:`tw_textlabs.GameOptions <tw_textlabs.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 = tw_textlabs.GameMaker() M.grammar = tw_textlabs.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 make_game(mode: str, lab_game_options: LabGameOptions) -> tw_textlabs.Game: """ Make a TextLab game. Arguments: mode: Difficulty mode, where the different modes correspond to: lab_game_options: Options for customizing the game generation. Returns: game: Generated game. Notes: Difficulty modes are defined as follows: * easy: nb. operations ranging from 1 to 2, nb. materials ranging from 2 to 3 as the difficulty increases. Max number of descriptors per entity is 1; * medium: nb. operations ranging from 3 to 5, nb. materials ranging from 3 to 4 as the difficulty increases. Max number of descriptors per entity is 2; * hard: nb. operations ranging from 4 to 6, nb. materials ranging from 5 to 8 as the difficulty increases. Max number of descriptors per entity is 3; Returns: Generated game. """ quest_gen_options = lab_game_options.quest_gen_options max_quest_length = quest_gen_options.max_depth surface_gen_options = lab_game_options.surface_gen_options quest_gen_options.win_condition = WinConditionType.ALL # Deal with any missing random seeds. seeds = get_seeds_for_game_generation(lab_game_options.seeds) rng_objects = lab_game_options.rngs['objects'] rng_quest = lab_game_options.rngs['quest'] n_materials = lab_game_options.quest_gen_options.nb_materials n_ops = lab_game_options.quest_gen_options.nb_ops all_op_types = KnowledgeBase.default().types.descendants('toe') chosen_op_types = rng_objects.choice(all_op_types, n_ops, replace=True) if lab_game_options.preset_ops: surface_gen_options.op_type_map = { 'tlq_op_{}'.format(i): chosen_op_types[i] for i in range(n_ops) } sg = SurfaceGenerator(surface_gen_options=surface_gen_options) quest_gen_options.quest_rng = rng_quest modes = ["easy", "medium", "hard"] mode_idx = modes.index(mode) metadata = {} # Collect infos for reproducibility. metadata["orig_seed"] = lab_game_options.seeds metadata["desc"] = "Lab-Game" metadata["mode"] = mode metadata["seeds"] = seeds metadata["world_size"] = 1 # Single room always metadata["max_quest_length"] = max_quest_length metadata["surface_gen_options"] = surface_gen_options.serialize() if mode == "easy": material_states = ["powder"] elif mode == "medium": material_states = rng_objects.choice(list(MATERIAL_STATES), n_materials, replace=True) elif mode == "hard": material_states = list(MATERIAL_STATES) M = LabGameMaker(surface_generator=sg) # Create the lab room. lab = M.new_room("lab") M.set_player(lab) # - Describe the room. lab.desc = "The lab is a magical place where you'll learn materials synthesis" # Add materials to the world. ent_type = 'm' n_max_descs = quest_gen_options.max_descs_per_ent[ent_type] for i in range(n_materials): mat_state = rng_objects.choice(material_states) mat = M.new_lab_entity('m') mat.add_property(mat_state) lab.add(mat) n_descs = rng_objects.randint(0, (n_max_descs + 1)) for j in range(n_descs): mdesc = M.new_lab_entity('mdsc') lab.add(mdesc) quest_gen_options.ent_desc_map[mat.var.name].append(mdesc.var.name) # Add operations and their descriptors ent_type = 'tlq_op' n_max_descs = quest_gen_options.max_descs_per_ent[ent_type] ops = [] # Either preset ops if lab_game_options.preset_ops: ops = [ M.new_tlq_op(dynamic_define=False, op_type=chosen_op_types[i]) for i in range(n_ops) ] op_type_map = { op.var.name: chosen_op_types[i] for i, op in enumerate(ops) } assert op_type_map == surface_gen_options.op_type_map else: # or make their classification part of the quest ops = [M.new_tlq_op(dynamic_define=True) for i in range(n_ops)] for op in ops: lab.add(op) n_descs = rng_objects.randint(0, (n_max_descs + 1)) for j in range(n_descs): odesc = M.new_lab_entity('odsc') lab.add(odesc) quest_gen_options.ent_desc_map[op.var.name].append(odesc.var.name) # Generate quest and corresponding surface (set in quest description) quest = M.generate_quest_surface_pair(quest_gen_options) game = M.build() game.metadata = metadata mode_choice = modes.index(mode) # TODO add uuid based on rest of settings uuid = "tw-lab_game-{specs}-{surface_gen}-{seeds}" uuid = uuid.format(specs=encode_seeds( (mode_choice, quest_gen_options.max_depth)), surface_gen=surface_gen_options.uuid, seeds=encode_seeds([seeds[k] for k in sorted(seeds)])) game.metadata["uuid"] = uuid return game
def make_game(mode: str, options: GameOptions) -> tw_textlabs.Game: """ Make a Treasure Hunter game. Arguments: mode: Mode for the game where * `'easy'`: rooms are all empty except where the two objects are placed. Also, connections between rooms have no door. * `'medium'`: adding closed doors and containers that might need to be open in order to find the object. * `'hard'`: adding locked doors and containers (necessary keys will in the inventory) that might need to be unlocked (and open) in order to find the object. options: For customizing the game generation (see :py:class:`tw_textlabs.GameOptions <tw_textlabs.generator.game.GameOptions>` for the list of available options). Returns: Generated game. """ kb = KnowledgeBase.default() metadata = {} # Collect infos for reproducibility. metadata["desc"] = "Treasure Hunter" metadata["mode"] = mode metadata["seeds"] = options.seeds metadata["world_size"] = options.nb_rooms metadata["quest_length"] = options.quest_length rngs = options.rngs rng_map = rngs['map'] rng_objects = rngs['objects'] rng_quest = rngs['quest'] rng_grammar = rngs['grammar'] modes = ["easy", "medium", "hard"] if mode == "easy": door_states = None n_distractors = 0 elif mode == "medium": door_states = ["open", "closed"] n_distractors = 10 elif mode == "hard": door_states = ["open", "closed", "locked"] n_distractors = 20 # Generate map. map_ = tw_textlabs.generator.make_map(n_rooms=options.nb_rooms, rng=rng_map, possible_door_states=door_states) assert len(map_.nodes()) == options.nb_rooms world = World.from_map(map_) # Randomly place the player. starting_room = None if len(world.rooms) > 1: starting_room = rng_map.choice(world.rooms) world.set_player_room(starting_room) # Add object the player has to pick up. types_counts = kb.types.count(world.state) obj_type = kb.types.sample(parent_type='o', rng=rng_objects, include_parent=True) var_id = get_new(obj_type, types_counts) right_obj = Variable(var_id, obj_type) world.add_fact(Proposition("in", [right_obj, world.inventory])) # Add containers and supporters to the world. types_counts = kb.types.count(world.state) objects = [] distractor_types = uniquify(['c', 's'] + kb.types.descendants('c') + kb.types.descendants('s')) for i in range(n_distractors): obj_type = rng_objects.choice(distractor_types) var_id = get_new(obj_type, types_counts) # This update the types_counts. objects.append(Variable(var_id, obj_type)) world.populate_with(objects, rng=rng_objects) # Add object the player should not pick up. types_counts = kb.types.count(world.state) obj_type = kb.types.sample(parent_type='o', rng=rng_objects, include_parent=True) var_id = get_new(obj_type, types_counts) wrong_obj = Variable(var_id, obj_type) # Place it anywhere in the world. world.populate_with([wrong_obj], rng=rng_objects) # Generate a quest that finishes by taking something (i.e. the right # object since it's the only one in the inventory). options.chaining.rules_per_depth = [kb.rules.get_matching("take.*")] options.chaining.backward = True options.chaining.rng = rng_quest #options.chaining.restricted_types = exceptions #exceptions = ["r", "c", "s", "d"] if mode == "easy" else ["r"] chain = tw_textlabs.generator.sample_quest(world.state, options.chaining) # Add objects needed for the quest. world.state = chain.initial_state event = Event(chain.actions) quest = Quest(win_events=[event], fail_events=[Event(conditions={Proposition("in", [wrong_obj, world.inventory])})]) grammar = tw_textlabs.generator.make_grammar(options.grammar, rng=rng_grammar) game = tw_textlabs.generator.make_game_with(world, [quest], grammar) game.metadata = metadata mode_choice = modes.index(mode) uuid = "tw-treasure_hunter-{specs}-{grammar}-{seeds}" uuid = uuid.format(specs=encode_seeds((mode_choice, options.nb_rooms, options.quest_length)), grammar=options.grammar.uuid, seeds=encode_seeds([options.seeds[k] for k in sorted(options.seeds)])) game.metadata["uuid"] = uuid return game