Esempio n. 1
0
    def testCropping(self):
        """Observations from subgames can be cropped for mutual compatibility."""

        # The games will use these worlds.
        arts = [
            ['P..', '...', '...'],
            [
                '...............', '...............', '.......P.......',
                '...............', '...............'
            ],
            ['...', '...', 'P..'],
        ]

        # But these croppers will ensure that they all have a reasonable size.
        croppers = [
            None,
            cropping.FixedCropper(top_left_corner=(1, 6), rows=3, cols=3),
            cropping.FixedCropper(top_left_corner=(0, 0), rows=3, cols=3),
        ]

        # Create and start the story.
        story = storytelling.Story(
            chapters=[lambda art=a: self._make_game(art) for a in arts],
            croppers=croppers)
        observation, reward, pcontinue = story.its_showtime()
        del reward, pcontinue  # unused

        # For better narration of these observation comparisons, read through
        # testSequenceOfGames.
        self.assertBoard(observation.board, arts[0])
        self.assertMachinima(engine=story,
                             frames=[(None, ['...', '.P.', '...']),
                                     (None, arts[2]),
                                     (None, ['...', '...', '.P.'])])
Esempio n. 2
0
    def testDictOfGames(self):
        """A `Story` will run as directed through a dict of games."""

        # The two games in this dict will use these two worlds.
        arts = {'one': ['P..', '...', '...'], 'two': ['...', '.P.', '...']}

        # Create and start the story. Note how we inject a value into the first
        # game's Plot that _DieRightward uses to tell Story how to advance through
        # the various subgames. A bit roundabout, but it has the side-effect of also
        # testing that Plot contents persist between games.
        story = storytelling.Story(
            {k: lambda art=v: self._make_game(art)
             for k, v in arts.items()},
            first_chapter='one')
        story.current_game.the_plot['chapter_names'] = [
            'two', 'one', 'one', None
        ]
        observation, reward, pcontinue = story.its_showtime()
        del reward, pcontinue  # unused

        # For better narration of these observation comparisons, read through
        # testSequenceOfGames.
        self.assertBoard(observation.board, arts['one'])  # First frame.
        self.assertMachinima(engine=story,
                             frames=[(None, arts['two']), (None, arts['one']),
                                     (None, arts['one']),
                                     (None, ['.P.', '...', '...'])])
Esempio n. 3
0
def make_game():
  """Builds and returns an Ordeal game."""

  # Factories for building the three subgames of Ordeal.
  def make_castle():
    return ascii_art.ascii_art_to_game(
        GAME_ART_CASTLE, what_lies_beneath=' ',
        sprites=dict(P=PlayerSprite, D=DragonduckSprite),
        update_schedule=['P', 'D'], z_order=['D', 'P'])

  def make_cavern():
    return ascii_art.ascii_art_to_game(
        GAME_ART_CAVERN, what_lies_beneath=' ',
        sprites=dict(P=PlayerSprite), drapes=dict(S=SwordDrape),
        update_schedule=['P', 'S'])

  def make_kansas():
    return ascii_art.ascii_art_to_game(
        GAME_ART_KANSAS, what_lies_beneath='~', sprites=dict(P=PlayerSprite))

  # A cropper for cropping the "Kansas" part of the game to the size of the
  # other two games.
  crop_kansas = cropping.ScrollingCropper(
      rows=8, cols=15, to_track='P', scroll_margins=(2, 3))

  return storytelling.Story(
      chapters=dict(castle=make_castle, cavern=make_cavern, kansas=make_kansas),
      croppers=dict(castle=None, cavern=None, kansas=crop_kansas),
      first_chapter='kansas')
Esempio n. 4
0
 def make_episode(self):
   """Factory method for generating new episodes of the game."""
   target_char = self._rng.choice(SYMBOLS_TO_SHUFFLE)
   return storytelling.Story([
       lambda: self._make_explore_phase(target_char),
       self._make_distractor_phase,
       lambda: self._make_reward_phase(target_char),
   ], croppers=common.get_cropper())
