コード例 #1
0
def make_croppers(level):
    """Builds and returns `ObservationCropper`s for the selected level.

  We make three croppers for each level: one centred on the player, one centred
  on one of the Patrollers (scary!), and one centred on a tantalising hoard of
  coins somewhere in the level (motivating!)

  Args:
    level: level to make `ObservationCropper`s for.

  Returns:
    a list of three `ObservationCropper`s.
  """
    return [
        # The player view.
        cropping.ScrollingCropper(rows=10,
                                  cols=30,
                                  to_track=['P'],
                                  initial_offset=STARTER_OFFSET[level]),
        # The patroller view.
        cropping.ScrollingCropper(rows=7,
                                  cols=10,
                                  to_track=['c'],
                                  pad_char=' ',
                                  scroll_margins=(None, 3)),
        # The teaser!
        cropping.FixedCropper(top_left_corner=TEASER_CORNER[level],
                              rows=12,
                              cols=20,
                              pad_char=' '),
    ]
コード例 #2
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')
コード例 #3
0
ファイル: grid_tasks.py プロジェクト: lampinen/homm_grids
    def __init__(self,
                 object_dict,
                 scroll_size=SCROLL_SIZE,
                 upsample_size=UPSAMPLE_SIZE,
                 agent_shape=AGENT_SHAPE,
                 scale=1.):

        cropper = cropping.ScrollingCropper(rows=scroll_size,
                                            cols=scroll_size,
                                            to_track=[AGENT_CHAR],
                                            pad_char=BG_CHAR,
                                            scroll_margins=(None, None))
        self.cropper = cropper
        self.upsample_size = upsample_size
        self.scroll_size = scroll_size
        self.scale = scale
        ones_square = np.ones([upsample_size, upsample_size], np.float32)
        agent_shape = self._render_plain_shape(agent_shape)
        self.decoder_dict = {
            ord(WALL_CHAR):
            ones_square[:, :, None] *
            np.array(COLOURS[WALL_CHAR])[None, None, :] * scale,
            ord(AGENT_CHAR):
            agent_shape[:, :, None] *
            np.array(COLOURS[AGENT_CHAR])[None, None, :] * scale,
        }
        self.bg_ord = ord(BG_CHAR)
        self.agent_ord = ord(AGENT_CHAR)
        for name, properties in object_dict.items():
            _, shape_name = name.split("_")
            raw_shape = self._render_plain_shape(shape_name)
            raw_color = np.array(properties["color"], np.float32)
            this_image = raw_shape[:, :, None] * raw_color[None, None, :]
            self.decoder_dict[ord(properties["char"])] = this_image * scale
コード例 #4
0
ファイル: mazeworld_env.py プロジェクト: echen9898/mmc
 def make_croppers(self):
     return [
         cropping.ScrollingCropper(rows=5,
                                   cols=5,
                                   to_track=['P'],
                                   scroll_margins=(None, None),
                                   pad_char=' ')
     ]
コード例 #5
0
def make_croppers(level):

    return [
        # The player view.
        cropping.ScrollingCropper(rows=10,
                                  cols=30,
                                  to_track=['P'],
                                  initial_offset=STARTER_OFFSET[level]),
    ]
コード例 #6
0
def make_croppers(level):
    """Builds and returns `ObservationCropper`s for the selected level.

  We make one cropper for each level: centred on the player. Room
  to add more if needed.

  Args:
    level: level to make `ObservationCropper`s for.

  Returns:
    a list of all the `ObservationCropper`s needed.
  """
    return [
        # The player view.
        cropping.ScrollingCropper(rows=5, cols=5, to_track=['P']),
    ]
コード例 #7
0
def make_croppers():
    """Builds and returns three `ObservationCropper`s for tennnn...nnnnis."""
    return [
        # Player 1 view.
        cropping.FixedCropper(top_left_corner=(0, 0), rows=10, cols=10),

        # The ball view.
        cropping.ScrollingCropper(rows=10,
                                  cols=31,
                                  to_track=['@'],
                                  scroll_margins=(0, None)),

        # Player 2 view.
        cropping.FixedCropper(top_left_corner=(0, len(MAZE_ART[0]) - 10),
                              rows=10,
                              cols=10),
    ]
