예제 #1
0
    def test_game_with_infinite_max_score(self):
        M = textworld.GameMaker()
        museum = M.new_room("Museum")

        statue = M.new(type="o", name="golden statue")
        pedestal = M.new(type="s", name="pedestal")

        pedestal.add(statue)
        museum.add(pedestal)

        M.set_player(museum)

        M.quests = [
            Quest(win_events=[
                Event(conditions=[M.new_fact('in', statue, M.inventory)]),
            ],
                  reward=10,
                  optional=True,
                  repeatable=True),
            Quest(win_events=[
                Event(conditions=[M.new_fact('at', statue, museum)]),
            ],
                  reward=0)
        ]
        M.set_walkthrough(
            ["take golden statue from pedestal", "look", "drop statue"])

        game = M.build()
        assert game.max_score == np.inf

        inform7 = Inform7Game(game)
        game_progress = GameProgression(game)
        assert len(game_progress.quest_progressions) == len(game.quests)

        # Following the actions associated to the last quest actually corresponds
        # to solving the whole game.
        for action in game_progress.winning_policy:
            assert not game_progress.done
            game_progress.update(action)

        assert game_progress.done
        assert not game_progress.quest_progressions[
            0].completed  # Repeatable quests can never be completed.
        assert game_progress.quest_progressions[
            1].completed  # Mandatory quest.

        # Solve the game while completing the optional quests.
        game_progress = GameProgression(game)
        for command in [
                "take golden statue from pedestal", "look", "look",
                "drop golden statue"
        ]:
            _apply_command(command, game_progress, inform7)

        progressions = game_progress.quest_progressions
        assert not progressions[0].done  # Repeatable quests can never be done.
        assert progressions[
            0].nb_completions == 3  # They could have been completed a number of times, though.
        assert progressions[1].done
        assert game_progress.score == 30
예제 #2
0
def test_names_disambiguation():
    M = textworld.GameMaker()
    room = M.new_room("room")
    M.set_player(room)

    apple = M.new(type="o", name="apple")
    orange = M.new(type="o", name="orange")
    tasty_apple = M.new(type="o", name="tasty apple")
    tasty_orange = M.new(type="o", name="tasty orange")
    room.add(apple, orange, tasty_apple, tasty_orange)

    game = M.build()
    game_name = "test_names_disambiguation"
    with make_temp_directory(prefix=game_name) as tmpdir:
        game_file = compile_game(game, game_name, games_folder=tmpdir)
        env = textworld.start(game_file)
        env.reset()
        game_state, _, done = env.step("take tasty apple")
        assert "tasty apple" in game_state.inventory
        game_state, _, done = env.step("take tasty orange")
        assert "tasty orange" in game_state.inventory

        env.reset()
        game_state, _, done = env.step("take orange")
        assert "tasty orange" not in game_state.inventory
        assert "orange" in game_state.inventory

        game_state, _, done = env.step("take tasty")
        assert "?" in game_state.feedback  # Disambiguation question.
        game_state, _, done = env.step("apple")
        assert "tasty orange" not in game_state.inventory
        assert "tasty apple" in game_state.inventory
        assert "tasty apple" not in game_state.description
예제 #3
0
def test_disambiguation_questions():
    M = textworld.GameMaker()
    room = M.new_room("room")
    M.set_player(room)

    tasty_apple = M.new(type="o", name="tasty apple")
    tasty_orange = M.new(type="o", name="tasty orange")
    room.add(tasty_apple, tasty_orange)

    game = M.build()
    game_name = "test_names_disambiguation"
    with make_temp_directory(prefix=game_name) as tmpdir:
        game_file = _compile_game(game, path=tmpdir)
        env = textworld.start(game_file, EnvInfos(description=True, inventory=True))

        game_state = env.reset()
        previous_inventory = game_state.inventory
        previous_description = game_state.description

        game_state, _, _ = env.step("take tasty")
        assert "?" in game_state.feedback  # Disambiguation question.

        # When there is a question in Inform7, the next string sent to the game
        # will be considered as the answer. We now make sure that asking for
        # extra information like `description` or `inventory` before answering
        # the question works.
        assert game_state.description == previous_description
        assert game_state.inventory == previous_inventory

        # Now answering the question.
        game_state, _, _ = env.step("apple")
        assert "That's not a verb I recognise." not in game_state.feedback
        assert "tasty orange" not in game_state.inventory
        assert "tasty apple" in game_state.inventory
        assert "tasty apple" not in game_state.description
예제 #4
0
def test_blend_instructions(verbose=False):
    # Make generation throughout the framework reproducible.
    g_rng.set_seed(1234)

    M = textworld.GameMaker()
    r1 = M.new_room()
    r2 = M.new_room()
    M.set_player(r1)

    path = M.connect(r1.north, r2.south)
    path.door = M.new(type="d", name="door")
    M.add_fact("locked", path.door)
    key = M.new(type="k", name="key")
    M.add_fact("match", key, path.door)
    r1.add(key)

    quest = M.set_quest_from_commands(["take key", "unlock door with key", "open door", "go north",
                                       "close door", "lock door with key", "drop key"])

    game = M.build()

    grammar1 = textworld.generator.make_grammar(flags={"blend_instructions": False},
                                                rng=np.random.RandomState(42))

    grammar2 = textworld.generator.make_grammar(flags={"blend_instructions": True},
                                                rng=np.random.RandomState(42))

    quest.desc = None
    game.change_grammar(grammar1)
    quest1 = quest.copy()
    quest.desc = None
    game.change_grammar(grammar2)
    quest2 = quest.copy()
    assert len(quest1.desc) > len(quest2.desc)
예제 #5
0
def test_quest_winning_condition_go():
    M = textworld.GameMaker()

    # R1 -- R2 -- R3
    R1 = M.new_room("West room")
    R2 = M.new_room("Center room")
    R3 = M.new_room("East room")
    M.set_player(R1)

    M.connect(R1.east, R2.west)
    M.connect(R2.east, R3.west)

    M.set_quest_from_commands(["go east", "go east"])

    game = M.build()
    game_name = "test_quest_winning_condition_go"
    with make_temp_directory(prefix=game_name) as tmpdir:
        game_file = compile_game(game, game_name, games_folder=tmpdir)

        env = textworld.start(game_file)
        env.reset()
        game_state, _, done = env.step("go east")
        assert not done
        assert not game_state.has_won

        game_state, _, done = env.step("go east")
        assert done
        assert game_state.has_won
예제 #6
0
def test_game_with_infinite_max_score():
    M = textworld.GameMaker()
    museum = M.new_room("Museum")

    statue = M.new(type="o", name="golden statue")
    pedestal = M.new(type="s", name="pedestal")

    pedestal.add(statue)
    museum.add(pedestal)

    M.set_player(museum)

    M.quests = [
        Quest(win_events=[
            Event(conditions=[M.new_fact('in', statue, M.inventory)]),
        ],
              reward=10,
              optional=True,
              repeatable=True),
        Quest(win_events=[
            Event(conditions=[M.new_fact('at', statue, museum)]),
        ],
              reward=0)
    ]
    M.set_walkthrough(["take statue from pedestal", "look", "drop statue"])

    game = M.build()
    game_name = "test_game_with_infinite_max_score"
    with make_temp_directory(prefix=game_name) as tmpdir:
        game_file = _compile_game(game, path=tmpdir)
        env = textworld.start(game_file)
        state = env.reset()
        state.max_score == np.inf  # Score increases for each turn the player hold the statue.

        state, score, done = env.step("look")
        assert not done
        assert score == 0

        state, score, done = env.step("take statue")
        assert score == 10

        state, score, done = env.step("wait")
        assert score == 20

        state, score, done = env.step("score")
        assert score == 20
        assert "You have so far scored 20 points," in state.feedback

        state, score, done = env.step("wait")
        assert score == 30

        state, score, done = env.step("drop statue")
        assert done
        assert score == 30

        assert "a total of 30 points," in state.feedback
