def make_game(): """Builds and returns a Hello World game.""" return ascii_art.ascii_art_to_game( HELLO_ART, what_lies_beneath=' ', sprites={'1': ascii_art.Partial(SlidingSprite, 0), '2': ascii_art.Partial(SlidingSprite, 1), '3': ascii_art.Partial(SlidingSprite, 2), '4': ascii_art.Partial(SlidingSprite, 3)}, drapes={'@': RollingDrape}, z_order='12@34')
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..'])
def make_game(level): """Builds and returns a Scrolly Maze game for the selected level.""" # A helper object that helps us with Scrolly-related setup paperwork. scrolly_info = prefab_drapes.Scrolly.PatternInfo( MAZES_ART[level], STAR_ART, board_northwest_corner_mark='+', what_lies_beneath=MAZES_WHAT_LIES_BENEATH[level]) walls_kwargs = scrolly_info.kwargs('#') coins_kwargs = scrolly_info.kwargs('@') player_position = scrolly_info.virtual_position('P') patroller_a_position = scrolly_info.virtual_position('a') patroller_b_position = scrolly_info.virtual_position('b') patroller_c_position = scrolly_info.virtual_position('c') return ascii_art.ascii_art_to_game( STAR_ART, what_lies_beneath=' ', sprites={ 'P': ascii_art.Partial(PlayerSprite, player_position), 'a': ascii_art.Partial(PatrollerSprite, patroller_a_position), 'b': ascii_art.Partial(PatrollerSprite, patroller_b_position), 'c': ascii_art.Partial(PatrollerSprite, patroller_c_position)}, drapes={ '#': ascii_art.Partial(MazeDrape, **walls_kwargs), '@': ascii_art.Partial(CashDrape, **coins_kwargs)}, # The base Backdrop class will do for a backdrop that just sits there. # In accordance with best practices, the one egocentric MazeWalker (the # player) is in a separate and later update group from all of the # pycolab entities that control non-traversable characters. update_schedule=[['#'], ['a', 'b', 'c', 'P'], ['@']], z_order='abc@#P')
def testBasicWalking(self): """A `MazeWalker` can walk, but not through walls.""" # Not helpful in this test, since argument lists are long: # pylint: disable=g-long-lambda # Our test takes place in this world... art = [ '.......', '..abcd.', '.n e.', '.m P f.', '.l g.', '.kjih..', '.......' ] # Here we make the game. engine = ascii_art.ascii_art_to_game( art=art, what_lies_beneath=' ', # Only P is a Sprite, and it can't walk through any of the walls. sprites=dict(P=ascii_art.Partial(tt.TestMazeWalker, impassable='abcdefghijklmn'))) # Go ahead and start the game. Nothing should change on the game board; # P will stay put. engine.its_showtime() # Now we execute a sequence of motions, comparing the actual observations # against ASCII-art depictions of our expected observations, and also making # certain that the MazeWalker's motion action helper methods produce the # expected return values (None when a motion succeeds; a description of the # obstacle when a motion fails). self.assertMachinima( engine=engine, post_updates=dict( # This post-update callable for the Sprite is what checks the return # value for correctness. P=lambda actions, board, layers, backdrop, things, the_plot: (self.assertEqual(the_plot['walk_result_P'], the_plot[ 'machinima_args'][0]))), frames=[ # Head to the top of the room and try to walk through the wall. ( 'n', # After going in this direction... [ '.......', '..abcd.', '.n P e.', # ...we expect to see this board... '.m f.', '.l g.', '.kjih..', # ...and this return value from the motion '.......' ], None), # action helper methods. ('n', [ '.......', '..abcd.', '.n P e.', '.m f.', '.l g.', '.kjih..', '.......' ], 'b'), ('nw', [ '.......', '..abcd.', '.n P e.', '.m f.', '.l g.', '.kjih..', '.......' ], (' ', 'a', 'b')), ('ne', [ '.......', '..abcd.', '.n P e.', '.m f.', '.l g.', '.kjih..', '.......' ], ('b', 'c', ' ')), # Head to the top right corner of the room and try to escape. ('e', [ '.......', '..abcd.', '.n Pe.', '.m f.', '.l g.', '.kjih..', '.......' ], None), ('e', [ '.......', '..abcd.', '.n Pe.', '.m f.', '.l g.', '.kjih..', '.......' ], 'e'), ('nw', [ '.......', '..abcd.', '.n Pe.', '.m f.', '.l g.', '.kjih..', '.......' ], (' ', 'b', 'c')), ('ne', [ '.......', '..abcd.', '.n Pe.', '.m f.', '.l g.', '.kjih..', '.......' ], ('c', 'd', 'e')), ('se', [ '.......', '..abcd.', '.n Pe.', '.m f.', '.l g.', '.kjih..', '.......' ], ('e', 'f', ' ')), # Head to the bottom right corner of the room and try to escape. ('s', [ '.......', '..abcd.', '.n e.', '.m Pf.', '.l g.', '.kjih..', '.......' ], None), ('s', [ '.......', '..abcd.', '.n e.', '.m f.', '.l Pg.', '.kjih..', '.......' ], None), ('s', [ '.......', '..abcd.', '.n e.', '.m f.', '.l Pg.', '.kjih..', '.......' ], 'h'), ('ne', [ '.......', '..abcd.', '.n e.', '.m f.', '.l Pg.', '.kjih..', '.......' ], (' ', 'f', 'g')), ('se', [ '.......', '..abcd.', '.n e.', '.m f.', '.l Pg.', '.kjih..', '.......' ], ('g', '.', 'h')), ('sw', [ '.......', '..abcd.', '.n e.', '.m f.', '.l Pg.', '.kjih..', '.......' ], ('h', 'i', ' ')), # Head to the bottom left corner of the room and try to escape. ('w', [ '.......', '..abcd.', '.n e.', '.m f.', '.l P g.', '.kjih..', '.......' ], None), ('w', [ '.......', '..abcd.', '.n e.', '.m f.', '.lP g.', '.kjih..', '.......' ], None), ('w', [ '.......', '..abcd.', '.n e.', '.m f.', '.lP g.', '.kjih..', '.......' ], 'l'), ('se', [ '.......', '..abcd.', '.n e.', '.m f.', '.lP g.', '.kjih..', '.......' ], (' ', 'i', 'j')), ('sw', [ '.......', '..abcd.', '.n e.', '.m f.', '.lP g.', '.kjih..', '.......' ], ('j', 'k', 'l')), ('nw', [ '.......', '..abcd.', '.n e.', '.m f.', '.lP g.', '.kjih..', '.......' ], ('l', 'm', ' ')), # Head to the top left corner of the room and try to escape. ('n', [ '.......', '..abcd.', '.n e.', '.mP f.', '.l g.', '.kjih..', '.......' ], None), ('n', [ '.......', '..abcd.', '.nP e.', '.m f.', '.l g.', '.kjih..', '.......' ], None), ('n', [ '.......', '..abcd.', '.nP e.', '.m f.', '.l g.', '.kjih..', '.......' ], 'a'), ('sw', [ '.......', '..abcd.', '.nP e.', '.m f.', '.l g.', '.kjih..', '.......' ], (' ', 'm', 'n')), ('nw', [ '.......', '..abcd.', '.nP e.', '.m f.', '.l g.', '.kjih..', '.......' ], ('n', '.', 'a')), ('ne', [ '.......', '..abcd.', '.nP e.', '.m f.', '.l g.', '.kjih..', '.......' ], ('a', 'b', ' ')), # Make certain that diagonal moves work (so far, they've only been # tried in situations where they fail). ('se', [ '.......', '..abcd.', '.n e.', '.m P f.', '.l g.', '.kjih..', '.......' ], None), ('ne', [ '.......', '..abcd.', '.n Pe.', '.m f.', '.l g.', '.kjih..', '.......' ], None), ('sw', [ '.......', '..abcd.', '.n e.', '.m P f.', '.l g.', '.kjih..', '.......' ], None), ('nw', [ '.......', '..abcd.', '.nP e.', '.m f.', '.l g.', '.kjih..', '.......' ], None), ], )
def testConfinedToBoard(self): """A confined-to-board MazeWalker can't walk off the board.""" # Not helpful in this test, since argument lists are long: # pylint: disable=g-long-lambda # Our test takes place in this world... art = [' ', ' P ', ' '] # Here we make the game. engine = ascii_art.ascii_art_to_game( art=art, what_lies_beneath=' ', # P is a MazeWalker, but this time it's confined to the board. sprites=dict(P=ascii_art.Partial( tt.TestMazeWalker, impassable='', confined_to_board=True))) # Go ahead and start the game. Nothing should change on the game board; # P will stay put. engine.its_showtime() # We'll abbreviate the symbol that MazeWalkers use to describe the edge of # the board in motion action helper method return values. EDGE = prefab_sprites.MazeWalker.EDGE # pylint: disable=invalid-name # Now we execute a sequence of motions, comparing the actual observations # against ASCII-art depictions of our expected observations, and also making # certain that the MazeWalker's motion action helper methods produce the # expected return values (None when a motion succeeds; a description of the # obstacle when a motion fails). self.assertMachinima( engine=engine, post_updates=dict( # This post-update callable for the Sprite is what checks the return # value for correctness. P=lambda actions, board, layers, backdrop, things, the_plot: (self.assertEqual(the_plot['walk_result_P'], the_plot[ 'machinima_args'][0]))), frames=[ ( 'n', # After going in this direction... [ ' P ', # ...we expect to see this board and this... ' ', # ... ↙ return value from motion action helper methods. ' ' ], None), # Try to escape the board to the north, northwest, and northeast. ('n', [' P ', ' ', ' '], EDGE), ('nw', [' P ', ' ', ' '], (' ', EDGE, EDGE)), ('ne', [' P ', ' ', ' '], (EDGE, EDGE, ' ')), # Bolt for the southwest corner. ('sw', [' ', ' P ', ' '], None), ('sw', [' ', ' ', 'P '], None), ('sw', [' ', ' ', 'P '], (EDGE, EDGE, EDGE)), # Try the southeast corner. ('e', [' ', ' ', ' P '], None), ('e', [' ', ' ', ' P '], None), ('e', [' ', ' ', ' P '], None), ('e', [' ', ' ', ' P'], None), ('se', [' ', ' ', ' P'], (EDGE, EDGE, EDGE)), ], )
def testNotConfinedToBoard(self): """An ordinary MazeWalker disappears if it walks off the board.""" # Our test takes place in this world... art = [' ', ' P ', ' '] # Here we make the game. engine = ascii_art.ascii_art_to_game( art=art, what_lies_beneath=' ', # P is a MazeWalker. sprites=dict( P=ascii_art.Partial(tt.TestMazeWalker, impassable=''))) # Go ahead and start the game. Nothing should change on the game board; # P will stay put. engine.its_showtime() # Now we execute a sequence of motions, comparing the actual observations # against ASCII-art depictions of our expected observations, and also making # sure that the MazeWalker's true position and virtual position change in # the expected ways. First we define a post-update callable that the Sprite # uses to check its true and virtual positions against expectations... def check_positions(actions, board, layers, backdrop, things, the_plot): del actions, board, layers, backdrop # Unused. self.assertEqual(the_plot['machinima_args'][0], things['P'].position) self.assertEqual(the_plot['machinima_args'][1], things['P'].virtual_position) # ...and then we execute the sequence. self.assertMachinima( engine=engine, post_updates=dict(P=check_positions), frames=[ ( 'n', # After going in this direction... [ ' P ', # ...we expect to see this board,... ' ', # ... ↙ this true position, and... ' ' ], (0, 2), (0, 2)), # ... ← this virtual position. # Exit the board to the north. True position becomes 0, 0 and the # Sprite goes invisible there; virtual position carries on as if the # board extended infinitely in all directions. So, we walk around # outside of the board for a bit... ('n', [' ', ' ', ' '], (0, 0), (-1, 2)), ('e', [' ', ' ', ' '], (0, 0), (-1, 3)), ('e', [' ', ' ', ' '], (0, 0), (-1, 4)), # ...and then back onto the board... ('sw', [' P ', ' ', ' '], (0, 3), (0, 3)), ('sw', [' ', ' P ', ' '], (1, 2), (1, 2)), ('sw', [' ', ' ', ' P '], (2, 1), (2, 1)), # ...and off the board again... ('sw', [' ', ' ', ' '], (0, 0), (3, 0)), ('nw', [' ', ' ', ' '], (0, 0), (2, -1)), # ...and we're back again! ('e', [' ', ' ', 'P '], (2, 0), (2, 0)), ('e', [' ', ' ', ' P '], (2, 1), (2, 1)), ('e', [' ', ' ', ' P '], (2, 2), (2, 2)), ], )
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) ### 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)