コード例 #8
0
def make_croppers(level):

  if level == 0:
    return [
        # The player view.
        cropping.ScrollingCropper(rows=11, cols=17, to_track=['P'], initial_offset=STARTER_OFFSET[level])]
  elif level == 1:
    return [cropping.ScrollingCropper(rows=11, cols=17, to_track=['P'], initial_offset=STARTER_OFFSET[level])]
  elif level == 2:
    return [cropping.ScrollingCropper(rows=19, cols=45, to_track=['P'], initial_offset=STARTER_OFFSET[level])]
  elif level == 3:
    return [cropping.ScrollingCropper(rows=19, cols=45, to_track=['P'], initial_offset=STARTER_OFFSET[level])]
  elif level == 4:
    return [cropping.ScrollingCropper(rows=29, cols=99, to_track=['P'], initial_offset=STARTER_OFFSET[level])]
  elif level == 5:
    return [cropping.ScrollingCropper(rows=29, cols=99, to_track=['P'], initial_offset=STARTER_OFFSET[level])]  
コード例 #9
0
def get_scrolling_cropper(rows=9, cols=9, crop_pad_char=" "):
    return cropping.ScrollingCropper(rows=rows,
                                     cols=cols,
                                     to_track=[ballet_core.AGENT_CHAR],
                                     pad_char=crop_pad_char,
                                     scroll_margins=(None, None))
コード例 #10
0
ファイル: common.py プロジェクト: kangyongxin/GBMRcode
def get_cropper():
    return cropping.ScrollingCropper(rows=5,
                                     cols=5,
                                     to_track=PLAYER,
                                     pad_char=BACKGROUND,
                                     scroll_margins=(2, 2))
コード例 #11
0
    def testScrollingInitialOffset(self):
        """Initial offsets (for dramatic effect) produce expected crops."""

        # Our test takes place in this world.
        art = [
            '#######', '# . . #', '#. . .#', '# P . #', '#. . .#', '# . . #',
            '#######'
        ]

        # All croppers have options that interact with the initial_offset
        # parameter in various ways.
        croppers = [
            # The first cropper is an easy case. The offset moves the window a
            # little bit, but the tracked agent is still within the scroll margins
            # (or within one row/column of them).
            cropping.ScrollingCropper(rows=3,
                                      cols=5,
                                      to_track=['P'],
                                      scroll_margins=(1, 1),
                                      initial_offset=(0, -1)),
            # The second cropper shifts the window so that the tracked agent is
            # outside the scroll margins---far enough (i.e. beyond one row/column)
            # that the cropper cannot scroll the agent back into the margins.
            # BUT: saccade is True, so the cropper should just shift to put the
            # agent at the centre.
            cropping.ScrollingCropper(rows=3,
                                      cols=5,
                                      to_track=['P'],
                                      scroll_margins=(None, None),
                                      initial_offset=(-1, -2),
                                      saccade=True),
            # The third cropper is like the above, but saccade is False. There will
            # be no shifting the agent back to the centre.
            cropping.ScrollingCropper(rows=3,
                                      cols=5,
                                      to_track=['P'],
                                      scroll_margins=(None, None),
                                      initial_offset=(-1, -2),
                                      saccade=False),
            # This cropper would like to shift the window so that it overlaps the
            # left edge of the world. Luckily, it specifies a padding character.
            cropping.ScrollingCropper(rows=3,
                                      cols=5,
                                      to_track=['P'],
                                      scroll_margins=(1, 1),
                                      initial_offset=(0, 1),
                                      pad_char=' '),
            # This cropper doesn't, so the window will be confined to the board.
            cropping.ScrollingCropper(rows=3,
                                      cols=5,
                                      to_track=['P'],
                                      scroll_margins=(1, 1),
                                      initial_offset=(0, 1)),
        ]

        # In a fresh engine, execute a "stay" move and check for expected crops.
        # pylint: disable=bad-whitespace
        self.assertMachinima(
            engine=self.make_engine(art, croppers),
            croppers=croppers,
            frames=[('stay',
                     zip(['. . .', '#. . ', 'P . #', ' #. .', '#. . '],
                         [' P . ', '# P .', ' . .#', ' # P ', '# P .'],
                         ['. . .', '#. . ', '. . #', ' #. .', '#. . ']))],
        )