예제 #7
0
def build_and_compile_no_quest_game(options: GameOptions) -> Tuple[Game, str]:
    M = textworld.GameMaker()

    room = M.new_room()
    M.set_player(room)
    item = M.new(type="o")
    room.add(item)
    game = M.build()

    game_file = _compile_test_game(game, options)
    return game, game_file
예제 #8
0
def test_do_not_overwrite_entity_desc(verbose=False):
    # Make generation throughout the framework reproducible.
    g_rng.set_seed(1234)

    M = textworld.GameMaker()
    r1 = M.new_room()
    M.set_player(r1)

    key = M.new(type="k", name="key", desc="This is a skeleton key.")
    r1.add(key)

    quest = M.set_quest_from_commands(["take key"])
    quest.desc = "Find a valuable object."

    M.build()
    assert key.infos.desc == "This is a skeleton key."
    assert quest.desc == "Find a valuable object."
예제 #9
0
def test_quest_losing_condition():
    g_rng.set_seed(2018)
    M = textworld.GameMaker()

    # The goal
    commands = ["go east", "insert carrot into chest"]

    # Create a 'bedroom' room.
    R1 = M.new_room("bedroom")
    R2 = M.new_room("kitchen")
    M.set_player(R1)

    path = M.connect(R1.east, R2.west)
    path.door = M.new(type='d', name='wooden door')
    path.door.add_property("open")

    carrot = M.new(type='f', name='carrot')
    M.inventory.add(carrot)

    # Add a closed chest in R2.
    chest = M.new(type='c', name='chest')
    chest.add_property("open")
    R2.add(chest)

    failing_conditions = (Proposition("eaten", [carrot.var]), )
    quest = M.set_quest_from_commands(commands)
    quest.set_failing_conditions(failing_conditions)
    game = M.build()

    game_name = "test_quest_losing_condition"
    with make_temp_directory(prefix=game_name) as tmpdir:
        game_file = compile_game(game, game_name, games_folder=tmpdir)

        env = textworld.start(game_file)
        env.reset()
        # Make sure we do not rely on the quest progression to
        # determine if the game was lost.
        assert not env._compute_intermediate_reward

        game_state, _, done = env.step("eat carrot")
        assert done
        assert game_state.has_lost
        assert not game_state.has_won
예제 #10
0
def build_and_compile_game(options: GameOptions):
    M = textworld.GameMaker()

    # Create a 'bedroom' room.
    R1 = M.new_room("bedroom")
    R2 = M.new_room("kitchen")
    M.set_player(R1)

    path = M.connect(R1.east, R2.west)
    path.door = M.new(type='d', name='wooden door')
    path.door.add_property("open")

    carrot = M.new(type='f', name='carrot')
    M.inventory.add(carrot)

    # Add a closed chest in R2.
    chest = M.new(type='c', name='chest')
    chest.add_property("open")
    R2.add(chest)

    quest1_cmds = ["go east", "insert carrot into chest"]
    carrot_in_chest = M.new_event_using_commands(quest1_cmds)
    eating_carrot = Event(conditions={M.new_fact("eaten", carrot)})
    quest1 = Quest(win_events=[carrot_in_chest],
                   fail_events=[eating_carrot],
                   reward=2)

    quest2_cmds = quest1_cmds + ["close chest"]
    quest2_actions = M.new_event_using_commands(quest2_cmds).actions
    chest_closed_with_carrot = Event(conditions={
        M.new_fact("in", carrot, chest),
        M.new_fact("closed", chest)
    },
                                     actions=quest2_actions)

    quest2 = Quest(win_events=[chest_closed_with_carrot],
                   fail_events=[eating_carrot])

    M.quests = [quest1, quest2]
    M.set_walkthrough(quest2_cmds)
    game = M.build()
    game_file = _compile_test_game(game, options)
    return game, game_file
예제 #11
0
def test_take_all_and_variants():
    M = textworld.GameMaker()

    # Empty room.
    room = M.new_room("room")
    M.set_player(room)

    game = M.build()
    game_name = "test_take_all_and_variants"
    with make_temp_directory(prefix=game_name) as tmpdir:
        game_file = compile_game(game, game_name, games_folder=tmpdir)
        env = textworld.start(game_file)
        env.reset()

        variants_to_test = itertools.product(["take", "get", "pick up"],
                                             ["all", "everything", "each"])
        for command in variants_to_test:
            game_state, _, done = env.step(" ".join(command))
            assert game_state.feedback.strip(
            ) == "You have to be more specific!"

    # Multiple objects to take.
    red_ball = M.new(type="o", name="red ball")
    blue_ball = M.new(type="o", name="blue ball")
    room.add(red_ball, blue_ball)

    game = M.build()
    game_name = "test_take_all_and_variants2"
    with make_temp_directory(prefix=game_name) as tmpdir:
        game_file = compile_game(game, game_name, games_folder=tmpdir)
        env = textworld.start(game_file)
        env.reset()

        game_state, _, done = env.step("take all ball")
        assert "red ball: Taken." in game_state.feedback
        assert "blue ball: Taken." in game_state.feedback
        assert "red ball" in game_state.inventory
        assert "blue ball" in game_state.inventory
예제 #12
0
def test_quest_with_multiple_winning_and_losing_conditions():
    g_rng.set_seed(2018)
    M = textworld.GameMaker()

    # Create a 'bedroom' room.
    R1 = M.new_room("bedroom")
    R2 = M.new_room("kitchen")
    M.set_player(R1)

    path = M.connect(R1.east, R2.west)
    path.door = M.new(type='d', name='wooden door')
    path.door.add_property("open")

    carrot = M.new(type='f', name='carrot')
    lettuce = M.new(type='f', name='lettuce')
    M.inventory.add(carrot)
    M.inventory.add(lettuce)

    # Add a closed chest in R2.
    chest = M.new(type='c', name='chest')
    chest.add_property("open")
    R2.add(chest)

    # The goal
    quest = Quest(win_events=[Event(conditions={M.new_fact("in", carrot, chest),
                                                M.new_fact("closed", chest)}),
                              Event(conditions={M.new_fact("eaten", lettuce)})],
                  fail_events=[Event(conditions={M.new_fact("in", lettuce, chest),
                                                 M.new_fact("closed", chest)}),
                               Event(conditions={M.new_fact("eaten", carrot)})])
    M.quests = [quest]
    game = M.build()

    game_name = "test_quest_with_multiple_winning_and_losing_conditions"
    with make_temp_directory(prefix=game_name) as tmpdir:
        game_file = _compile_game(game, path=tmpdir)

        env = textworld.start(game_file)

        # Failing - 1
        env.reset()
        game_state, _, done = env.step("eat carrot")
        assert done
        assert game_state.lost
        assert not game_state.won

        # Failing - 2
        env.reset()
        game_state, _, done = env.step("go east")
        assert not done
        game_state, _, done = env.step("insert lettuce into chest")
        assert not done
        game_state, _, done = env.step("close chest")
        assert done
        assert game_state.lost
        assert not game_state.won

        # Failing - 1
        env.reset()
        game_state, _, done = env.step("eat lettuce")
        assert done
        assert not game_state.lost
        assert game_state.won

        # Winning - 2
        env.reset()
        game_state, _, done = env.step("go east")
        assert not done
        game_state, _, done = env.step("insert carrot into chest")
        assert not done
        game_state, _, done = env.step("close chest")
        assert done
        assert not game_state.lost
        assert game_state.won