Esempio n. 5
0
    def testCompatibilityChecking(self):
        """The `Story` constructor spots compatibility problems between games."""

        # The first Story will fail because these game worlds have different sizes,
        # and there are no croppers in use.
        arts = [
            ['P..', '...', '...'],
            [
                '...............', '...............', '.......P.......',
                '...............', '...............'
            ],
        ]

        with self.assertRaisesRegexp(ValueError,
                                     'observations that are the same'):
            _ = storytelling.Story(
                [lambda art=a: self._make_game(art) for a in arts])

        # This next Story will fail because characters are shared between Sprites,
        # Drapes, and at least one Backdrop.
        art = ['1..', '.2.', '..3']

        error_regexp = (r'.*same character in two different ways'
                        r'.*both a Sprite and a Drape: \[2\];'
                        r'.*both a Sprite and in a Backdrop: \[1\];'
                        r'.*both a Drape and in a Backdrop: \[3\].*')
        with self.assertRaisesRegexp(ValueError, error_regexp):
            _ = storytelling.Story([
                # pylint: disable=g-long-lambda
                lambda: ascii_art.ascii_art_to_game(
                    art,
                    what_lies_beneath='.',
                    sprites={'1': self._DieRightward},
                    drapes={'2': self._IdleDrape}),
                lambda: ascii_art.ascii_art_to_game(
                    art,
                    what_lies_beneath='.',
                    sprites={'2': self._DieRightward},
                    drapes={'3': self._IdleDrape}),
                # pylint: enable=g-long-lambda
            ])
Esempio n. 6
0
  def make_episode(self):
    """Factory method for generating new episodes of the game."""
    if self._crop:
      croppers = common.get_cropper()
    else:
      croppers = None

    return storytelling.Story([
        self._make_explore_phase,
        self._make_distractor_phase,
        self._make_reward_phase,
    ], croppers=croppers)
Esempio n. 7
0
    def testSequenceOfGames(self):
        """A `Story` will play through a sequence of games automatically."""

        # The four games in the sequence use these four worlds.
        arts = list(
            zip(['P..', '...', '...', '...'], ['...', '...', 'P..', '...'],
                ['...', 'P..', '...', '.P.']))

        # Create and start the story.
        story = storytelling.Story(
            [lambda art=a: self._make_game(art) for a in arts])
        observation, reward, pcontinue = story.its_showtime()
        del reward, pcontinue  # unused

        # The first frame is the first game's first frame.
        self.assertBoard(observation.board, arts[0])

        # This should set us up to get the following sequence of observations.
        self.assertMachinima(
            engine=story,
            frames=[
                # The second frame is the second game's first frame. The terminal
                # frame from the first game is discarded, so we never get to see
                # the Sprite move rightward. (None was the action just prior to the
                # second frame. Actions are not used in this test.)
                (None, arts[1]),

                # The third frame is the third game's first frame. Same reasoning.
                (None, arts[2]),

                # The fourth frame is the fourth game's first frame.
                (None, arts[3]),

                # The fifth frame is the fourth game's last frame. In a Story, you
                # always see the terminal frame of the very last game (and you see
                # no other game's terminal frame).
                (None, ['...', '...', '..P'])
            ],
        )
