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=' '), ]
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')
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
def make_croppers(self): return [ cropping.ScrollingCropper(rows=5, cols=5, to_track=['P'], scroll_margins=(None, None), pad_char=' ') ]
def make_croppers(level): return [ # The player view. cropping.ScrollingCropper(rows=10, cols=30, to_track=['P'], initial_offset=STARTER_OFFSET[level]), ]
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']), ]
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), ]
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])]
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))
def get_cropper(): return cropping.ScrollingCropper(rows=5, cols=5, to_track=PLAYER, pad_char=BACKGROUND, scroll_margins=(2, 2))
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 .'], ['. . .', '#. . ', '. . #', ' #. .', '#. . ']))], )
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..'])), ], )
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. '], [' . .', ' . .'])), ], )
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#'], [' ', '#####'])), ], )