예제 #13
0
def make_game(settings: Mapping[str, str],
              options: Optional[GameOptions] = None) -> textworld.Game:
    """ Make a simple game.

    Arguments:
        settings: Difficulty settings (see notes).
        options:
            For customizing the game generation (see
            :py:class:`textworld.GameOptions <textworld.generator.game.GameOptions>`
            for the list of available options).

    Returns:
        Generated game.

    Notes:
        The settings that can be provided are:

        * rewards : The reward frequency: dense, balanced, or sparse.
        * goal : The description of the game's objective shown at the
          beginning of the game: detailed, bried, or none.
        * test : Whether this game should be drawn from the test
          distributions of games.
    """
    metadata = {}  # Collect infos for reproducibility.
    metadata["desc"] = "Simple game"
    metadata["seeds"] = options.seeds
    metadata["world_size"] = 6
    metadata["quest_length"] = None  # TBD

    rngs = options.rngs
    rng_map = rngs['map']
    rng_objects = rngs['objects']
    rng_grammar = rngs['grammar']
    rng_quest = rngs['quest']

    # Make the generation process reproducible.
    textworld.g_rng.set_seed(2018)

    M = textworld.GameMaker()
    M.grammar = textworld.generator.make_grammar(options.grammar,
                                                 rng=rng_grammar)

    # Start by building the layout of the world.
    bedroom = M.new_room("bedroom")
    kitchen = M.new_room("kitchen")
    livingroom = M.new_room("living room")
    bathroom = M.new_room("bathroom")
    backyard = M.new_room("backyard")
    garden = M.new_room("garden")

    # Connect rooms together.
    bedroom_kitchen = M.connect(bedroom.east, kitchen.west)
    kitchen_bathroom = M.connect(kitchen.north, bathroom.south)
    kitchen_livingroom = M.connect(kitchen.south, livingroom.north)
    kitchen_backyard = M.connect(kitchen.east, backyard.west)
    backyard_garden = M.connect(backyard.south, garden.north)

    # Add doors.
    bedroom_kitchen.door = M.new(type='d', name='wooden door')
    kitchen_backyard.door = M.new(type='d', name='screen door')

    kitchen_backyard.door.add_property("closed")

    # Design the bedroom.
    drawer = M.new(type='c', name='chest drawer')
    trunk = M.new(type='c', name='antique trunk')
    bed = M.new(type='s', name='king-size bed')
    bedroom.add(drawer, trunk, bed)

    # Close the trunk and drawer.
    trunk.add_property("closed")
    drawer.add_property("closed")

    # - The bedroom's door is locked
    bedroom_kitchen.door.add_property("locked")

    # Design the kitchen.
    counter = M.new(type='s', name='counter')
    stove = M.new(type='s', name='stove')
    kitchen_island = M.new(type='s', name='kitchen island')
    refrigerator = M.new(type='c', name='refrigerator')
    kitchen.add(counter, stove, kitchen_island, refrigerator)

    # - Add some food in the refrigerator.
    apple = M.new(type='f', name='apple')
    milk = M.new(type='f', name='milk')
    refrigerator.add(apple, milk)

    # Design the bathroom.
    toilet = M.new(type='c', name='toilet')
    sink = M.new(type='s', name='sink')
    bath = M.new(type='c', name='bath')
    bathroom.add(toilet, sink, bath)

    toothbrush = M.new(type='o', name='toothbrush')
    sink.add(toothbrush)
    soap_bar = M.new(type='o', name='soap bar')
    bath.add(soap_bar)

    # Design the living room.
    couch = M.new(type='s', name='couch')
    low_table = M.new(type='s', name='low table')
    tv = M.new(type='s', name='tv')
    livingroom.add(couch, low_table, tv)

    remote = M.new(type='o', name='remote')
    low_table.add(remote)
    bag_of_chips = M.new(type='f', name='half of a bag of chips')
    couch.add(bag_of_chips)

    # Design backyard.
    bbq = M.new(type='s', name='bbq')
    patio_table = M.new(type='s', name='patio table')
    chairs = M.new(type='s', name='set of chairs')
    backyard.add(bbq, patio_table, chairs)

    # Design garden.
    shovel = M.new(type='o', name='shovel')
    tomato = M.new(type='f', name='tomato plant')
    pepper = M.new(type='f', name='bell pepper')
    lettuce = M.new(type='f', name='lettuce')
    garden.add(shovel, tomato, pepper, lettuce)

    # Close all containers
    for container in M.findall(type='c'):
        container.add_property("closed")

    # Set uncooked property for to all food items.
    foods = M.findall(type='f')
    for food in foods:
        food.add_property("edible")

    food_names = [food.name for food in foods]

    # Shuffle the position of the food items.
    rng_quest.shuffle(food_names)

    for food, name in zip(foods, food_names):
        food.orig_name = food.name
        food.name = name

    # The player starts in the bedroom.
    M.set_player(bedroom)

    # Quest
    walkthrough = []

    # Part I - Escaping the room.
    # Generate the key that unlocks the bedroom door.
    bedroom_key = M.new(type='k', name='old key')
    M.add_fact("match", bedroom_key, bedroom_kitchen.door)

    # Decide where to hide the key.
    if rng_quest.rand() > 0.5:
        drawer.add(bedroom_key)
        walkthrough.append("open chest drawer")
        walkthrough.append("take old key from chest drawer")
        bedroom_key_holder = drawer
    else:
        trunk.add(bedroom_key)
        walkthrough.append("open antique trunk")
        walkthrough.append("take old key from antique trunk")
        bedroom_key_holder = trunk

    # Unlock the door, open it and leave the room.
    walkthrough.append("unlock wooden door with old key")
    walkthrough.append("open wooden door")
    walkthrough.append("go east")

    # Part II - Find food item.
    # 1. Randomly pick a food item to cook.
    food = rng_quest.choice(foods)

    if settings["test"]:
        TEST_FOODS = ["garlic", "kiwi", "carrot"]
        food.name = rng_quest.choice(TEST_FOODS)

    # Retrieve the food item and get back in the kitchen.
    # HACK: handcrafting the walkthrough.
    if food.orig_name in ["apple", "milk"]:
        rooms_to_visit = []
        doors_to_open = []
        walkthrough.append("open refrigerator")
        walkthrough.append("take {} from refrigerator".format(food.name))
    elif food.orig_name == "half of a bag of chips":
        rooms_to_visit = [livingroom]
        doors_to_open = []
        walkthrough.append("go south")
        walkthrough.append("take {} from couch".format(food.name))
        walkthrough.append("go north")
    elif food.orig_name in ["bell pepper", "lettuce", "tomato plant"]:
        rooms_to_visit = [backyard, garden]
        doors_to_open = [kitchen_backyard.door]
        walkthrough.append("open screen door")
        walkthrough.append("go east")
        walkthrough.append("go south")
        walkthrough.append("take {}".format(food.name))
        walkthrough.append("go north")
        walkthrough.append("go west")

    # Part II - Cooking the food item.
    walkthrough.append("put {} on stove".format(food.name))
    # walkthrough.append("cook {}".format(food.name))
    # walkthrough.append("eat {}".format(food.name))

    # 2. Determine the winning condition(s) of the subgoals.
    quests = []
    bedroom_key_holder

    if settings["rewards"] == "dense":
        # Finding the bedroom key and opening the bedroom door.
        # 1. Opening the container.
        quests.append(
            Quest(win_events=[
                Event(conditions={M.new_fact("open", bedroom_key_holder)})
            ]))

        # 2. Getting the key.
        quests.append(
            Quest(win_events=[
                Event(conditions={M.new_fact("in", bedroom_key, M.inventory)})
            ]))

        # 3. Unlocking the bedroom door.
        quests.append(
            Quest(win_events=[
                Event(conditions={M.new_fact("closed", bedroom_kitchen.door)})
            ]))

        # 4. Opening the bedroom door.
        quests.append(
            Quest(win_events=[
                Event(conditions={M.new_fact("open", bedroom_kitchen.door)})
            ]))

    if settings["rewards"] in ["dense", "balanced"]:
        # Escaping out of the bedroom.
        quests.append(
            Quest(win_events=[
                Event(conditions={M.new_fact("at", M.player, kitchen)})
            ]))

    if settings["rewards"] in ["dense", "balanced"]:
        # Opening doors.
        for door in doors_to_open:
            quests.append(
                Quest(
                    win_events=[Event(conditions={M.new_fact("open", door)})]))

    if settings["rewards"] == "dense":
        # Moving through places.
        for room in rooms_to_visit:
            quests.append(
                Quest(win_events=[
                    Event(conditions={M.new_fact("at", M.player, room)})
                ]))

    if settings["rewards"] in ["dense", "balanced"]:
        # Retrieving the food item.
        quests.append(
            Quest(win_events=[
                Event(conditions={M.new_fact("in", food, M.inventory)})
            ]))

    if settings["rewards"] in ["dense", "balanced"]:
        # Retrieving the food item.
        quests.append(
            Quest(win_events=[
                Event(conditions={M.new_fact("in", food, M.inventory)})
            ]))

    if settings["rewards"] in ["dense", "balanced", "sparse"]:
        # Putting the food on the stove.
        quests.append(
            Quest(
                win_events=[Event(
                    conditions={M.new_fact("on", food, stove)})]))

    # 3. Determine the losing condition(s) of the game.
    quests.append(
        Quest(fail_events=[Event(conditions={M.new_fact("eaten", food)})]))

    # Set the subquest(s).
    M.quests = quests

    # - Add a hint of what needs to be done in this game.
    objective = "The dinner is almost ready! It's only missing a grilled {}."
    objective = objective.format(food.name)
    note = M.new(type='o', name='note', desc=objective)
    kitchen_island.add(note)

    game = M.build()
    game.main_quest = M.new_quest_using_commands(walkthrough)
    game.change_grammar(game.grammar)

    if settings["goal"] == "detailed":
        # Use the detailed version of the objective.
        pass
    elif settings["goal"] == "brief":
        # Use a very high-level description of the objective.
        game.objective = objective
    elif settings["goal"] == "none":
        # No description of the objective.
        game.objective = ""

    game.metadata = metadata
    uuid = "tw-simple-r{rewards}+g{goal}+{dataset}-{flags}-{seeds}"
    uuid = uuid.format(rewards=str.title(settings["rewards"]),
                       goal=str.title(settings["goal"]),
                       dataset="test" if settings["test"] else "train",
                       flags=options.grammar.uuid,
                       seeds=encode_seeds(
                           [options.seeds[k] for k in sorted(options.seeds)]))
    game.metadata["uuid"] = uuid
    return game
