Пример #1
0
    def _do_test_reward_and_episode_end(self, q_pre_update, expected_discount):
        """Core implementation of `testRewardAndEpisodeEndWith*Discount` tests.

    Args:
      q_pre_update: Pre-`update` code to inject for the Q sprite.
      expected_discount: `discount` we expect to observe after the final
          game step.
    """
        # Not helpful in this test, since argument lists are long:
        # pylint: disable=g-long-lambda

        # Our test takes place in this tiny world:
        art = ['.........', '...Q.R...', '.........']

        # Here we make the game.
        engine = ascii_art.ascii_art_to_game(
            art=art,
            what_lies_beneath='.',
            # Q and R are sprites.
            sprites=dict(Q=tt.TestSprite, R=tt.TestSprite),
            # We set a fixed update schedule for deterministic tests.
            update_schedule='QR')

        ### GAME ITERATION #0. Nothing happens. No entity has issued a reward, so
        ### the reward is None.

        unused_observation, reward, discount = engine.its_showtime()
        self.assertIsNone(reward)
        self.assertEqual(discount, 1.0)
        self.assertFalse(engine.game_over)

        ### GAME ITERATION #1. Have the sprites credit us with some reward. Note
        ### how reward is accumulated across all entities.

        tt.pre_update(
            engine, 'Q',
            lambda actions, board, layers, backdrop, things, the_plot:
            (the_plot.add_reward('pyco')))
        tt.pre_update(
            engine, 'R',
            lambda actions, board, layers, backdrop, things, the_plot:
            (the_plot.add_reward('lab!')))

        unused_observation, reward, discount = engine.play('mound of beans')
        self.assertEqual(reward, 'pycolab!')
        self.assertEqual(discount, 1.0)
        self.assertFalse(engine.game_over)

        ### GAME ITERATION #2. Have Q call the whole thing off.

        tt.pre_update(engine, 'Q', q_pre_update)
        tt.pre_update(
            engine, 'R',
            lambda actions, board, layers, backdrop, things, the_plot:
            (the_plot.add_reward('trousers')))

        unused_observation, reward, discount = engine.play('mound of beans')
        self.assertEqual(reward, 'trousers')
        self.assertEqual(discount, expected_discount)
        self.assertTrue(engine.game_over)
Пример #2
0
    def testChangingZOrdering(self):
        """Game entities can change the Z-ordering."""
        # Not helpful in this test, since argument lists are long:
        # pylint: disable=g-long-lambda

        # Our test takes place in this very tiny world:
        art = ['.abc.']

        # Here we make the game.
        engine = ascii_art.ascii_art_to_game(
            art=art,
            what_lies_beneath='.',
            # a, b, and c are sprites.
            sprites=dict(a=ascii_art.Partial(tt.TestMazeWalker, impassable=''),
                         b=ascii_art.Partial(tt.TestMazeWalker, impassable=''),
                         c=ascii_art.Partial(tt.TestMazeWalker,
                                             impassable='')),
            # Note this initial z-ordering.
            z_order='abc')

        ### GAME ITERATION #0. Nothing happens; we just get the game started.

        engine.its_showtime()

        ### GAME ITERATION #1. All of our sprites move to stand on top of one
        ### another. No Z-order change yet.

        observation, unused_reward, unused_discount = engine.play({
            'a': 'e',
            'c': 'w'
        })
        self.assertBoard(observation.board, ['..c..'])

        ### GAME ITERATION #2. b moves in front of c. Z-ordering should be 'acb'.

        tt.pre_update(
            engine, 'b',
            lambda actions, board, layers, backdrop, things, the_plot:
            (the_plot.change_z_order('b', 'c')))
        observation, unused_reward, unused_discount = engine.play(None)
        self.assertBoard(observation.board, ['..b..'])

        ### GAME ITERATION #2. c moves to the back. Z-ordering should be 'cab'.

        tt.pre_update(
            engine, 'c',
            lambda actions, board, layers, backdrop, things, the_plot:
            (the_plot.change_z_order('c', None)))
        observation, unused_reward, unused_discount = engine.play(None)
        self.assertBoard(observation.board, ['..b..'])

        ### GAME ITERATION #3. b moves to the back. Z-ordering should be 'bca'.

        tt.pre_update(
            engine, 'b',
            lambda actions, board, layers, backdrop, things, the_plot:
            (the_plot.change_z_order('b', None)))
        observation, unused_reward, unused_discount = engine.play(None)
        self.assertBoard(observation.board, ['..a..'])