コード例 #12
0
    def testScrollingMargins(self):
        """Scrolling margins work, interacting with board edges as intended."""

        # Our test takes place in this world.
        art = [
            '.........', '. ; ; ; .', '.; , , ;.', '. , . , .', '.; .P. ;.',
            '. , . , .', '.; , , ;.', '. ; ; ; .', '.........'
        ]

        # Our six croppers are the Cartesian product of:
        #   margin is on { vertical edges; horizontal edges; both edges }
        # and
        #   the scrolling window { can; cannot } go beyond the edge of the board.
        # And to be clear, "no margin" means egocentric scrolling---in a sense, the
        # tightest possible margins.
        croppers = [
            cropping.ScrollingCropper(  # Margins on vertical edges,
                rows=5,
                cols=5,
                to_track=['P'],  # can overlap the board's edge.
                scroll_margins=(None, 1),
                pad_char=' '),
            cropping.ScrollingCropper(  # cannot overlap the board's edge.
                rows=5,
                cols=5,
                to_track=['P'],
                scroll_margins=(None, 1)),
            cropping.ScrollingCropper(  # Margins on horizontal edges,
                rows=5,
                cols=5,
                to_track=['P'],  # can overlap the board's edge.
                scroll_margins=(1, None),
                pad_char=' '),
            cropping.ScrollingCropper(  # cannot overlap the board's edge.
                rows=5,
                cols=5,
                to_track=['P'],
                scroll_margins=(1, None)),
            cropping.ScrollingCropper(  # Margins on both edges,
                rows=5,
                cols=5,
                to_track=['P'],  # can overlap the board's edge.
                scroll_margins=(1, 1),
                pad_char=' '),
            cropping.ScrollingCropper(  # cannot overlap the board's edge.
                rows=5,
                cols=5,
                to_track=['P'],
                scroll_margins=(1, 1)),
        ]

        # In a fresh engine, walk the Sprite westward and check for expected crops.
        # pylint: disable=bad-whitespace
        self.assertMachinima(
            engine=self.make_engine(art, croppers),
            croppers=croppers,
            frames=[
                ('w',
                 zip([' , , ', ' , , ', '; , ,', '; , ,', ' , , ', ' , , '],
                     [', . ,', ', . ,', ' , . ', ' , . ', ', . ,', ', . ,'],
                     [' P . ', ' P . ', '; P .', '; P .', ' P . ', ' P . '],
                     [', . ,', ', . ,', ' , . ', ' , . ', ', . ,', ', . ,'],
                     [' , , ', ' , , ', '; , ,', '; , ,', ' , , ', ' , , '])),
                ('w',
                 zip(['; , ,', '; , ,', '.; , ', '.; , ', '; , ,', '; , ,'],
                     [' , . ', ' , . ', '. , .', '. , .', ' , . ', ' , . '],
                     [';P. .', ';P. .', '.;P. ', '.;P. ', ';P. .', ';P. .'],
                     [' , . ', ' , . ', '. , .', '. , .', ' , . ', ' , . '],
                     ['; , ,', '; , ,', '.; , ', '.; , ', '; , ,', '; , ,'])),
                ('w',
                 zip(['.; , ', '.; , ', ' .; ,', '.; , ', '.; , ', '.; , '],
                     ['. , .', '. , .', ' . , ', '. , .', '. , .', '. , .'],
                     ['.P . ', '.P . ', ' .P .', '.P . ', '.P . ', '.P . '],
                     ['. , .', '. , .', ' . , ', '. , .', '. , .', '. , .'],
                     ['.; , ', '.; , ', ' .; ,', '.; , ', '.; , ', '.; , '])),
                ('w',
                 zip([' .; ,', '.; , ', '  .; ', '.; , ', ' .; ,', '.; , '],
                     [' . , ', '. , .', '  . ,', '. , .', ' . , ', '. , .'],
                     [' P; .', 'P; . ', '  P; ', 'P; . ', ' P; .', 'P; . '],
                     [' . , ', '. , .', '  . ,', '. , .', ' . , ', '. , .'],
                     [' .; ,', '.; , ', '  .; ', '.; , ', ' .; ,', '.; , '])),
            ],
        )

        # In a fresh engine, walk the Sprite eastward and check for expected crops.
        self.assertMachinima(
            engine=self.make_engine(art, croppers),
            croppers=croppers,
            frames=[
                ('e',
                 zip([' , , ', ' , , ', ', , ;', ', , ;', ' , , ', ' , , '],
                     [', . ,', ', . ,', ' . , ', ' . , ', ', . ,', ', . ,'],
                     [' . P ', ' . P ', '. P ;', '. P ;', ' . P ', ' . P '],
                     [', . ,', ', . ,', ' . , ', ' . , ', ', . ,', ', . ,'],
                     [' , , ', ' , , ', ', , ;', ', , ;', ' , , ', ' , , '])),
                ('e',
                 zip([', , ;', ', , ;', ' , ;.', ' , ;.', ', , ;', ', , ;'],
                     [' . , ', ' . , ', '. , .', '. , .', ' . , ', ' . , '],
                     ['. .P;', '. .P;', ' .P;.', ' .P;.', '. .P;', '. .P;'],
                     [' . , ', ' . , ', '. , .', '. , .', ' . , ', ' . , '],
                     [', , ;', ', , ;', ' , ;.', ' , ;.', ', , ;', ', , ;'])),
                ('e',
                 zip([' , ;.', ' , ;.', ', ;. ', ' , ;.', ' , ;.', ' , ;.'],
                     ['. , .', '. , .', ' , . ', '. , .', '. , .', '. , .'],
                     [' . P.', ' . P.', '. P. ', ' . P.', ' . P.', ' . P.'],
                     ['. , .', '. , .', ' , . ', '. , .', '. , .', '. , .'],
                     [' , ;.', ' , ;.', ', ;. ', ' , ;.', ' , ;.', ' , ;.'])),
                ('e',
                 zip([', ;. ', ' , ;.', ' ;.  ', ' , ;.', ', ;. ', ' , ;.'],
                     [' , . ', '. , .', ', .  ', '. , .', ' , . ', '. , .'],
                     ['. ;P ', ' . ;P', ' ;P  ', ' . ;P', '. ;P ', ' . ;P'],
                     [' , . ', '. , .', ', .  ', '. , .', ' , . ', '. , .'],
                     [', ;. ', ' , ;.', ' ;.  ', ' , ;.', ', ;. ', ' , ;.'])),
            ],
        )

        # In a fresh engine, walk the Sprite northward and check for expected crops.
        self.assertMachinima(
            engine=self.make_engine(art, croppers),
            croppers=croppers,
            frames=[
                ('n',
                 zip(['; ; ;', '; ; ;', ' , , ', ' , , ', ' , , ', ' , , '],
                     [' , , ', ' , , ', ', P ,', ', P ,', ', P ,', ', P ,'],
                     [', P ,', ', P ,', ' . . ', ' . . ', ' . . ', ' . . '],
                     [' . . ', ' . . ', ', . ,', ', . ,', ', . ,', ', . ,'],
                     [', . ,', ', . ,', ' , , ', ' , , ', ' , , ', ' , , '])),
                ('n',
                 zip(['.....', '.....', '; ; ;', '; ; ;', '; ; ;', '; ; ;'],
                     ['; ; ;', '; ; ;', ' ,P, ', ' ,P, ', ' ,P, ', ' ,P, '],
                     [' ,P, ', ' ,P, ', ', . ,', ', . ,', ', . ,', ', . ,'],
                     [', . ,', ', . ,', ' . . ', ' . . ', ' . . ', ' . . '],
                     [' . . ', ' . . ', ', . ,', ', . ,', ', . ,', ', . ,'])),
                ('n',
                 zip(['     ', '.....', '.....', '.....', '.....', '.....'],
                     ['.....', '; P ;', '; P ;', '; P ;', '; P ;', '; P ;'],
                     ['; P ;', ' , , ', ' , , ', ' , , ', ' , , ', ' , , '],
                     [' , , ', ', . ,', ', . ,', ', . ,', ', . ,', ', . ,'],
                     [', . ,', ' . . ', ' . . ', ' . . ', ' . . ', ' . . '])),
                ('n',
                 zip(['     ', '..P..', '     ', '..P..', '     ', '..P..'],
                     ['     ', '; ; ;', '..P..', '; ; ;', '..P..', '; ; ;'],
                     ['..P..', ' , , ', '; ; ;', ' , , ', '; ; ;', ' , , '],
                     ['; ; ;', ', . ,', ' , , ', ', . ,', ' , , ', ', . ,'],
                     [' , , ', ' . . ', ', . ,', ' . . ', ', . ,', ' . . '])),
            ],
        )

        # In a fresh engine, walk the Sprite southward and check for expected crops.
        self.assertMachinima(
            engine=self.make_engine(art, croppers),
            croppers=croppers,
            frames=[
                ('s',
                 zip([', . ,', ', . ,', ' , , ', ' , , ', ' , , ', ' , , '],
                     [' . . ', ' . . ', ', . ,', ', . ,', ', . ,', ', . ,'],
                     [', P ,', ', P ,', ' . . ', ' . . ', ' . . ', ' . . '],
                     [' , , ', ' , , ', ', P ,', ', P ,', ', P ,', ', P ,'],
                     ['; ; ;', '; ; ;', ' , , ', ' , , ', ' , , ', ' , , '])),
                ('s',
                 zip([' . . ', ' . . ', ', . ,', ', . ,', ', . ,', ', . ,'],
                     [', . ,', ', . ,', ' . . ', ' . . ', ' . . ', ' . . '],
                     [' ,P, ', ' ,P, ', ', . ,', ', . ,', ', . ,', ', . ,'],
                     ['; ; ;', '; ; ;', ' ,P, ', ' ,P, ', ' ,P, ', ' ,P, '],
                     ['.....', '.....', '; ; ;', '; ; ;', '; ; ;', '; ; ;'])),
                ('s',
                 zip([', . ,', ' . . ', ' . . ', ' . . ', ' . . ', ' . . '],
                     [' , , ', ', . ,', ', . ,', ', . ,', ', . ,', ', . ,'],
                     ['; P ;', ' , , ', ' , , ', ' , , ', ' , , ', ' , , '],
                     ['.....', '; P ;', '; P ;', '; P ;', '; P ;', '; P ;'],
                     ['     ', '.....', '.....', '.....', '.....', '.....'])),
                ('s',
                 zip([' , , ', ' . . ', ', . ,', ' . . ', ', . ,', ' . . '],
                     ['; ; ;', ', . ,', ' , , ', ', . ,', ' , , ', ', . ,'],
                     ['..P..', ' , , ', '; ; ;', ' , , ', '; ; ;', ' , , '],
                     ['     ', '; ; ;', '..P..', '; ; ;', '..P..', '; ; ;'],
                     ['     ', '..P..', '     ', '..P..', '     ', '..P..'])),
            ],
        )