예제 #14
0
def test_cannot_win_or_lose_a_quest_twice():
    g_rng.set_seed(2018)
    M = textworld.GameMaker()

    # Create a 'bedroom' room.
    R1 = M.new_room("bedroom")
    R2 = M.new_room("kitchen")
    M.set_player(R1)

    path = M.connect(R1.east, R2.west)
    path.door = M.new(type='d', name='wooden door')
    path.door.add_property("open")

    carrot = M.new(type='f', name='carrot')
    lettuce = M.new(type='f', name='lettuce')
    M.inventory.add(carrot)
    M.inventory.add(lettuce)

    # Add a closed chest in R2.
    chest = M.new(type='c', name='chest')
    chest.add_property("open")
    R2.add(chest)

    # The goals
    event_carrot_in_closed_chest = Event(conditions={M.new_fact("in", carrot, chest),
                                                     M.new_fact("closed", chest)})
    event_drop_carrot_R1 = Event(conditions={M.new_fact("at", carrot, R1)})
    event_drop_carrot_R2 = Event(conditions={M.new_fact("at", carrot, R2)})

    quest1 = Quest(win_events=[event_carrot_in_closed_chest],
                   fail_events=[event_drop_carrot_R1, event_drop_carrot_R2])

    event_lettuce_in_closed_chest = Event(conditions={M.new_fact("in", lettuce, chest),
                                                      M.new_fact("closed", chest)})
    event_drop_lettuce_R1 = Event(conditions={M.new_fact("at", lettuce, R1)})
    event_drop_lettuce_R2 = Event(conditions={M.new_fact("at", lettuce, R2)})

    quest2 = Quest(win_events=[event_lettuce_in_closed_chest],
                   fail_events=[event_drop_lettuce_R1, event_drop_lettuce_R2])

    M.quests = [quest1, quest2]
    game = M.build()

    game_name = "test_cannot_win_or_lose_a_quest_twice"
    with make_temp_directory(prefix=game_name) as tmpdir:
        game_file = _compile_game(game, path=tmpdir)

        env = textworld.start(game_file)

        # Complete quest1 then fail it.
        env.reset()
        game_state, score, done = env.step("go east")
        assert score == 0
        game_state, score, done = env.step("insert carrot into chest")
        assert score == 0
        game_state, score, done = env.step("close chest")
        assert score == 1
        assert not done
        game_state, score, done = env.step("open chest")

        # Re-completing quest1 doesn't award more points.
        game_state, score, done = env.step("close chest")
        assert score == 1
        assert not done

        game_state, score, done = env.step("open chest")
        game_state, score, done = env.step("take carrot from chest")
        game_state, score, done = env.step("drop carrot")
        assert score == 1
        assert not done

        # Then fail quest2.
        game_state, score, done = env.step("drop lettuce")
        assert done
        assert game_state.lost
        assert not game_state.won

        env.reset()
        game_state, score, done = env.step("go east")
        game_state, score, done = env.step("insert carrot into chest")
        game_state, score, done = env.step("insert lettuce into chest")
        game_state, score, done = env.step("close chest")
        assert score == 2
        assert done
        assert not game_state.lost
        assert game_state.won