Пример #3
0
    def testScrolly(self):
        """Verify correct interoperation of `Scrolly` and `MazeWalker`."""
        # Not helpful in this test, since argument lists are long:
        # pylint: disable=g-long-lambda

        # The test takes place in this scrolling world.
        maze_art = [
            '########################',
            '#          #       #   #',
            '# # ###### #   ### # # #',
            '# #      #   #   # # # #',
            '######## +#### ### # # #',  # Note top-left corner.
            '# #      #   # #N    # #',  # A non-player character.
            '# # ###### # # #########',
            '#          #P          #',  # The player.
            '# #################### #',
            '#                      #',
            '########################'
        ]

        board_art = [
            '~~~~~',  # The maze art will drape across this board art.
            '~~~~~',
            '~~~~~',
            '~~~~~',
            '~~~~~'
        ]

        # Build and test the helper object that helps us construct a Scrolly object.
        scrolly_info = prefab_drapes.Scrolly.PatternInfo(
            maze_art,
            board_art,
            board_northwest_corner_mark='+',
            what_lies_beneath='#')

        self.assertEqual((5, 5), scrolly_info.kwargs('#')['board_shape'])
        self.assertEqual((4, 9),
                         scrolly_info.kwargs('#')['board_northwest_corner'])
        np.testing.assert_array_equal(
            scrolly_info.kwargs('#')['whole_pattern'],
            np.array([
                list(row) for row in [
                    '111111111111111111111111', '100000000001000000010001',
                    '101011111101000111010101', '101000000100010001010101',
                    '111111110111110111010101', '101000000100010100000101',
                    '101011111101010111111111', '100000000001000000000001',
                    '101111111111111111111101', '100000000000000000000001',
                    '111111111111111111111111'
                ]
            ]).astype(bool))

        # Here we make the game. In this game, scrolling will only occur if it looks
        # like we are getting to within one pixel of the edge of the game board.
        engine = ascii_art.ascii_art_to_game(
            art=board_art,
            what_lies_beneath='~',

            # N and P are MazeWalkers, and P is an egocentric scroller.
            sprites=dict(N=ascii_art.Partial(tt.TestMazeWalker, impassable=''),
                         P=ascii_art.Partial(tt.TestMazeWalker,
                                             impassable='#N',
                                             egocentric_scroller=True)),

            # The world itself is a Scrolly drape with narrow margins.
            drapes={
                '#':
                ascii_art.Partial(tt.TestScrolly,
                                  scroll_margins=(1, 1),
                                  **scrolly_info.kwargs('#'))
            },

            # The update schedule is in a separate and later update group from the
            # group containing the pycolab entities that control non-traversable
            # characters (i.e. the wall, '#').
            update_schedule=[['#'], ['N', 'P']])

        # Go ahead and start the game. We will take this opportunity to teleport the
        # two sprites into their proper locations in the scrolling world. This is
        # often something you take care of in your Sprite's constructor, and doing
        # it this way is not encouraged at all.
        tt.pre_update(
            engine,
            'N',
            thing_to_do=(
                lambda actions, board, layers, backdrop, things, the_plot:
                (things['N']._teleport(scrolly_info.virtual_position('N')))))
        tt.pre_update(
            engine,
            'P',
            thing_to_do=(
                lambda actions, board, layers, backdrop, things, the_plot:
                (things['P']._teleport(scrolly_info.virtual_position('P')))))
        engine.its_showtime()

        # We will soon want to execute a sequence of motions that the player Sprite
        # P and the moving Scrolly drape # should pay attention to, but the
        # non-player Sprite N should ignore. We can send motion commands just to P
        # and # by using "dict format" actions for TestMazeWalker and TestScrolly.
        go = lambda direction: {'P': direction, '#': direction}

        # Now we execute a sequence of motions, comparing the actual observations
        # against ASCII-art depictions of our expected observations.
        self.assertMachinima(
            engine=engine,
            frames=[
                # Let's start by doing nothing and seeing that the world looks as
                # we expect.
                (None, ['#####', '#~~~#', '#~#~#', '~~#P~', '#####']),

                # Let's try to go in directions that are blocked off and verify that
                # we stay put.
                (go('w'), ['#####', '#~~~#', '#~#~#', '~~#P~', '#####']),
                (go('sw'), ['#####', '#~~~#', '#~#~#', '~~#P~', '#####']),
                (go('s'), ['#####', '#~~~#', '#~#~#', '~~#P~', '#####']),

                # Now let's try to go drive by our friend N.
                (go('e'), ['####~', '~~~#~', '~#~#~', '~#~P~', '#####']),
                (go('e'), ['###~#', '~~#~#', '#~#~#', '#~~P~', '#####']),
                (go('e'), ['##~##', '~#~#N', '~#~##', '~~~P~', '#####']),
                (go('nw'), ['##~##', '~#~#N', '~#P##', '~~~~~', '#####']),
                (go('n'), ['##~##', '~#P#N', '~#~##', '~~~~~', '#####']),
                (go('n'), ['~#~~~', '##P##', '~#~#N', '~#~##', '~~~~~']),

                # And back to our starting place. The 'sw' move in particular is
                # tricky, since it only requires a scroll in one dimension, even
                # though the Sprite is moving in two dimensions at once.
                (go('s'), ['~#~~~', '##~##', '~#P#N', '~#~##', '~~~~~']),
                (go('s'), ['~#~~~', '##~##', '~#~#N', '~#P##', '~~~~~']),
                (go('sw'), ['##~##', '~#~#N', '~#~##', '~P~~~', '#####']),
                (go('w'), ['###~#', '~~#~#', '#~#~#', '#P~~~', '#####']),

                # Now exercise that same logic again along the horizontal axis (at
                # the "se" move in particular).
                (go('e'), ['###~#', '~~#~#', '#~#~#', '#~P~~', '#####']),
                (go('ne'), ['###~#', '~~#~#', '#~#P#', '#~~~~', '#####']),
                (go('se'), ['##~##', '~#~#N', '~#~##', '~~~P~', '#####']),
            ],
        )

        # Now let's start over with a new game. In this next game, we set no margins
        # at all, so scrolling the world is mandatory at each game iteration.
        engine = ascii_art.ascii_art_to_game(
            art=board_art,
            what_lies_beneath='~',

            # N and P are MazeWalkers, and P is an egocentric scroller.
            sprites=dict(N=ascii_art.Partial(tt.TestMazeWalker, impassable=''),
                         P=ascii_art.Partial(tt.TestMazeWalker,
                                             impassable='#N',
                                             egocentric_scroller=True)),

            # The world itself is a Scrolly drape with narrow margins.
            drapes={
                '#':
                ascii_art.Partial(tt.TestScrolly,
                                  scroll_margins=None,
                                  **scrolly_info.kwargs('#'))
            },

            # The update schedule is in a separate and later update group from the
            # group containing the pycolab entities that control non-traversable
            # characters (i.e. the wall, '#').
            update_schedule=[['#'], ['N', 'P']])

        # Again we use our start-of-game "teleportation trick", which is not really
        # appropriate for anything but tests, and even then it's pushing it...
        tt.pre_update(
            engine,
            'N',
            thing_to_do=(
                lambda actions, board, layers, backdrop, things, the_plot:
                (things['N']._teleport(scrolly_info.virtual_position('N')))))
        tt.pre_update(
            engine,
            'P',
            thing_to_do=(
                lambda actions, board, layers, backdrop, things, the_plot:
                (things['P']._teleport(scrolly_info.virtual_position('P')))))
        engine.its_showtime()

        # Here is a sequence of motions and the observations we expect to see.
        self.assertMachinima(
            engine=engine,
            frames=[
                # Let's start by doing nothing and seeing that the world looks as
                # we expect.
                (None, ['#####', '#~~~#', '#~#~#', '~~#P~', '#####']),

                # As before, let's try to go in directions that are blocked off and
                # verify that we stay put.
                (go('w'), ['#####', '#~~~#', '#~#~#', '~~#P~', '#####']),
                (go('sw'), ['#####', '#~~~#', '#~#~#', '~~#P~', '#####']),
                (go('s'), ['#####', '#~~~#', '#~#~#', '~~#P~', '#####']),

                # We're going to head for the western edge of the map. First we need
                # to head over this short vertically-oriented wall...
                (go('n'), ['#~~~#', '#####', '#~~~#', '#~#P#', '~~#~~']),
                (go('nw'), ['##~#~', '~#~~~', '~####', '~#~P~', '##~#~']),
                (go('sw'), ['~~#~~', '#~###', '~~#~~', '###P#', '~~~~#']),
                (go('sw'), ['##~##', '~~~#~', '####~', '~~~P~', '#####']),

                # Now, westward ho! An interesting thing will happen when the
                # scrolling world runs out of scenery---the sprite will actually
                # have to change its location on the game board.
                (go('w'), ['###~#', '~~~~#', '#####', '~~~P~', '#####']),
                (go('w'), ['####~', '~~~~~', '#####', '~~~P~', '#####']),
                (go('w'), ['#####', '~~~~~', '~####', '~~~P~', '#####']),
                (go('w'), ['#####', '#~~~~', '#~###', '~~~P~', '#####']),
                (go('w'), ['#####', '~#~~~', '~#~##', '~~~P~', '~####']),
                (go('w'), ['#####', '#~#~~', '#~#~#', '#~~P~', '#~###']),
                (
                    go(
                        'w'
                    ),  # Behold: eppur non si muove. The world is stuck, and
                    [
                        '#####',  # the Sprite is forced to move around inside if it.
                        '#~#~~',
                        '#~#~#',
                        '#~P~~',
                        '#~###'
                    ]),

                # Now, what happens if we try to go southwest? The board can (and
                # should) scroll vertically to keep the Sprite on the same row, but
                # it still can't scroll horizontally.
                (go('sw'), ['#~#~~', '#~#~#', '#~~~~', '#P###', '#~~~~']),

                # Now we head up and back east, and then that's about enough. In
                # both of these motions, the world can scroll around the Sprite, so
                # it's back to business as usual.
                (go('ne'), ['#####', '~#~~~', '~#~##', '~P~~~', '~####']),
                (go('e'), ['#####', '#~~~~', '#~###', '~P~~~', '#####']),
            ],
        )
