Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions scripts/tw-make
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ def parse_args():
help="Nb. of objects in the world.")
custom_parser.add_argument("--quest-length", type=int, default=5, metavar="LENGTH",
help="Minimum nb. of actions the quest requires to be completed.")
custom_parser.add_argument("--quest-breadth", type=int, default=3, metavar="BREADTH",
help="Control how non-linear a quest can be.")

challenge_parser = subparsers.add_parser("challenge", parents=[general_parser],
help='Generate a game for one of the challenges.')
Expand All @@ -72,7 +74,7 @@ if __name__ == "__main__":
}

if args.subcommand == "custom":
game_file, game = textworld.make(args.world_size, args.nb_objects, args.quest_length, grammar_flags,
game_file, game = textworld.make(args.world_size, args.nb_objects, args.quest_length, args.quest_breadth, grammar_flags,
seed=args.seed, games_dir=args.output)

elif args.subcommand == "challenge":
Expand All @@ -87,7 +89,7 @@ if __name__ == "__main__":

print("Game generated: {}".format(game_file))
if args.verbose:
print(game.quests[0].desc)
print(game.objective)

if args.view:
textworld.render.visualize(game, interactive=True)
2 changes: 1 addition & 1 deletion scripts/tw-stats
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ if __name__ == "__main__":
continue

if len(game.quests) > 0:
objectives[game_filename] = game.quests[0].desc
objectives[game_filename] = game.objective

names |= set(info.name for info in game.infos.values() if info.name is not None)
game_logger.collect(game)
Expand Down
9 changes: 5 additions & 4 deletions tests/test_make_game.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,22 @@ def test_making_game_with_names_to_exclude():
g_rng.set_seed(42)

with make_temp_directory(prefix="test_render_wrapper") as tmpdir:
game_file1, game1 = textworld.make(2, 20, 3, {"names_to_exclude": []},
game_file1, game1 = textworld.make(2, 20, 3, 3, {"names_to_exclude": []},
seed=123, games_dir=tmpdir)

game1_objects_names = [info.name for info in game1.infos.values() if info.name is not None]
game_file2, game2 = textworld.make(2, 20, 3, {"names_to_exclude": game1_objects_names},
game_file2, game2 = textworld.make(2, 20, 3, 3, {"names_to_exclude": game1_objects_names},
seed=123, games_dir=tmpdir)
game2_objects_names = [info.name for info in game2.infos.values() if info.name is not None]
game2.grammar.flags.encode()
assert len(set(game1_objects_names) & set(game2_objects_names)) == 0


def test_making_game_is_reproducible_with_seed():
grammar_flags = {}
with make_temp_directory(prefix="test_render_wrapper") as tmpdir:
game_file1, game1 = textworld.make(2, 20, 3, grammar_flags, seed=123, games_dir=tmpdir)
game_file2, game2 = textworld.make(2, 20, 3, grammar_flags, seed=123, games_dir=tmpdir)
game_file1, game1 = textworld.make(2, 20, 3, 3, grammar_flags, seed=123, games_dir=tmpdir)
game_file2, game2 = textworld.make(2, 20, 3, 3, grammar_flags, seed=123, games_dir=tmpdir)
assert game_file1 == game_file2
assert game1 == game2
# Make sure they are not the same Python objects.
Expand Down
9 changes: 5 additions & 4 deletions tests/test_play_generated_games.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@ def test_play_generated_games():
# Sample game specs.
world_size = rng.randint(1, 10)
nb_objects = rng.randint(0, 20)
quest_length = rng.randint(1, 10)
quest_length = rng.randint(2, 5)
quest_breadth = rng.randint(3, 7)
game_seed = rng.randint(0, 65365)
grammar_flags = {} # Default grammar.

with make_temp_directory(prefix="test_play_generated_games") as tmpdir:
game_file, game = textworld.make(world_size, nb_objects, quest_length, grammar_flags,
game_file, game = textworld.make(world_size, nb_objects, quest_length, quest_breadth, grammar_flags,
seed=game_seed, games_dir=tmpdir)

# Solve the game using WalkthroughAgent.
Expand All @@ -47,10 +48,10 @@ def test_play_generated_games():
game_state, reward, done = env.step(command)

if done:
msg = "Finished before playing `max_steps` steps."
msg = "Finished before playing `max_steps` steps because of command '{}'.".format(command)
if game_state.has_won:
msg += " (winning)"
assert game_state._game_progression.winning_policy == []
assert len(game_state._game_progression.winning_policy) == 0

if game_state.has_lost:
msg += " (losing)"
Expand Down
2 changes: 1 addition & 1 deletion tests/test_textworld.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def test_game_walkthrough_agent(self):
agent = textworld.agents.WalkthroughAgent()
env = textworld.start(self.game_file)
env.activate_state_tracking()
commands = self.game.quests[0].commands
commands = self.game.quests[-1].commands
agent.reset(env)
game_state = env.reset()

Expand Down
4 changes: 2 additions & 2 deletions tests/test_tw_play.py → tests/test_tw-play.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
from textworld.utils import make_temp_directory


def test_making_a_custom_game():
def test_playing_a_game():
with make_temp_directory(prefix="test_tw-play") as tmpdir:
game_file, _ = textworld.make(5, 10, 5, {}, seed=1234, games_dir=tmpdir)
game_file, _ = textworld.make(5, 10, 5, 4, {}, seed=1234, games_dir=tmpdir)

command = ["tw-play", "--max-steps", "100", "--mode", "random", game_file]
assert check_call(command) == 0
Expand Down
2 changes: 1 addition & 1 deletion textworld/agents/walkthrough.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def reset(self, env):
raise NameError(msg)

# Load command from the generated game.
self._commands = iter(env.game.quests[0].commands)
self._commands = iter(env.game.main_quest.commands)

def act(self, game_state, reward, done):
try:
Expand Down
9 changes: 4 additions & 5 deletions textworld/envs/glulx/git_glulx_ml.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,13 +146,10 @@ def init(self, output: str, game=None,
"""
output = _strip_input_prompt_symbol(output)
super().init(output)
self._game_progression = GameProgression(game, track_quest=compute_intermediate_reward)
self._game_progression = GameProgression(game, track_quests=compute_intermediate_reward)
self._state_tracking = state_tracking
self._compute_intermediate_reward = compute_intermediate_reward and len(game.quests) > 0

self._objective = ""
if len(game.quests) > 0:
self._objective = game.quests[0].desc
self._objective = game.objective

def view(self) -> "GlulxGameState":
"""
Expand Down Expand Up @@ -317,6 +314,7 @@ def intermediate_reward(self):

@property
def score(self):
# XXX: Should the score reflect the sum of all subquests' reward?
if self.has_won:
return 1
elif self.has_lost:
Expand All @@ -326,6 +324,7 @@ def score(self):

@property
def max_score(self):
# XXX: Should the score reflect the sum of all subquests' reward?
return 1

@property
Expand Down
2 changes: 1 addition & 1 deletion textworld/envs/wrappers/tests/test_viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def test_html_viewer():
num_items = 10
g_rng.set_seed(1234)
grammar_flags = {"theme": "house", "include_adj": True}
game = textworld.generator.make_game(world_size=num_nodes, nb_objects=num_items, quest_length=3, grammar_flags=grammar_flags)
game = textworld.generator.make_game(world_size=num_nodes, nb_objects=num_items, quest_length=3, quest_breadth=1, grammar_flags=grammar_flags)

game_name = "test_html_viewer_wrapper"
with make_temp_directory(prefix=game_name) as tmpdir:
Expand Down
29 changes: 26 additions & 3 deletions textworld/generator/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ def make_game_with(world, quests=None, grammar=None):
return game


def make_game(world_size: int, nb_objects: int, quest_length: int,
def make_game(world_size: int, nb_objects: int, quest_length: int, quest_breadth: int,
grammar_flags: Mapping = {},
rngs: Optional[Dict[str, RandomState]] = None
) -> Game:
Expand All @@ -158,6 +158,7 @@ def make_game(world_size: int, nb_objects: int, quest_length: int,
world_size: Number of rooms in the world.
nb_objects: Number of objects in the world.
quest_length: Minimum nb. of actions the quest requires to be completed.
quest_breadth: How many branches the quest can have.
grammar_flags: Options for the grammar.

Returns:
Expand All @@ -175,14 +176,34 @@ def make_game(world_size: int, nb_objects: int, quest_length: int,
world = make_world(world_size, nb_objects=0, rngs=rngs)

# Sample a quest according to quest_length.
options = ChainingOptions()
class Options(ChainingOptions):

def get_rules(self, depth):
if depth == 0:
# Last action should not be "go <dir>".
return data.get_rules().get_matching("^(?!go.*).*")
else:
return super().get_rules(depth)

options = Options()
options.backward = True
options.min_depth = 1
options.max_depth = quest_length
options.min_breadth = 1
options.max_breadth = quest_breadth
options.create_variables = True
options.rng = rngs['rng_quest']
options.restricted_types = {"r", "d"}
chain = sample_quest(world.state, options)

subquests = []
for i in range(1, len(chain.nodes)):
if chain.nodes[i].breadth != chain.nodes[i - 1].breadth:
quest = Quest(chain.actions[:i])
subquests.append(quest)

quest = Quest(chain.actions)
subquests.append(quest)

# Set the initial state required for the quest.
world.state = chain.initial_state
Expand All @@ -191,7 +212,9 @@ def make_game(world_size: int, nb_objects: int, quest_length: int,
world.populate(nb_objects, rng=rngs['rng_objects'])

grammar = make_grammar(grammar_flags, rng=rngs['rng_grammar'])
game = make_game_with(world, [quest], grammar)
game = make_game_with(world, subquests, grammar)
game.change_grammar(grammar)

return game


Expand Down
8 changes: 7 additions & 1 deletion textworld/generator/data/logic/door.twl
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,13 @@ type d : t {
link3 :: link(r, d, r') & link(r, d', r') -> fail();

# There cannot be more than four doors in a room.
dr2 :: link(r, d1: d, r1: r) & link(r, d2: d, r2: r) & link(r, d3: d, r3: r) & link(r, d4: d, r4: r) & link(r, d5: d, r5: r) -> fail();
too_many_doors :: link(r, d1: d, r1: r) & link(r, d2: d, r2: r) & link(r, d3: d, r3: r) & link(r, d4: d, r4: r) & link(r, d5: d, r5: r) -> fail();

# There cannot be more than four doors in a room.
dr1 :: free(r, r1: r) & link(r, d2: d, r2: r) & link(r, d3: d, r3: r) & link(r, d4: d, r4: r) & link(r, d5: d, r5: r) -> fail();
dr2 :: free(r, r1: r) & free(r, r2: r) & link(r, d3: d, r3: r) & link(r, d4: d, r4: r) & link(r, d5: d, r5: r) -> fail();
dr3 :: free(r, r1: r) & free(r, r2: r) & free(r, r3: r) & link(r, d4: d, r4: r) & link(r, d5: d, r5: r) -> fail();
dr4 :: free(r, r1: r) & free(r, r2: r) & free(r, r3: r) & free(r, r4: r) & link(r, d5: d, r5: r) -> fail();

free1 :: link(r, d, r') & free(r, r') & closed(d) -> fail();
free2 :: link(r, d, r') & free(r, r') & locked(d) -> fail();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ action_seperator_go/north: #afterhave# gone north, ;#emptyinstruction1#;#emptyin
action_seperator_go/east: #afterhave# gone east, ;#emptyinstruction1#;#emptyinstruction2#;#emptyinstruction3#;#emptyinstruction4#;#emptyinstruction5#;#emptyinstruction6#;#emptyinstruction7#;#emptyinstruction8#;#emptyinstruction9#;#emptyinstruction10#
action_seperator_go/west: #afterhave# gone west, ;#emptyinstruction1#;#emptyinstruction2#;#emptyinstruction3#;#emptyinstruction4#;#emptyinstruction5#;#emptyinstruction6#;#emptyinstruction7#;#emptyinstruction8#;#emptyinstruction9#;#emptyinstruction10#
action_separator_close: #afterhave# #closed# the #close_open_types#, ; #after# #closing# the #close_open_types#, ;#emptyinstruction1#;#emptyinstruction2#;#emptyinstruction3#;#emptyinstruction4#;#emptyinstruction5#;#emptyinstruction6#;#emptyinstruction7#;#emptyinstruction8#;#emptyinstruction9#;#emptyinstruction10#
action_separator_drop: #afterhave# #dropped# #obj_types#, ; #after# #dropping# #obj_types#, ;#emptyinstruction1#;#emptyinstruction2#;#emptyinstruction3#;#emptyinstruction4#;#emptyinstruction5#;#emptyinstruction6#;#emptyinstruction7#;#emptyinstruction8#;#emptyinstruction9#;#emptyinstruction10#
action_separator_drop: #afterhave# #dropped# the #obj_types#, ; #after# #dropping# the #obj_types#, ;#emptyinstruction1#;#emptyinstruction2#;#emptyinstruction3#;#emptyinstruction4#;#emptyinstruction5#;#emptyinstruction6#;#emptyinstruction7#;#emptyinstruction8#;#emptyinstruction9#;#emptyinstruction10#
#Separator Symbols
afterhave:After you have;Having;Once you have;If you have
havetaken:taken;got;picked up
Expand Down
Loading