예제 #15
0
def test_names_disambiguation():
    M = textworld.GameMaker()
    room = M.new_room("room")
    M.set_player(room)

    apple = M.new(type="o", name="apple")
    orange = M.new(type="o", name="orange")
    tasty_apple = M.new(type="o", name="tasty apple")
    tasty_orange = M.new(type="o", name="tasty orange")
    room.add(apple, orange, tasty_apple, tasty_orange)

    game = M.build()
    game_name = "test_names_disambiguation"
    with make_temp_directory(prefix=game_name) as tmpdir:
        game_file = _compile_game(game, path=tmpdir)
        env = textworld.start(game_file, EnvInfos(description=True, inventory=True))
        env.reset()
        game_state, _, done = env.step("take tasty apple")
        assert "tasty apple" in game_state.inventory
        game_state, _, done = env.step("take tasty orange")
        assert "tasty orange" in game_state.inventory

        env.reset()
        game_state, _, done = env.step("take orange")
        assert "tasty orange" not in game_state.inventory
        assert "orange" in game_state.inventory

        game_state, _, done = env.step("take tasty")
        assert "?" in game_state.feedback  # Disambiguation question.
        game_state, _, done = env.step("apple")
        assert "tasty orange" not in game_state.inventory
        assert "tasty apple" in game_state.inventory
        assert "tasty apple" not in game_state.description

    # Actions with two arguments.
    M = textworld.GameMaker()
    roomA = M.new_room("roomA")
    roomB = M.new_room("roomB")
    roomC = M.new_room("roomC")
    M.set_player(roomA)

    path = M.connect(roomA.east, roomB.west)
    gateway = M.new_door(path, name="gateway")

    path = M.connect(roomA.west, roomC.east)
    rectangular_gateway = M.new_door(path, name="rectangular gateway")

    keycard = M.new(type="k", name="keycard")
    rectangular_keycard = M.new(type="k", name="rectangular keycard")
    roomA.add(keycard, rectangular_keycard)

    M.add_fact("match", keycard, gateway)
    M.add_fact("match", rectangular_keycard, rectangular_gateway)
    M.add_fact("locked", gateway)
    M.add_fact("locked", rectangular_gateway)

    game = M.build()
    game_name = "test_names_disambiguation"
    with make_temp_directory(prefix=game_name) as tmpdir:
        game_file = _compile_game(game, path=tmpdir)
        env = textworld.start(game_file, EnvInfos(description=True, inventory=True))
        env.reset()
        game_state, _, done = env.step("take keycard")
        assert "keycard" in game_state.inventory
        game_state, _, done = env.step("take keycard")  # Already in your inventory.
        assert "rectangular keycard" not in game_state.inventory
        game_state, _, done = env.step("take rectangular keycard")
        assert "rectangular keycard" in game_state.inventory

        game_state, _, done = env.step("unlock gateway with rectangular keycard")
        assert "That doesn't seem to fit the lock." in game_state.feedback
        game_state, _, done = env.step("unlock gateway with keycard")
        game_state, _, done = env.step("open gateway")
        game_state, _, done = env.step("go east")
        assert "-= Roomb =-" in game_state.description

        game_state, _, done = env.step("go west")
        game_state, _, done = env.step("unlock rectangular gateway with keycard")
        assert "That doesn't seem to fit the lock." in game_state.feedback
        game_state, _, done = env.step("unlock rectangular gateway with rectangular keycard")
        game_state, _, done = env.step("open rectangular gateway")
        game_state, _, done = env.step("go west")
        assert "-= Roomc =-" in game_state.description

    # Test invariance of the order in which ambiguous object names are defined.
    # First define "type G safe" then a "safe".
    M = textworld.GameMaker()
    garage = M.new_room("garage")
    M.set_player(garage)

    key = M.new(type="k", name="key")
    typeG_safe = M.new(type="c", name="type G safe")
    safe = M.new(type="c", name="safe")

    safe.add(key)
    garage.add(safe, typeG_safe)

    M.add_fact("open", safe)

    game = M.build()
    game_name = "test_names_disambiguation"
    with make_temp_directory(prefix=game_name) as tmpdir:
        game_file = _compile_game(game, path=tmpdir)
        env = textworld.start(game_file, EnvInfos(inventory=True))
        game_state = env.reset()
        game_state, _, done = env.step("take key from safe")
        assert "key" in game_state.inventory

    # First define "safe" then "type G safe".
    M = textworld.GameMaker()
    garage = M.new_room("garage")
    M.set_player(garage)

    key = M.new(type="k", name="key")
    safe = M.new(type="c", name="safe")
    typeG_safe = M.new(type="c", name="type G safe")

    safe.add(key)
    garage.add(safe, typeG_safe)

    M.add_fact("open", safe)

    game = M.build()
    game_name = "test_names_disambiguation"
    with make_temp_directory(prefix=game_name) as tmpdir:
        game_file = _compile_game(game, path=tmpdir)
        env = textworld.start(game_file, EnvInfos(inventory=True))
        game_state = env.reset()
        game_state, _, done = env.step("take key from safe")
        assert "key" in game_state.inventory
예제 #16
0
    def test_game_with_optional_and_repeatable_quests(self):
        M = textworld.GameMaker()
        museum = M.new_room("Museum")

        weak_statue = M.new(type="o", name="ivory statue")
        normal_statue = M.new(type="o", name="stone statue")
        strong_statue = M.new(type="o", name="granite statue")

        museum.add(weak_statue)
        museum.add(normal_statue)
        museum.add(strong_statue)

        M.set_player(museum)

        M.quests = [
            Quest(win_events=[
                Event(conditions=[M.new_fact('in', weak_statue, M.inventory)]),
            ],
                  reward=-10,
                  optional=True,
                  repeatable=True),
            Quest(win_events=[
                Event(
                    conditions=[M.new_fact('in', normal_statue, M.inventory)]),
            ],
                  reward=3,
                  optional=True),
            Quest(
                win_events=[
                    Event(conditions=[
                        M.new_fact('in', strong_statue, M.inventory)
                    ]),
                ],
                reward=5,
            )
        ]
        M.set_walkthrough(
            ["take ivory", "take stone", "drop ivory", "take granite"])
        game = M.build()

        inform7 = Inform7Game(game)
        game_progress = GameProgression(game)
        assert len(game_progress.quest_progressions) == len(game.quests)

        # Following the actions associated to the last quest actually corresponds
        # to solving the whole game.
        for action in game_progress.winning_policy:
            assert not game_progress.done
            game_progress.update(action)

        assert game_progress.done
        assert not game_progress.quest_progressions[
            0].completed  # Optional, negative quest.
        assert not game_progress.quest_progressions[
            1].completed  # Optional, positive quest.
        assert game_progress.quest_progressions[
            2].completed  # Mandatory quest.

        # Solve the game while completing the optional quests.
        game_progress = GameProgression(game)
        for command in [
                "take ivory statue", "look", "take stone statue",
                "drop ivory statue", "take granite statue"
        ]:
            _apply_command(command, game_progress, inform7)

        progressions = game_progress.quest_progressions
        assert not progressions[0].done  # Repeatable quests can never be done.
        assert progressions[
            0].nb_completions == 3  # They could have been completed a number of times, though.
        assert progressions[
            1].done  # The nonrepeatable-optional quest can be done.
        assert progressions[2].done

        assert game.max_score == 8
        assert game_progress.score == -22
