Skip to content
Merged
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
37 changes: 10 additions & 27 deletions scripts_dev/benchmark_framework.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,35 +10,14 @@
from textworld.generator import World


def generate_never_ending_game_old(args):
g_rng.set_seed(args.seed)
msg = "--max-steps {} --nb-objects {} --nb-rooms {} --seed {}"
print(msg.format(args.max_steps, args.nb_objects, args.nb_rooms, g_rng.seed))
print("Generating game...")

map_ = textworld.generator.make_map(n_rooms=args.nb_rooms)
world = World.from_map(map_)
world.set_player_room()
world.populate(nb_objects=args.nb_objects)
grammar = textworld.generator.make_grammar(flags={"theme": "house"})

quests = [] # No quest
game = textworld.generator.make_game_with(world, quests, grammar)

game_name = "neverending"
game_file = textworld.generator.compile_game(game, game_name, force_recompile=True,
games_folder=args.output)
return game_file


def generate_never_ending_game(args):
g_rng.set_seed(args.seed)
msg = "--max-steps {} --nb-objects {} --nb-rooms {} --quest-length {} --seed {}"
print(msg.format(args.max_steps, args.nb_objects, args.nb_rooms, args.quest_length, g_rng.seed))
msg = "--max-steps {} --nb-objects {} --nb-rooms {} --quest-length {} --quest-breadth {} --seed {}"
print(msg.format(args.max_steps, args.nb_objects, args.nb_rooms, args.quest_length, args.quest_breadth, g_rng.seed))
print("Generating game...")

grammar_flags = {}
game = textworld.generator.make_game(args.nb_rooms, args.nb_objects, args.quest_length, grammar_flags)
game = textworld.generator.make_game(args.nb_rooms, args.nb_objects, args.quest_length, args.quest_breadth, grammar_flags)
if args.no_quest:
game.quests = []

Expand All @@ -52,9 +31,11 @@ def benchmark(game_file, args):
print("Using {}".format(env.__class__.__name__))

if args.mode == "random":
agent = textworld.agents.RandomTextAgent()
agent = textworld.agents.NaiveAgent()
elif args.mode == "random-cmd":
agent = textworld.agents.RandomCommandAgent()
elif args.mode == "walkthrough":
agent = textworld.agents.WalkthroughAgent()

agent.reset(env)

Expand Down Expand Up @@ -96,13 +77,15 @@ def parse_args():
help="Nb. of rooms in the world. Default: %(default)s")
parser.add_argument("--nb-objects", type=int, default=50,
help="Nb. of objects in the world. Default: %(default)s")
parser.add_argument("--quest-length", type=int, default=10,
parser.add_argument("--quest-length", type=int, default=5,
help="Minimum nb. of actions the quest requires to be completed. Default: %(default)s")
parser.add_argument("--quest-breadth", type=int, default=3,
help="Control how non-linear a quest can be. Default: %(default)s")
parser.add_argument("--max-steps", type=int, default=1000,
help="Stop the game after that many steps. Default: %(default)s")
parser.add_argument("--output", default="./gen_games/",
help="Output folder to save generated game files.")
parser.add_argument("--mode", default="random-cmd", choices=["random", "random-cmd"])
parser.add_argument("--mode", default="random-cmd", choices=["random", "random-cmd", "walkthrough"])
parser.add_argument("--no-quest", action="store_true")
parser.add_argument("--compute_intermediate_reward", action="store_true")
parser.add_argument("--activate_state_tracking", action="store_true")
Expand Down
8 changes: 4 additions & 4 deletions tests/test_make_game.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ 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]
assert len(set(game1_objects_names) & set(game2_objects_names)) == 0
Expand All @@ -24,8 +24,8 @@ def test_making_game_with_names_to_exclude():
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
5 changes: 3 additions & 2 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 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.main_quest.commands
agent.reset(env)
game_state = env.reset()

Expand Down
8 changes: 4 additions & 4 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():
with make_temp_directory(prefix="test_tw-play") as tmpdir:
game_file, _ = textworld.make(5, 10, 5, {}, seed=1234, games_dir=tmpdir)
def test_playing_a_game():
with make_temp_directory(prefix="test_tw-play") as 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 All @@ -18,4 +18,4 @@ def test_making_a_custom_game():
assert check_call(command) == 0