コード例 #13
0
    def testScrollingSaccade(self):
        """`ScrollingCropper` can "saccade" correctly between scroll targets."""

        # Our test takes place in this world.
        art = [
            ' . . . ',
            '. . . .',
            ' . . . ',
            '. .P. .',  # The agent...
            ' . .%% ',
            '. .%%%.',  # ...and a blobby drape.
            ' . %%. '
        ]

        # We have two kinds of egocentric croppers: one that can saccade between
        # scroll targets (i.e. if its scroll target moves more than one pixel in any
        # direction, it jumps to follow it) and one that does not. In this test, we
        # name two scrolling targets: the highest priority is the Sprite 'P', and
        # the Drape '%' is the lowest priority. Both can crop a region outside of
        # the game board.
        croppers = [
            cropping.ScrollingCropper(rows=3,
                                      cols=5,
                                      to_track=['P', '%'],
                                      scroll_margins=(None, None),
                                      pad_char=' ',
                                      saccade=True),
            cropping.ScrollingCropper(rows=3,
                                      cols=5,
                                      to_track=['P', '%'],
                                      scroll_margins=(None, None),
                                      pad_char=' ',
                                      saccade=False),
        ]

        # In a fresh engine, walk the Sprite around; we check for expected crops.
        # pylint: disable=bad-whitespace
        self.assertMachinima(
            engine=self.make_engine(art, croppers),
            croppers=croppers,
            frames=[
                # The first three steps to the west proceed normally.
                ('w',
                 zip([' . . ', ' . . '], ['. P .', '. P .'],
                     [' . .%', ' . .%'])),
                ('w',
                 zip(['  . .', '  . .'], [' .P. ', ' .P. '],
                     ['  . .', '  . .'])),
                (
                    'w',
                    zip(
                        ['   . ', '   . '
                         ],  # So far, so normal. We're now at the
                        ['  P .', '  P .'],  # western edge of the board.
                        ['   . ', '   . '])),

                # With this step northwest, the Sprite will "leave the board" and
                # become invisible. The cropper that can saccade will jump straight
                # to the second-priority target, while the cropper that can't
                # saccade will wait patiently in place for a scroll target to drift
                # back into the centre of its window.
                ('nw',
                 zip([' .%% ', '   . '], ['.%%%.', '  . .'],
                     [' %%. ', '   . '])),

                # Bringing the Sprite back in two rows above the place where it
                # exited will snap the saccading cropper back into place. But it's
                # still too far away for the non-saccading cropper.
                ('ne',
                 zip(['   . ', '   . '], ['  P .', '  . .'],
                     ['   . ', '   . '])),

                # But if the Sprite drops one row, it's within one step---scrolling
                # distance---of the place where the non-saccading cropper was
                # waiting. That's close enough for it to "lock on"!
                ('s',
                 zip(['  . .', '  . .'], ['  P. ', '  P. '],
                     ['  . .', '  . .'])),
            ],
        )