예제 #17
0
def make(settings: Mapping[str, str], options: Optional[GameOptions] = None) -> textworld.Game:
    """ Make a Cooking game.

    Arguments:
        settings: Difficulty settings (see notes).
        options:
            For customizing the game generation (see
            :py:class:`textworld.GameOptions <textworld.generator.game.GameOptions>`
            for the list of available options).

    Returns:
        Generated game.

    Notes:
        The settings that can be provided are:

        * recipe : Number of ingredients in the recipe.
        * take : Number of ingredients to fetch. It must be less
          or equal to the value of the `recipe` skill.
        * open : Whether containers/doors need to be opened.
        * cook : Whether some ingredients need to be cooked.
        * cut : Whether some ingredients need to be cut.
        * drop : Whether the player's inventory has limited capacity.
        * go : Number of locations in the game (1, 6, 9, or 12).
    """
    options = options or GameOptions()

    # Load knowledge base specific to this challenge.
    options.kb = KnowledgeBase.load(KB_PATH)

    rngs = options.rngs
    rng_map = rngs['map']
    rng_objects = rngs['objects']
    rng_grammar = rngs['grammar']
    rng_quest = rngs['quest']
    rng_recipe = np.random.RandomState(settings["recipe_seed"])

    allowed_foods = list(FOODS)
    allowed_food_preparations = get_food_preparations(list(FOODS))
    if settings["split"] == "train":
        allowed_foods = list(FOODS_SPLITS['train'])
        allowed_food_preparations = dict(FOOD_PREPARATIONS_SPLITS['train'])
    elif settings["split"] == "valid":
        allowed_foods = list(FOODS_SPLITS['valid'])
        allowed_food_preparations = get_food_preparations(FOODS_SPLITS['valid'])
        # Also add food from the training set but with different preparations.
        allowed_foods += [f for f in FOODS if f in FOODS_SPLITS['train']]
        allowed_food_preparations.update(dict(FOOD_PREPARATIONS_SPLITS['valid']))
    elif settings["split"] == "test":
        allowed_foods = list(FOODS_SPLITS['test'])
        allowed_food_preparations = get_food_preparations(FOODS_SPLITS['test'])
        # Also add food from the training set but with different preparations.
        allowed_foods += [f for f in FOODS if f in FOODS_SPLITS['train']]
        allowed_food_preparations.update(dict(FOOD_PREPARATIONS_SPLITS['test']))

    if settings.get("cut"):
        # If "cut" skill is specified, remove all "uncut" preparations.
        for food, preparations in allowed_food_preparations.items():
            allowed_food_preparations[food] = [preparation for preparation in preparations if "uncut" not in preparation]

    if settings.get("cook"):
        # If "cook" skill is specified, remove all "raw" preparations.
        for food, preparations in list(allowed_food_preparations.items()):
            allowed_food_preparations[food] = [preparation for preparation in preparations if "raw" not in preparation]
            if len(allowed_food_preparations[food]) == 0:
                del allowed_food_preparations[food]
                allowed_foods.remove(food)

    M = textworld.GameMaker(options)

    recipe = M.new(type='RECIPE', name='')
    meal = M.new(type='meal', name='meal')
    M.add_fact("out", meal, recipe)
    meal.add_property("edible")
    M.nowhere.append(recipe)  # Out of play object.
    M.nowhere.append(meal)  # Out of play object.

    options.nb_rooms = settings.get("go", 1)
    if options.nb_rooms == 1:
        rooms_to_place = ROOMS[:1]
    elif options.nb_rooms == 6:
        rooms_to_place = ROOMS[:2]
    elif options.nb_rooms == 9:
        rooms_to_place = ROOMS[:3]
    elif options.nb_rooms == 12:
        rooms_to_place = ROOMS[:4]
    else:
        raise ValueError("Cooking games can only have {1, 6, 9, 12} rooms.")

    G = make_graph_world(rng_map, rooms_to_place, NEIGHBORS, size=(5, 5))
    rooms = M.import_graph(G)

    # Add doors
    for infos in DOORS:
        room1 = M.find_by_name(infos["path"][0])
        room2 = M.find_by_name(infos["path"][1])
        if room1 is None or room2 is None:
            continue  # This door doesn't exist in this world.

        path = M.find_path(room1, room2)
        if path:
            assert path.door is None
            name = pick_name(M, infos["names"], rng_objects)
            door = M.new_door(path, name)
            door.add_property("closed")

    # Find kitchen.
    kitchen = M.find_by_name("kitchen")

    # The following predicates will be used to force the "prepare meal"
    # command to happen in the kitchen.
    M.add_fact("cooking_location", kitchen, recipe)

    # Place some default furnitures.
    place_entities(M, ["table", "stove", "oven", "counter", "fridge", "BBQ", "shelf", "showcase"], rng_objects)

    # Place some random furnitures.
    nb_furnitures = rng_objects.randint(len(rooms), len(ENTITIES) + 1)
    place_random_furnitures(M, nb_furnitures, rng_objects)

    # Place the cookbook and knife somewhere.
    cookbook = place_entity(M, "cookbook", rng_objects)
    cookbook.infos.synonyms = ["recipe"]
    if rng_objects.rand() > 0.5 or settings.get("cut"):
        knife = place_entity(M, "knife", rng_objects)

    start_room = rng_map.choice(M.rooms)
    M.set_player(start_room)

    M.grammar = textworld.generator.make_grammar(options.grammar, rng=rng_grammar)

    # Remove every food preparation with grilled, if there is no BBQ.
    if M.find_by_name("BBQ") is None:
        for name, food_preparations in allowed_food_preparations.items():
            allowed_food_preparations[name] = [food_preparation for food_preparation in food_preparations
                                               if "grilled" not in food_preparation]

        # Disallow food with an empty preparation list.
        allowed_foods = [name for name in allowed_foods if allowed_food_preparations[name]]

    # Decide which ingredients are needed.
    nb_ingredients = settings.get("recipe", 1)
    assert nb_ingredients > 0 and nb_ingredients <= 5, "recipe must have {1,2,3,4,5} ingredients."
    ingredient_foods = place_random_foods(M, nb_ingredients, rng_quest, allowed_foods)

    # Sort by name (to help differentiate unique recipes).
    ingredient_foods = sorted(ingredient_foods, key=lambda f: f.name)

    # Decide on how the ingredients should be processed.
    ingredients = []
    for i, food in enumerate(ingredient_foods):
        food_preparations = allowed_food_preparations[food.name]
        idx = rng_quest.randint(0, len(food_preparations))
        type_of_cooking, type_of_cutting = food_preparations[idx]
        ingredients.append((food, type_of_cooking, type_of_cutting))

        # ingredient = M.new(type="ingredient", name="")
        # food.add_property("ingredient_{}".format(i + 1))
        # M.add_fact("base", food, ingredient)
        # M.add_fact(type_of_cutting, ingredient)
        # M.add_fact(type_of_cooking, ingredient)
        # M.add_fact("in", ingredient, recipe)
        # M.nowhere.append(ingredient)

    # Move ingredients in the player's inventory according to the `take` skill.
    nb_ingredients_already_in_inventory = nb_ingredients - settings.get("take", 0)
    shuffled_ingredients = list(ingredient_foods)
    rng_quest.shuffle(shuffled_ingredients)
    for ingredient in shuffled_ingredients[:nb_ingredients_already_in_inventory]:
        M.move(ingredient, M.inventory)

    # Compute inventory capacity.
    inventory_limit = 10  # More than enough.
    if settings.get("drop"):
        inventory_limit = nb_ingredients
        if nb_ingredients == 1 and settings.get("cut"):
            inventory_limit += 1  # So we can hold the knife along with the ingredient.

    # Add distractors for each ingredient.
    def _place_one_distractor(candidates, ingredient):
        rng_objects.shuffle(candidates)
        for food_name in candidates:
            distractor = M.find_by_name(food_name)
            if distractor:
                if distractor.parent == ingredient.parent:
                    break  # That object already exists and is considered as a distractor.

                continue  # That object already exists. Can't used it as distractor.

            # Place the distractor in the same "container" as the ingredient.
            distractor = place_food(M, food_name, rng_objects, place_it=False)
            ingredient.parent.add(distractor)
            break

    for ingredient in ingredient_foods:
        if ingredient.parent == M.inventory and nb_ingredients_already_in_inventory >= inventory_limit:
            # If ingredient is in the inventory but inventory is full, do not add distractors.
            continue

        splits = ingredient.name.split()
        if len(splits) == 1:
            continue  # No distractors.

        prefix, suffix = splits[0], splits[-1]
        same_prefix_list = [f for f in allowed_foods if f.startswith(prefix) if f != ingredient.name]
        same_suffix_list = [f for f in allowed_foods if f.endswith(suffix) if f != ingredient.name]

        if same_prefix_list:
            _place_one_distractor(same_prefix_list, ingredient)

        if same_suffix_list:
            _place_one_distractor(same_suffix_list, ingredient)

    # Add distractors foods. The amount is drawn from N(nb_ingredients, 3).
    nb_distractors = abs(int(rng_objects.randn(1) * 3 + nb_ingredients))
    distractors = place_random_foods(M, nb_distractors, rng_objects, allowed_foods)

    # If recipe_seed is positive, a new recipe is sampled.
    if settings["recipe_seed"] > 0:
        assert settings.get("take", 0), "Shuffle recipe requires the 'take' skill."
        potential_ingredients = ingredient_foods + distractors
        rng_recipe.shuffle(potential_ingredients)
        ingredient_foods = potential_ingredients[:nb_ingredients]

        # Decide on how the ingredients of the new recipe should be processed.
        ingredients = []
        for i, food in enumerate(ingredient_foods):
            food_preparations = allowed_food_preparations[food.name]
            idx = rng_recipe.randint(0, len(food_preparations))
            type_of_cooking, type_of_cutting = food_preparations[idx]
            ingredients.append((food, type_of_cooking, type_of_cutting))

    # Add necessary facts about the recipe.
    for food, type_of_cooking, type_of_cutting in ingredients:
        ingredient = M.new(type="ingredient", name="")
        food.add_property("ingredient_{}".format(i + 1))
        M.add_fact("base", food, ingredient)
        M.add_fact(type_of_cutting, ingredient)
        M.add_fact(type_of_cooking, ingredient)
        M.add_fact("in", ingredient, recipe)
        M.nowhere.append(ingredient)

    # Depending on the skills and how the ingredient should be processed
    # we change the predicates of the food objects accordingly.
    for food, type_of_cooking, type_of_cutting in ingredients:
        if not settings.get("cook"):  # Food should already be cooked accordingly.
            food.add_property(type_of_cooking)
            food.add_property("cooked")
            if food.has_property("inedible"):
                food.add_property("edible")
                food.remove_property("inedible")
            if food.has_property("raw"):
                food.remove_property("raw")
            if food.has_property("needs_cooking"):
                food.remove_property("needs_cooking")

        if not settings.get("cut"):  # Food should already be cut accordingly.
            food.add_property(type_of_cutting)
            food.remove_property("uncut")

    if not settings.get("open"):
        for entity in M._entities.values():
            if entity.has_property("closed"):
                entity.remove_property("closed")
                entity.add_property("open")

    walkthrough = []
    # Build TextWorld quests.
    quests = []
    consumed_ingredient_events = []
    for i, ingredient in enumerate(ingredients):
        ingredient_consumed = Event(conditions={M.new_fact("consumed", ingredient[0])})
        consumed_ingredient_events.append(ingredient_consumed)
        ingredient_burned = Event(conditions={M.new_fact("burned", ingredient[0])})
        quests.append(Quest(win_events=[], fail_events=[ingredient_burned]))

        if ingredient[0] not in M.inventory:
            holding_ingredient = Event(conditions={M.new_fact("in", ingredient[0], M.inventory)})
            quests.append(Quest(win_events=[holding_ingredient]))

        win_events = []
        if ingredient[1] != TYPES_OF_COOKING[0] and not ingredient[0].has_property(ingredient[1]):
            win_events += [Event(conditions={M.new_fact(ingredient[1], ingredient[0])})]

        fail_events = [Event(conditions={M.new_fact(t, ingredient[0])})
                       for t in set(TYPES_OF_COOKING[1:]) - {ingredient[1]}]  # Wrong cooking.

        quests.append(Quest(win_events=win_events, fail_events=[ingredient_consumed] + fail_events))

        win_events = []
        if ingredient[2] != TYPES_OF_CUTTING[0] and not ingredient[0].has_property(ingredient[2]):
            win_events += [Event(conditions={M.new_fact(ingredient[2], ingredient[0])})]

        fail_events = [Event(conditions={M.new_fact(t, ingredient[0])})
                       for t in set(TYPES_OF_CUTTING[1:]) - {ingredient[2]}]  # Wrong cutting.

        quests.append(Quest(win_events=win_events, fail_events=[ingredient_consumed] + fail_events))

    holding_meal = Event(conditions={M.new_fact("in", meal, M.inventory)})
    quests.append(Quest(win_events=[holding_meal], fail_events=consumed_ingredient_events))

    meal_burned = Event(conditions={M.new_fact("burned", meal)})
    meal_consumed = Event(conditions={M.new_fact("consumed", meal)})
    quests.append(Quest(win_events=[meal_consumed], fail_events=[meal_burned]))

    M.quests = quests

    G = compute_graph(M)  # Needed by the move(...) function called below.

    # Build walkthrough.
    current_room = start_room
    walkthrough = []

    # Start by checking the inventory.
    walkthrough.append("inventory")

    # 0. Find the kitchen and read the cookbook.
    walkthrough += move(M, G, current_room, kitchen)
    current_room = kitchen
    walkthrough.append("examine cookbook")

    # 1. Drop unneeded objects.
    for entity in M.inventory.content:
        if entity not in ingredient_foods:
            walkthrough.append("drop {}".format(entity.name))

    # 2. Collect the ingredients.
    for food, type_of_cooking, type_of_cutting in ingredients:
        if food.parent == M.inventory:
            continue

        food_room = food.parent.parent if food.parent.parent else food.parent
        walkthrough += move(M, G, current_room, food_room)

        if food.parent.has_property("closed"):
            walkthrough.append("open {}".format(food.parent.name))

        if food.parent == food_room:
            walkthrough.append("take {}".format(food.name))
        else:
            walkthrough.append("take {} from {}".format(food.name, food.parent.name))

        current_room = food_room

    # 3. Go back to the kitchen.
    walkthrough += move(M, G, current_room, kitchen)

    # 4. Process ingredients (cook).
    if settings.get("cook"):
        for food, type_of_cooking, _ in ingredients:
            if type_of_cooking == "fried":
                stove = M.find_by_name("stove")
                walkthrough.append("cook {} with {}".format(food.name, stove.name))
            elif type_of_cooking == "roasted":
                oven = M.find_by_name("oven")
                walkthrough.append("cook {} with {}".format(food.name, oven.name))
            elif type_of_cooking == "grilled":
                toaster = M.find_by_name("BBQ")
                # 3.a move to the backyard.
                walkthrough += move(M, G, kitchen, toaster.parent)
                # 3.b grill the food.
                walkthrough.append("cook {} with {}".format(food.name, toaster.name))
                # 3.c move back to the kitchen.
                walkthrough += move(M, G, toaster.parent, kitchen)

    # 5. Process ingredients (cut).
    if settings.get("cut"):
        free_up_space = settings.get("drop") and not len(ingredients) == 1
        knife = M.find_by_name("knife")
        if knife:
            knife_location = knife.parent.name
            knife_on_the_floor = knife_location == "kitchen"
            for i, (food, _, type_of_cutting) in enumerate(ingredients):
                if type_of_cutting == "uncut":
                    continue

                if free_up_space:
                    ingredient_to_drop = ingredients[(i + 1) % len(ingredients)][0]
                    walkthrough.append("drop {}".format(ingredient_to_drop.name))

                # Assume knife is reachable.
                if knife_on_the_floor:
                    walkthrough.append("take {}".format(knife.name))
                else:
                    walkthrough.append("take {} from {}".format(knife.name, knife_location))

                if type_of_cutting == "chopped":
                    walkthrough.append("chop {} with {}".format(food.name, knife.name))
                elif type_of_cutting == "sliced":
                    walkthrough.append("slice {} with {}".format(food.name, knife.name))
                elif type_of_cutting == "diced":
                    walkthrough.append("dice {} with {}".format(food.name, knife.name))

                walkthrough.append("drop {}".format(knife.name))
                knife_on_the_floor = True
                if free_up_space:
                    walkthrough.append("take {}".format(ingredient_to_drop.name))

    # 6. Prepare and eat meal.
    walkthrough.append("prepare meal")
    walkthrough.append("eat meal")

    cookbook_desc = "You open the copy of 'Cooking: A Modern Approach (3rd Ed.)' and start reading:\n"
    recipe = textwrap.dedent(
        """
        Recipe #1
        ---------
        Gather all following ingredients and follow the directions to prepare this tasty meal.

        Ingredients:
        {ingredients}

        Directions:
        {directions}
        """
    )
    recipe_ingredients = "\n  ".join(ingredient[0].name for ingredient in ingredients)

    recipe_directions = []
    for ingredient in ingredients:
        cutting_verb = TYPES_OF_CUTTING_VERBS.get(ingredient[2])
        if cutting_verb:
            recipe_directions.append(cutting_verb + " the " + ingredient[0].name)

        cooking_verb = TYPES_OF_COOKING_VERBS.get(ingredient[1])
        if cooking_verb:
            recipe_directions.append(cooking_verb + " the " + ingredient[0].name)

    recipe_directions.append("prepare meal")
    recipe_directions = "\n  ".join(recipe_directions)
    recipe = recipe.format(ingredients=recipe_ingredients, directions=recipe_directions)
    cookbook.infos.desc = cookbook_desc + recipe

    # Limit capacity of the inventory.
    for i in range(inventory_limit):
        slot = M.new(type="slot", name="")
        if i < len(M.inventory.content):
            slot.add_property("used")
        else:
            slot.add_property("free")

        M.nowhere.append(slot)

    # Sanity checks:
    for entity in M._entities.values():
        if entity.type in ["c", "d"]:
            if not (entity.has_property("closed")
                    or entity.has_property("open")
                    or entity.has_property("locked")):
                raise ValueError("Forgot to add closed/locked/open property for '{}'.".format(entity.name))

    # M.set_walkthrough(walkthrough)  # BUG: having several "slots" causes issues with dependency tree.
    game = M.build()

    # Collect infos about this game.
    metadata = {
        "seeds": options.seeds,
        "goal": cookbook.infos.desc,
        "recipe": recipe,
        "ingredients": [(food.name, cooking, cutting) for food, cooking, cutting in ingredients],
        "settings": settings,
        "entities": [e.name for e in M._entities.values() if e.name],
        "nb_distractors": nb_distractors,
        "walkthrough": walkthrough,
        "max_score": sum(quest.reward for quest in game.quests),
    }

    objective = ("You are hungry! Let's cook a delicious meal. Check the cookbook"
                 " in the kitchen for the recipe. Once done, enjoy your meal!")
    game.objective = objective

    game.metadata = metadata
    skills_uuid = "+".join("{}{}".format(k, "" if settings[k] is True else settings[k])
                           for k in SKILLS if k in settings and settings[k])
    uuid = "tw-cooking{split}-{specs}-{seeds}"
    uuid = uuid.format(split="-{}".format(settings["split"]) if settings.get("split") else "",
                       specs=skills_uuid,
                       seeds=encode_seeds([options.seeds[k] for k in sorted(options.seeds)]))
    game.metadata["uuid"] = uuid
    return game