command = ["tw-play", "--max-steps", "100", "--mode", "walkthrough", game_file]
assert check_call(command) == 0
assert check_call(command) == 0
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
75 changes: 50 additions & 25 deletions textworld/envs/glulx/git_glulx_ml.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def _detect_i7_events_debug_tags(text: str) -> Tuple[List[str], str]:
"""
matches = []
open_tags = []
for match in re.findall("\[[^]]+\]\n?", text):
for match in re.findall(r"\[[^]]+\]\n?", text):
text = text.replace(match, "") # Remove i7 debug tags.
tag_name = match.strip()[1:-1] # Strip starting '[' and trailing ']'.

Expand Down Expand Up @@ -127,12 +127,12 @@ def __init__(self, *args, **kwargs):
:param kwargs: The kwargs
"""
super().__init__(*args, **kwargs)
self._has_won = False
self._has_lost = False
self.has_timeout = False
self._state_tracking = False
self._compute_intermediate_reward = False
self._max_score = 0

def init(self, output: str, game=None,
def init(self, output: str, game: Game,
state_tracking: bool = False, compute_intermediate_reward: bool = False):
"""
Initialize the game state and set tracking parameters.
Expand All @@ -149,10 +149,9 @@ def init(self, output: str, game=None,
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
self._score = 0
self._max_score = self._game_progression.max_score

def view(self) -> "GlulxGameState":
"""
Expand All @@ -177,6 +176,7 @@ def view(self) -> "GlulxGameState":
game_state._nb_moves = self.nb_moves
game_state._has_won = self.has_won
game_state._has_lost = self.has_lost
game_state.has_timeout = self.has_timeout

if self._state_tracking:
game_state._admissible_commands = self.admissible_commands
Expand All @@ -199,6 +199,7 @@ def update(self, command: str, output: str) -> "GlulxGameState":
game_state = super().update(command, output)
game_state.previous_state = self.view()
game_state._objective = self.objective
game_state._max_score = self.max_score
game_state._game_progression = self._game_progression
game_state._state_tracking = self._state_tracking
game_state._compute_intermediate_reward = self._compute_intermediate_reward
Expand All @@ -215,12 +216,6 @@ def update(self, command: str, output: str) -> "GlulxGameState":
# An action that affects the state of the game.
game_state._game_progression.update(game_state._action)

if game_state._compute_intermediate_reward:
if game_state._game_progression.winning_policy is None:
game_state._has_lost = True
elif len(game_state._game_progression.winning_policy) == 0:
game_state._has_won = True

return game_state

@property
Expand Down Expand Up @@ -317,24 +312,54 @@ def intermediate_reward(self):

@property
def score(self):
if self.has_won:
return 1
elif self.has_lost:
return -1

return 0
if not hasattr(self, "_score"):
if self._state_tracking:
self._score = self._game_progression.score
else:

# Check if there was any Inform7 events.
if self._feedback == self._raw:
self._score = self.previous_state.score
else:
output = self._raw
if not self.game_ended:
output = self._env._send("score")

match = re.search("scored (?P<score>[0-9]+) out of a possible (?P<max_score>[0-9]+),", output)
self._score = 0
if match:
self._score = int(match.groupdict()["score"])

return self._score

@property
def max_score(self):
return 1
return self._max_score

@property
def has_won(self):
return self._has_won or '*** The End ***' in self.feedback
if not hasattr(self, "_has_won"):
if self._compute_intermediate_reward:
self._has_won = self._game_progression.completed
else:
self._has_won = '*** The End ***' in self.feedback

return self._has_won

@property
def has_lost(self):
return self._has_lost or '*** You lost! ***' in self.feedback
if not hasattr(self, "_has_lost"):
if self._compute_intermediate_reward:
self._has_lost = self._game_progression.failed
else:
self._has_lost = '*** You lost! ***' in self.feedback

return self._has_lost

@property
def game_ended(self) -> bool:
""" Whether the game is finished or not. """
return self.has_won | self.has_lost | self.has_timeout

@property
def game_infos(self) -> Mapping:
Expand Down Expand Up @@ -439,8 +464,8 @@ def step(self, command: str) -> Tuple[GlulxGameState, float, bool]:
raise GameNotRunningError()

self.game_state = self.game_state.update(command, output)
done = self.game_state.game_ended or not self.game_running
return self.game_state, self.game_state.score, done
self.game_state.has_timeout = not self.game_running
return self.game_state, self.game_state.score, self.game_state.game_ended

def _send(self, command: str) -> Union[str, None]:
if not self.game_running:
Expand Down
Loading