Пример #4
0
    def testUpdateScheduleAndZOrder(self):
        """The engine abides by the update schedule and the Z-ordering."""

        # Our test takes place in this 3x5 world...
        art = ['.acb.', '..c..', '.dce.']

        # Here we make the game.
        engine = ascii_art.ascii_art_to_game(
            art=art,
            what_lies_beneath='.',

            # a, b, c, and d are sprites.
            sprites=dict(a=ascii_art.Partial(tt.TestMazeWalker, impassable=''),
                         b=ascii_art.Partial(tt.TestMazeWalker, impassable=''),
                         d=ascii_art.Partial(tt.TestMazeWalker, impassable=''),
                         e=ascii_art.Partial(tt.TestMazeWalker,
                                             impassable='')),

            # c is a drape that just sits there; Z is an invisible drape that
            # also just sits there.
            drapes=dict(c=tt.TestDrape, Z=tt.TestDrape),

            # This update schedule means that in a single game iteration:
            # 1. the Backdrop, a, and b all update, then the board is re-rendered,
            # 2. c updates, then the board is re-rendered,
            # 3. d and e update, then the board is re-rendered,
            # 4. Z updates, then the board is re-rendered.
            update_schedule=[['a', 'b'], ['c'], ['d', 'e'], ['Z']],

            # The Z-ordering says to render the entities in this order, from
            # back to front.
            z_order='Zabcde')

        ### GAME ITERATION #0. During the first update sweep, since none of the
        ### Sprites change their locations and none of the Drapes change their
        ### curtains, all entities will see the initial rendering of the board.

        tt.pre_update(engine, 'a', self.expectBoard(art, err_msg='a @ 0'))
        tt.pre_update(engine, 'b', self.expectBoard(art, err_msg='b @ 0'))
        tt.pre_update(engine, 'c', self.expectBoard(art, err_msg='c @ 0'))
        tt.pre_update(engine, 'd', self.expectBoard(art, err_msg='d @ 0'))
        tt.pre_update(engine, 'e', self.expectBoard(art, err_msg='e @ 0'))
        tt.pre_update(engine, 'Z', self.expectBoard(art, err_msg='Z @ 0'))

        observation, unused_reward, discount = engine.its_showtime()

        # Check that the observation is right and that discount is 1.
        self.assertBoard(observation.board, art, err_msg='obs @ 0')
        self.assertEqual(discount, 1.0)

        # Check that miscellaneous properties work.
        self.assertEqual(engine.rows, 3)
        self.assertEqual(engine.cols, 5)
        self.assertEqual(engine.z_order, ['Z', 'a', 'b', 'c', 'd', 'e'])
        self.assertSetEqual(set(engine.things.keys()),
                            {'a', 'b', 'c', 'd', 'e', 'Z'})
        self.assertIn('.', engine.backdrop.palette)

        ### GAME ITERATION #1. All sprites take a step to the right. As the
        ### update sweep takes place, the segmented update schedule causes
        ### different entities to see the board in different configurations.

        # a and b see the board as it was rendered after the last iteration.
        tt.pre_update(engine, 'a', self.expectBoard(art, err_msg='a @ 1'))
        tt.pre_update(engine, 'b', self.expectBoard(art, err_msg='b @ 1'))

        # c sees the board after a and b have moved right, but not d and e. Note
        # the Z-ordering determining how c and d overlap.
        tt.pre_update(
            engine, 'c',
            self.expectBoard(['..c.b', '..c..', '.dce.'], err_msg='c @ 1'))

        ## d and e see the board after c's update, but of course c didn't change...
        tt.pre_update(
            engine, 'd',
            self.expectBoard(['..c.b', '..c..', '.dce.'], err_msg='d @ 1'))
        tt.pre_update(
            engine, 'e',
            self.expectBoard(['..c.b', '..c..', '.dce.'], err_msg='e @ 1'))

        # Z sees the board after everyone else has moved.
        tt.pre_update(
            engine, 'Z',
            self.expectBoard(['..c.b', '..c..', '..d.e'], err_msg='Z @ 1'))

        observation, unused_reward, unused_discount = engine.play('e')

        # Check that the observation is right and that discount is 1.
        self.assertBoard(observation.board, ['..c.b', '..c..', '..d.e'],
                         err_msg='obs @ 1')
        self.assertEqual(discount, 1.0)

        ### GAME ITERATION #2. All sprites take a step to the left. We'll trust
        ### that this took place in the expected order and just check that the
        ### observation is correct.

        observation, unused_reward, unused_discount = engine.play('w')
        self.assertBoard(observation.board, art, err_msg='obs @ 2')
        self.assertEqual(discount, 1.0)

        ### GAME ITERATION #3. All sprites take another step to the left. We
        ### check everything again, this time.

        # First update group.
        tt.pre_update(engine, 'a', self.expectBoard(art, err_msg='a @ 3'))
        tt.pre_update(engine, 'b', self.expectBoard(art, err_msg='b @ 3'))

        # Second update group.
        tt.pre_update(
            engine, 'c',
            self.expectBoard(['a.c..', '..c..', '.dce.'], err_msg='c @ 3'))

        # Second update group.
        tt.pre_update(
            engine, 'd',
            self.expectBoard(['a.c..', '..c..', '.dce.'], err_msg='d @ 3'))
        tt.pre_update(
            engine, 'e',
            self.expectBoard(['a.c..', '..c..', '.dce.'], err_msg='e @ 3'))

        observation, unused_reward, unused_discount = engine.play('w')

        # Check that the observation is right and that discount is 1.
        self.assertBoard(observation.board, ['a.c..', '..c..', 'd.e..'],
                         err_msg='obs @ 3')
        self.assertEqual(discount, 1.0)