예제 #18
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 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
예제 #19
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
예제 #20
0
def test_optional_and_repeatable_quests():
    M = textworld.GameMaker()
    museum = M.new_room("Museum")

    weak_statue = M.new(type="o", name="ivory statue")
    normal_statue = M.new(type="o", name="stone statue")
    strong_statue = M.new(type="o", name="granite statue")

    museum.add(weak_statue)
    museum.add(normal_statue)
    museum.add(strong_statue)

    M.set_player(museum)

    M.quests = [
        Quest(win_events=[
            Event(conditions=[M.new_fact('in', weak_statue, M.inventory)]),
        ],
              reward=-10,
              optional=True,
              repeatable=True),
        Quest(win_events=[
            Event(conditions=[M.new_fact('in', normal_statue, M.inventory)]),
        ],
              reward=3,
              optional=True),
        Quest(
            win_events=[
                Event(
                    conditions=[M.new_fact('in', strong_statue, M.inventory)]),
            ],
            reward=5,
        )
    ]
    M.set_walkthrough(
        ["take ivory", "take stone", "drop ivory", "take granite"])

    game = M.build()
    game_name = "test_optional_and_repeatable_quests"
    with make_temp_directory(prefix=game_name) as tmpdir:
        game_file = _compile_game(game, path=tmpdir)
        env = textworld.start(game_file)
        state = env.reset()
        state.max_score == 8  # 5 (main quest) + 3 (optional quest)

        state, score, done = env.step("take ivory")
        assert not done
        assert score == -10

        state, score, done = env.step("look")
        assert score == -20

        state, score, done = env.step("take stone")
        assert not done
        assert score == -27  # -10 + 3

        state, score, done = env.step("drop ivory")
        assert score == -27

        state, score, done = env.step("take granite")
        assert done
        assert score == -22  # +5.
        assert "-22 out of a possible 8" in state.feedback