コード例 #14
0
    def testEgocentricScrolling(self):
        """Basic egocentric scrolling works as advertised."""

        # Our test takes place in this world.
        art = [
            '#######', '# . . #', '#. . .#', '# .P. #', '#. . .#', '# . . #',
            '#######'
        ]

        # We have two types of egocentric croppers. The first is allowed to crop a
        # region outside the board, while the second is not.
        croppers = [
            # We have two types of egocentric croppers. This one is allowed to have
            # its cropping region extend outside of the game board.
            cropping.ScrollingCropper(rows=5,
                                      cols=5,
                                      to_track=['P'],
                                      scroll_margins=(None, None),
                                      pad_char=' '),
            # This one is not allowed to do that.
            cropping.ScrollingCropper(rows=5,
                                      cols=5,
                                      to_track=['P'],
                                      scroll_margins=(None, None)),
        ]

        # In a fresh engine, walk northwest and check for expected crops.
        # pylint: disable=bad-whitespace
        self.assertMachinima(
            engine=self.make_engine(art, croppers),
            croppers=croppers,
            frames=[
                (
                    'nw',  # The action, and cropped observations below.
                    zip(['#####', '#####'], ['# . .', '# . .'],
                        ['#.P. ', '#.P. '], ['# . .', '# . .'],
                        ['#. . ', '#. . '])),
                ('nw',
                 zip(['     ', '#####'], [' ####', '#P. .'],
                     [' #P. ', '#. . '], [' #. .', '# . .'],
                     [' # . ', '#. . '])),
            ],
        )
        # pylint: enable=bad-whitespace

        # In a fresh engine, walk southeast and check for expected crops.
        # pylint: disable=bad-whitespace
        self.assertMachinima(
            engine=self.make_engine(art, croppers),
            croppers=croppers,
            frames=[
                ('se',
                 zip([' . .#', ' . .#'], ['. . #', '. . #'],
                     [' .P.#', ' .P.#'], ['. . #', '. . #'],
                     ['#####', '#####'])),
                ('se',
                 zip([' . # ', ' . .#'], ['. .# ', '. . #'],
                     [' .P# ', ' . .#'], ['#### ', '. .P#'],
                     ['     ', '#####'])),
            ],
        )