Пример #5
0
    def testPlotStateVariables(self):
        """State variables inside the Plot are updated correctly."""

        # Our test takes place in this very tiny world:
        art = ['.abc.']

        # Here we make the game.
        engine = ascii_art.ascii_art_to_game(
            art=art,
            what_lies_beneath='.',
            # a, b, and c are sprites.
            sprites=dict(a=tt.TestSprite, b=tt.TestSprite, c=tt.TestSprite),
            # We will test to see that these update groups are reflected in the
            # update_group property of the Plot. The ascii_art_to_game function
            # comes up with its own names for update groups, though, and those are
            # off limits to us, so we can't just check values directly...
            update_schedule=[['a', 'b'], ['c']])

        # ...so, we will store game iterations and update group values in this
        # dict, and check that all is as expected.
        state_info = []

        def add_state_info(actions, board, layers, backdrop, things, the_plot):
            del actions, board, layers, backdrop, things  # Unused.
            state_info.append((the_plot.frame, the_plot.update_group))

        ### GAME ITERATION #0.

        tt.pre_update(engine, 'a', add_state_info)
        tt.pre_update(engine, 'b', add_state_info)
        tt.pre_update(engine, 'c', add_state_info)

        engine.its_showtime()

        [(a_frame, a_update_group), (b_frame, b_update_group),
         (c_frame, c_update_group)] = state_info[:]

        self.assertEqual([0, 0, 0], [a_frame, b_frame, c_frame])
        self.assertEqual(a_update_group, b_update_group)
        self.assertNotEqual(a_update_group, c_update_group)

        ### GAME ITERATION #1.

        tt.pre_update(engine, 'a', add_state_info)
        tt.pre_update(engine, 'b', add_state_info)
        tt.pre_update(engine, 'c', add_state_info)

        engine.play('↑↑↓↓←→←→BA★')

        [(a_frame, a_new_update_group), (b_frame, b_new_update_group),
         (c_frame, c_new_update_group)] = state_info[3:]  # note 3:

        self.assertEqual([1, 1, 1], [a_frame, b_frame, c_frame])
        self.assertEqual(a_update_group, a_new_update_group)
        self.assertEqual(b_update_group, b_new_update_group)
        self.assertEqual(c_update_group, c_new_update_group)