Esempio n. 8
0
    def testStandIns(self):
        """The "abstraction breakers" in `Story` are suitably simulated."""

        # The games will use these worlds.
        arts = list(zip(['P~~', '..S'], ['~~~', '..D'], ['www', '###']))

        # Create the story.
        story = storytelling.Story([
            # this is perfectly readable :-P
            # pylint: disable=g-long-lambda
            lambda: ascii_art.ascii_art_to_game(arts[0],
                                                what_lies_beneath='~',
                                                sprites=
                                                {'P': self._DieRightward},
                                                drapes={'w': self._IdleDrape}),
            lambda: ascii_art.ascii_art_to_game(arts[1],
                                                what_lies_beneath='.',
                                                sprites=
                                                {'S': self._DieRightward},
                                                drapes={'D': self._IdleDrape})
            # pylint: enable=g-long-lambda
        ])

        # The "abstraction breaker" methods should fake the same sorts of results
        # that you would get if the Story were actually implemented by a single
        # Engine. Sprites and Drapes should contain an entry for any character
        # associated with a Sprite or a Drape across all the games, and the
        # contrived Backdrop's palette should have all the backdrop characters
        # from anywhere in the story.
        self.assertEqual(sorted(story.backdrop.palette), ['#', '.', '~'])
        self.assertEqual(sorted(story.things), ['D', 'P', 'S', 'w'])

        # The only real Sprites and Drapes in story.things are the ones from the
        # current game; the others are dummies. Here we test the module function
        # that identifies dummies.
        self.assertTrue(storytelling.is_fictional(story.things['D']))
        self.assertFalse(storytelling.is_fictional(story.things['P']))
        self.assertTrue(storytelling.is_fictional(story.things['S']))
        self.assertFalse(storytelling.is_fictional(story.things['w']))
Esempio n. 9
0
    def testInterGameRewardAccumulation(self):
        """Inter-game terminal rewards are carried into the next game."""
        class GenerousQuitterDrape(plab_things.Drape):
            """This Drape gives a reward of 5 and quits immediately."""
            def update(self, actions, board, layers, backdrop, things,
                       the_plot):
                the_plot.add_reward(5.0)
                the_plot.terminate_episode()

        # Create a new Story with all subgames using the usual art.
        art = ['P..', '...', '...']
        story = storytelling.Story([
            # this is perfectly readable :-P
            # pylint: disable=g-long-lambda

            # We should see the initial observation from this first game, but not
            # the second, terminal observation. The agent receives no reward.
            lambda: ascii_art.ascii_art_to_game(
                art, what_lies_beneath='.', sprites={'P': self._DieRightward}),

            # We should see no observations from the next three games, since they
            # all terminate immediately (courtesy of GenerousQuitterDrape). However,
            # they also each contribute an additional 5.0 to the summed reward the
            # agent will eventually see...
            lambda: ascii_art.ascii_art_to_game(
                art,
                what_lies_beneath='.',
                sprites={'P': self._DieRightward},
                drapes={'Q': GenerousQuitterDrape}),
            lambda: ascii_art.ascii_art_to_game(
                art,
                what_lies_beneath='.',
                sprites={'P': self._DieRightward},
                drapes={'Q': GenerousQuitterDrape}),
            lambda: ascii_art.ascii_art_to_game(
                art,
                what_lies_beneath='.',
                sprites={'P': self._DieRightward},
                drapes={'Q': GenerousQuitterDrape}),

            # ...when it sees the first observation of this game here. The second,
            # terminal observation is dropped, but then...
            lambda: ascii_art.ascii_art_to_game(
                art, what_lies_beneath='.', sprites={'P': self._DieRightward}),

            # ...we finally see an observation from a game involving a
            # GenerousQuitterDrape when we see its first and terminal step, as the
            # terminal step of the story. We also receive another 5.0 reward.
            lambda: ascii_art.ascii_art_to_game(
                art,
                what_lies_beneath='.',
                sprites={'P': self._DieRightward},
                drapes={'Q': GenerousQuitterDrape}),
            # pylint: enable=g-long-lambda
        ])

        # Now to see if our predictions are true.
        observation, reward, pcontinue = story.its_showtime()  # First step.
        self.assertBoard(observation.board, art)
        self.assertIsNone(reward)  # Nobody assigned any reward in this step.
        self.assertEqual(pcontinue, 1.0)

        observation, reward, pcontinue = story.play(None)  # Second step.
        self.assertBoard(observation.board,
                         art)  # First obs. of penultimate game.
        self.assertEqual(reward,
                         15.0)  # Summed across final steps of games 2-4.
        self.assertEqual(pcontinue, 1.0)

        observation, reward, pcontinue = story.play(None)  # Third, final step.
        self.assertBoard(observation.board,
                         art)  # Terminal obs. of final game.
        self.assertEqual(reward, 5.0)  # From the GenerousQuitterDrape.
        self.assertEqual(pcontinue, 0.0)