def make_game(level): """Builds and returns a Better Scrolly Maze game for the selected level.""" maze_ascii = MAZES_ART[level] for row in range(25, 38): if 'c' in maze_ascii[row]: maze_ascii[row] = maze_ascii[row].replace('c', ' ', 1) new_coord = random.sample(ROOMS[4], 1)[0] maze_ascii[new_coord[0]] = maze_ascii[new_coord[0]][:new_coord[1]] + 'c' + maze_ascii[new_coord[0]][new_coord[1]+1:] return ascii_art.ascii_art_to_game( maze_ascii, what_lies_beneath=' ', sprites={ 'P': PlayerSprite, 'a': MoveableObject, 'b': WhiteNoiseObject, 'c': FixedObject, 'd': BrownianObject}, update_schedule=['P', 'a', 'b', 'c', 'd'], z_order='abcdP')
def make_game(level): """Builds and returns a Better Scrolly Maze game for the selected level.""" maze_ascii = MAZES_ART[level] # change location of fixed object in any of the rooms new_room = random.randint(1, 3) for row in range(1, 17): if 'a' in maze_ascii[row]: maze_ascii[row] = maze_ascii[row].replace('a', ' ', 1) new_coord = random.sample(ROOMS[new_room], 1)[0] maze_ascii[new_coord[0]] = maze_ascii[new_coord[0]][:new_coord[1]] + 'a' + maze_ascii[new_coord[0]][new_coord[1]+1:] return ascii_art.ascii_art_to_game( maze_ascii, what_lies_beneath=' ', sprites={ 'P': PlayerSprite, 'a': FixedObject, 'b': WhiteNoiseObject}, update_schedule=['P', 'a', 'b'], z_order='abP')
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]) #scrolly_info = prefab_drapes.Scrolly.PatternInfo(MAZES_ART[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') patroller_d_position = scrolly_info.virtual_position('d') return ascii_art.ascii_art_to_game( art=MAZES_ART[3], 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), 'd': ascii_art.Partial(PatrollerSprite, patroller_d_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', 'd', 'P']] #z_order='abcd@#P' )
def _make_explore_phase(self): # Keep only one key and one player position. explore_grid = common.keep_n_characters_in_grid( EXPLORE_GRID, common.KEY, 1) explore_grid = common.keep_n_characters_in_grid( explore_grid, common.PLAYER, 1) return ascii_art.ascii_art_to_game( art=explore_grid, what_lies_beneath=' ', sprites={ common.PLAYER: PlayerSprite, common.KEY: KeySprite, common.INDICATOR: ascii_art.Partial(objects.IndicatorObjectSprite, char_to_track=common.KEY, override_position=(0, 5)), common.TIMER: ascii_art.Partial(common.TimerSprite, self._max_frames['explore']), }, update_schedule=[ common.PLAYER, common.KEY, common.INDICATOR, common.TIMER], z_order=[common.KEY, common.INDICATOR, common.PLAYER, common.TIMER], )
def make_game(self): """Builds a trap tube game. Returns: pycolab.Engine """ config = self._make_trap_tube_config() sprites = {AGENT: ascii_art.Partial(AgentSprite, SYMBOLIC_OBJECTS)} drapes = { FOOD: ascii_art.Partial(FoodDrape, config.food_position), TRAP: TrapDrape, EXIT: ExitDrape, TUBE1: Tube1Drape, TUBE2: Tube2Drape, TOOL: ascii_art.Partial(ToolDrape, config.tool_position, tool_size=config.tool_size, tool_direction=config.tool_direction), TASK: TaskDrape } update_schedule = [[FOOD], [TOOL], [EXIT], [AGENT], [TUBE1, TUBE2], [TRAP], [TASK]] z_order = [TASK, TRAP, EXIT, TUBE1, TUBE2, FOOD, TOOL, AGENT] game = ascii_art.ascii_art_to_game(config.art, ' ', sprites, drapes, update_schedule=update_schedule, z_order=z_order, occlusion_in_layers=False) return game
def make_game(level, reward_config): """Builds and returns a Better Scrolly Maze game for the selected level.""" maze_ascii = MAZES_ART[level] # change location of fixed object in the top room for row in range(1, 5): if 'a' in maze_ascii[row]: maze_ascii[row] = maze_ascii[row].replace('a', ' ', 1) new_coord = random.sample(ROOMS[2], 1)[0] maze_ascii[new_coord[0]] = maze_ascii[new_coord[ 0]][:new_coord[1]] + 'a' + maze_ascii[new_coord[0]][new_coord[1] + 1:] return ascii_art.ascii_art_to_game( maze_ascii, what_lies_beneath=' ', sprites={ 'P': PlayerSprite, 'a': ascii_art.Partial(FixedObject, reward=reward_config['a']), 'b': ascii_art.Partial(FixedObject, reward=reward_config['b']), }, update_schedule=['P', 'a', 'b'], z_order='abP')
def build_engine(occlusion_in_layers): # Our test concerns renderings of this game world. art = ['..', '..'] # Here we make the game. The sprite `a` will cover a Drape element `b` , # which covers another Sprite `c`. If `occlusion_in_layers` is False, we # should still be able to see them in the layers, otherwise we should not. # In the flat `board`, occlusion stil occurs regardless and we should only # see those entities with higher z-order. engine = ascii_art.ascii_art_to_game( art=art, what_lies_beneath='.', # Note: since a and c do not appear in the game art, these sprites # are placed in the top-left corner (0, 0). sprites=dict(a=ascii_art.Partial(tt.TestMazeWalker, impassable=''), c=ascii_art.Partial(tt.TestMazeWalker, impassable='')), drapes=dict(b=FullOnDrape), occlusion_in_layers=occlusion_in_layers, z_order='abc') return engine
def _make_explore_phase(self, target_char): # Keep only one coloured position and one player position. grid = common.keep_n_characters_in_grid(EXPLORE_GRID, 'p', 1, common.BORDER) grid = common.keep_n_characters_in_grid(grid, 'p', 0, target_char) grid = common.keep_n_characters_in_grid(grid, common.PLAYER, 1) return ascii_art.ascii_art_to_game( grid, what_lies_beneath=' ', sprites={ common.PLAYER: ascii_art.Partial( common.PlayerSprite, impassable=common.BORDER + target_char), target_char: objects.ObjectSprite, common.TIMER: ascii_art.Partial(common.TimerSprite, self._max_frames['explore']), }, update_schedule=[common.PLAYER, target_char, common.TIMER], z_order=[target_char, common.PLAYER, common.TIMER], )
def make_pycolab_game(): sprites = {} sprites[PLAYER_CHARACTER] = PlayerSprite for box_char in box_characters_in_game: sprites[box_char] = BoxSprite drapes = {} drapes[JUDGE_CHARACTER] = JudgeDrape drapes[FILLED_CHARACTER] = FilledDrape for char in ALL_BUCKET_CHARACTERS: drapes[char] = BucketDrape update_schedule = [] update_schedule.append(box_characters_in_game) update_schedule.append(ALL_BUCKET_CHARACTERS) update_schedule.append( [PLAYER_CHARACTER, FILLED_CHARACTER, JUDGE_CHARACTER]) return ascii_art.ascii_art_to_game( art, what_lies_beneath=BACKGROUND_CHARACTER, sprites=sprites, drapes=drapes, update_schedule=update_schedule)
def make_game(level, reward_config): """Builds and returns a Better Scrolly Maze game for the selected level.""" maze_ascii = MAZES_ART[0] # change location of fixed object along row 4 maze_ascii[4] = maze_ascii[4].replace('a', ' ', 1) new_col = np.random.randint(8, 33) maze_ascii[4] = maze_ascii[4][:new_col] + 'a' + maze_ascii[4][new_col + 1:] return ascii_art.ascii_art_to_game(maze_ascii, what_lies_beneath=' ', sprites={ 'P': ascii_art.Partial( PlayerSprite, enemy_r=reward_config['b'], obj_r=reward_config['a']), 'a': FixedObject, 'b': BouncingObject }, update_schedule=['P', 'a', 'b'], z_order='abP')
def make_game(self): """Builds and returns a four-rooms game with Pursuader and Evader.""" return ascii_art.ascii_art_to_game( GAME_ART, what_lies_beneath=' ', sprites={'P': PursuerSprite, 'E': EvaderSprite}, update_schedule=[['E'], ['P']])
def make_BoyanChain(art): return ascii_art.ascii_art_to_game(art, what_lies_beneath=' ', sprites={'P': PlayerSprite_BoyanChain})
def _generate_random_game(rand, grid_size, solution_length, num_forward, num_backward, branch_length, max_num_steps): """Generate game proceduraly; aborts if `MAX_PLACEMENT_TRIES` is reached.""" # Sample new problem. solution_length, locks_keys = _sample_keys_locks_long(rand, solution_length, num_forward, num_backward, branch_length) # By randomizing the list of keys and locks we use all the possible colors. key_lock_ids = list(zip(KEYS, LOCKS)) rand.shuffle(key_lock_ids) full_map_size = grid_size + WALL_WIDTH * 2 art = [ [BACKGROUND for i in range(full_map_size)] for _ in range(full_map_size) ] art = np.array(art) art[:WALL_WIDTH, :] = BORDER art[-WALL_WIDTH:, :] = BORDER art[:, :WALL_WIDTH] = BORDER art[:, -WALL_WIDTH:] = BORDER drapes = {} distractors = [] placement_tries = 0 # Place items necessary for the sampled problem for i, (l, k) in enumerate(locks_keys): is_distractor = False if i > solution_length: is_distractor = True placed = False while not placed: if placement_tries > MAX_PLACEMENT_TRIES: return False x = rand.randint(0, grid_size - 3) + WALL_WIDTH y = rand.randint(1, grid_size - 1) + WALL_WIDTH if _check_spacing(art, x, y): placed = True # Check if box contains the gem if k == -1: art[y][x] = GEM drapes[GEM] = ascii_art.Partial(GemDrape, x=x, y=y) else: key = key_lock_ids[k - 1][0] art[y][x] = key drapes[key] = ascii_art.Partial(KeyDrape, x=x, y=y) # Check if box has a lock if l != 0: lock = key_lock_ids[l - 1][1] art[y][x + 1] = lock drapes[lock] = ascii_art.Partial(LockDrape, x=x + 1, y=y) if is_distractor: distractors.append((x + 1, y)) else: placement_tries += 1 # Place player placed = False while not placed: if placement_tries > MAX_PLACEMENT_TRIES: return False x = rand.randint(0, grid_size - 1) + WALL_WIDTH y = rand.randint(1, grid_size - 1) + WALL_WIDTH if art[y][x] == BACKGROUND: sprites = { PLAYER: ascii_art.Partial(PlayerSprite, grid_size, x, y, distractors, max_num_steps) } placed = True art[y][x] = PLAYER else: placement_tries += 1 order = sorted(drapes.keys()) update_schedule = [PLAYER] + order z_order = order + [PLAYER] art_as_list_of_strings = [] for art_ in art: art_as_list_of_strings.append(''.join(art_)) art = art_as_list_of_strings art = [''.join(a) for a in art] game = ascii_art.ascii_art_to_game( art=art, what_lies_beneath=BACKGROUND, sprites=sprites, drapes=drapes, update_schedule=update_schedule, z_order=z_order) return game
def make_game(): """Builds and returns a four-rooms game.""" return ascii_art.ascii_art_to_game(GAME_ART, what_lies_beneath=' ', sprites={'P': PlayerSprite})
def _make_game(self, art): """Make a game with one _DieRightward sprite. Valid chars are [P.].""" return ascii_art.ascii_art_to_game(art, what_lies_beneath='.', sprites={'P': self._DieRightward})
def make_game(): """Builds and returns a Fluvial Natation game.""" return ascii_art.ascii_art_to_game(GAME_ART, what_lies_beneath=' ', sprites={'P': PlayerSprite})
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 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~~~', '#####']), ], )
def make_game(): """Builds and returns a blocking_maze_game.""" return ascii_art.ascii_art_to_game(GAME_ART, what_lies_beneath=' ', sprites={'!': PlayerSprite})
def make_game(level, reward_config): """Builds and returns a Better Scrolly Maze game for the selected level.""" maze_ascii = MAZES_ART[level] # change location of fixed object in all the rooms for row in range(4, 37): if 'a' in maze_ascii[row]: maze_ascii[row] = maze_ascii[row].replace('a', ' ', 1) new_coord = random.sample(ROOMS[1], 1)[0] maze_ascii[new_coord[0]] = maze_ascii[ new_coord[0]][:new_coord[1]] + 'a' + maze_ascii[ new_coord[0]][new_coord[1] + 1:] if 'b' in maze_ascii[row]: maze_ascii[row] = maze_ascii[row].replace('b', ' ', 1) new_coord = random.sample(ROOMS[2], 1)[0] maze_ascii[new_coord[0]] = maze_ascii[ new_coord[0]][:new_coord[1]] + 'b' + maze_ascii[ new_coord[0]][new_coord[1] + 1:] if 'c' in maze_ascii[row]: maze_ascii[row] = maze_ascii[row].replace('c', ' ', 1) new_coord = random.sample(ROOMS[3], 1)[0] maze_ascii[new_coord[0]] = maze_ascii[ new_coord[0]][:new_coord[1]] + 'c' + maze_ascii[ new_coord[0]][new_coord[1] + 1:] if 'd' in maze_ascii[row]: maze_ascii[row] = maze_ascii[row].replace('d', ' ', 1) new_coord = random.sample(ROOMS[4], 1)[0] maze_ascii[new_coord[0]] = maze_ascii[ new_coord[0]][:new_coord[1]] + 'd' + maze_ascii[ new_coord[0]][new_coord[1] + 1:] if 'e' in maze_ascii[row]: maze_ascii[row] = maze_ascii[row].replace('e', ' ', 1) new_coord = random.sample(ROOMS[5], 1)[0] maze_ascii[new_coord[0]] = maze_ascii[ new_coord[0]][:new_coord[1]] + 'e' + maze_ascii[ new_coord[0]][new_coord[1] + 1:] if 'f' in maze_ascii[row]: maze_ascii[row] = maze_ascii[row].replace('f', ' ', 1) new_coord = random.sample(ROOMS[6], 1)[0] maze_ascii[new_coord[0]] = maze_ascii[ new_coord[0]][:new_coord[1]] + 'f' + maze_ascii[ new_coord[0]][new_coord[1] + 1:] if 'g' in maze_ascii[row]: maze_ascii[row] = maze_ascii[row].replace('g', ' ', 1) new_coord = random.sample(ROOMS[7], 1)[0] maze_ascii[new_coord[0]] = maze_ascii[ new_coord[0]][:new_coord[1]] + 'g' + maze_ascii[ new_coord[0]][new_coord[1] + 1:] if 'h' in maze_ascii[row]: maze_ascii[row] = maze_ascii[row].replace('h', ' ', 1) new_coord = random.sample(ROOMS[8], 1)[0] maze_ascii[new_coord[0]] = maze_ascii[ new_coord[0]][:new_coord[1]] + 'h' + maze_ascii[ new_coord[0]][new_coord[1] + 1:] a, b, c, d, e, f, g, h = reward_config.values() return ascii_art.ascii_art_to_game( maze_ascii, what_lies_beneath=' ', sprites={ 'P': ascii_art.Partial(PlayerSprite, a=a, b=b, c=c, d=d, e=e, f=f, g=g, h=h), 'a': FixedObject, 'b': FixedObject, 'c': FixedObject, 'd': FixedObject, 'e': FixedObject, 'f': FixedObject, 'g': FixedObject, 'h': FixedObject }, update_schedule=['P', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'], z_order='abcdefghP')
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 make_game( width: int = defaults.WIDTH, height: int = defaults.HEIGHT, max_rooms: int = defaults.MAX_ROOMS, seed: Optional[int] = defaults.SEED, slippery_coefficient: float = defaults.SLIPPERY_COEFFICIENT, default_reward: float = defaults.DEFAULT_REWARD, goal_reward: float = defaults.GOAL_REWARD, catastrophe_reward: float = defaults.CATASTROPHE_REWARD, ) -> Engine: """Builds a gridworld `pycolab` game. Args: Returns: A `pycolab` game. """ maze = labmaze.RandomMaze( width=width, height=height, max_rooms=max_rooms, random_seed=seed, spawns_per_room=1, spawn_token="P", objects_per_room=1, object_token="G", ) # Keep only one agent position. agent_positions = np.asarray(np.where(maze.entity_layer == "P")) I_p = np.random.choice(agent_positions.shape[-1]) maze.entity_layer[maze.entity_layer == "P"] = " " maze.entity_layer[tuple(agent_positions[:, I_p])] = "P" # Keep only one goal. goal_positions = np.asarray(np.where(maze.entity_layer == "G")) I_g, I_c = np.random.choice(goal_positions.shape[-1], size=2, replace=False) maze.entity_layer[maze.entity_layer == "G"] = " " maze.entity_layer[tuple(goal_positions[:, I_g])] = "G" maze.entity_layer[tuple(goal_positions[:, I_c])] = "C" art = str(maze.entity_layer).split("\n")[:-1] sprites = { "P": ascii_art.Partial( AgentSprite, default_reward=default_reward, slippery_coefficient=slippery_coefficient, seed=seed, ) } drapes = { "G": ascii_art.Partial( BoxDrape, reward=goal_reward, terminal=True, ), "C": ascii_art.Partial( BoxDrape, reward=catastrophe_reward, terminal=True, ) } return ascii_art.ascii_art_to_game( art, what_lies_beneath=" ", sprites=sprites, drapes=drapes, )
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 make_game(): """Builds and returns a cliff-walk game.""" return ascii_art.ascii_art_to_game(GAME_ART, what_lies_beneath='.', sprites={'P': PlayerSprite})
def make_maze(maze): """Builds and returns a blocking maze gridworld game.""" return ascii_art.ascii_art_to_game(maze, what_lies_beneath=' ', sprites={'P': PlayerSprite})
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)
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)
def testRendering(self): """Test various rendering utilities.""" # This helper will allow us to compare numpy bool_ arrays with "art" drawn # as lists of '0' and '1' characters. def assertMask(actual_mask, mask_art, err_msg=''): # pylint: disable=invalid-name np.testing.assert_array_equal( actual_mask, np.array([list(row) for row in mask_art]).astype(bool), err_msg) # Our test concerns renderings of this game world. art = ['..H..H..o..', '..HHHH..i..', '..H..H..i..'] # Here we make the game. Note specification of Q, an empty Drape. engine = ascii_art.ascii_art_to_game(art=art, what_lies_beneath='.', drapes=dict(Q=tt.TestDrape)) ### GAME ITERATION 0. We just run it to get an observation. observation, unused_reward, unused_discount = engine.its_showtime() ### Evaluate the observation's binary feature masks. # The observation's layer member should have an entry for all characters # that could be on the board, including ones for invisible Drapes. self.assertEqual(sorted(observation.layers.keys()), sorted(list('.HioQ'))) # Check that all the layer masks have the right contents. assertMask(observation.layers['.'], ['11011011011', '11000011011', '11011011011']) assertMask(observation.layers['H'], ['00100100000', '00111100000', '00100100000']) assertMask(observation.layers['i'], ['00000000000', '00000000100', '00000000100']) assertMask(observation.layers['o'], ['00000000100', '00000000000', '00000000000']) assertMask(observation.layers['Q'], ['00000000000', '00000000000', '00000000000']) ### Test correct operation of ObservationCharacterRepainter. repainter = rendering.ObservationCharacterRepainter( dict(H='J', i='J', Q='M')) repainted = repainter(observation) # Check that the repainted board looks correct. self.assertBoard(repainted.board, ['..J..J..o..', '..JJJJ..J..', '..J..J..J..']) # The repainted board should have these binary feature masks: self.assertEqual(sorted(repainted.layers.keys()), sorted(list('.JoM'))) # The binary feature masks should have these contents: assertMask(repainted.layers['.'], ['11011011011', '11000011011', '11011011011']) assertMask(repainted.layers['J'], ['00100100000', '00111100100', '00100100100']) assertMask(repainted.layers['o'], ['00000000100', '00000000000', '00000000000']) assertMask(repainted.layers['M'], ['00000000000', '00000000000', '00000000000']) ### Test correct operation of ObservationToArray for 2-D and 3-D arrays. # For the 2-D conversion, we'll do our own "homebrew" repainter, but just # for the Observation.board representation. Recall that the board member of # an Observation is a 2-D array of uint8s. converter = rendering.ObservationToArray( { '.': ord(' '), 'J': ord('#'), 'o': ord('*'), 'M': ord('?') }, dtype=np.uint8) converted = converter(repainted) self.assertBoard(converted, [' # # * ', ' #### # ', ' # # # ']) # Test that layer permutation happens correctly for the 2-D case. converter = rendering.ObservationToArray( { '.': ord(' '), 'J': ord('#'), 'o': ord('*'), 'M': ord('?') }, dtype=np.uint8, permute=(1, 0)) converted = converter(repainted) self.assertBoard(converted, [ ' ', ' ', '###', ' # ', ' # ', '###', ' ', ' ', '*##', ' ', ' ' ]) # For the 3-D conversion, we'll create a 3-D feature array that's a lot like # our feature masks. converter = rendering.ObservationToArray( { '.': (1, 0, 0, 0), 'J': (0, 1, 0, 0), 'o': (0, 0, 1, 0), 'M': (0, 0, 0, 1) }, dtype=bool) converted = converter(repainted) self.assertEqual(converted.shape, (4, 3, 11)) assertMask(converted[0, :], ['11011011011', '11000011011', '11011011011']) assertMask(converted[1, :], ['00100100000', '00111100100', '00100100100']) assertMask(converted[2, :], ['00000000100', '00000000000', '00000000000']) assertMask(converted[3, :], ['00000000000', '00000000000', '00000000000']) # And another layer permutation test for the 3-D case. converter = rendering.ObservationToArray( { '.': (1, 0, 0, 0), 'J': (0, 1, 0, 0), 'o': (0, 0, 1, 0), 'M': (0, 0, 0, 1) }, dtype=bool, permute=(1, 2, 0)) converted = converter(repainted) self.assertEqual(converted.shape, (3, 11, 4)) assertMask(converted[..., 0], ['11011011011', '11000011011', '11011011011']) assertMask(converted[..., 1], ['00100100000', '00111100100', '00100100100']) assertMask(converted[..., 2], ['00000000100', '00000000000', '00000000000']) assertMask(converted[..., 3], ['00000000000', '00000000000', '00000000000']) ### Test ObservationToFeatureArray, which creates 3-D feature arrays faster. converter = rendering.ObservationToFeatureArray('.JoM') converted = converter(repainted) self.assertEqual(converted.shape, (4, 3, 11)) assertMask(converted[0, :], ['11011011011', '11000011011', '11011011011']) assertMask(converted[1, :], ['00100100000', '00111100100', '00100100100']) assertMask(converted[2, :], ['00000000100', '00000000000', '00000000000']) assertMask(converted[3, :], ['00000000000', '00000000000', '00000000000']) ### Test ObservationToFeatureArray's layer permutation capability. converter = rendering.ObservationToFeatureArray('.J', permute=(1, 0, 2)) converted = converter(repainted) self.assertEqual(converted.shape, (3, 2, 11)) assertMask(converted[0, :], ['11011011011', '00100100000']) assertMask(converted[1, :], ['11000011011', '00111100100']) assertMask(converted[2, :], ['11011011011', '00100100100'])
def make_game(): """Builds and returns an Apprehend game.""" return ascii_art.ascii_art_to_game( GAME_ART, what_lies_beneath=' ', sprites={'P': PlayerSprite, 'b': BallSprite}, update_schedule=['b', 'P'])
def make_game(game_type, good_color, bad_color, switched_colors=False): if switched_colors: good_color, bad_color = bad_color, good_color # switcheroo these_sprites = {} these_drapes = {} if game_type == "pick_up": shape = "square" num_objects = PICK_UP_NUM_OBJECTS_PER good_obj = good_color + "_" + shape bad_obj = bad_color + "_" + shape good_char = OBJECTS[good_obj]["char"] bad_char = OBJECTS[bad_obj]["char"] these_drapes.update({ good_char: ascii_art.Partial(ValueDrape, value=1.), bad_char: ascii_art.Partial(ValueDrape, value=NEG_VALUE) }) elif game_type == "pusher": shape = "tee" num_objects = PUSHER_NUM_OBJECTS_PER good_obj = good_color + "_" + shape bad_obj = bad_color + "_" + shape good_char = OBJECTS[good_obj]["char"] bad_char = OBJECTS[bad_obj]["char"] these_drapes.update({ good_char: ascii_art.Partial(PushableDrape, value=1.), bad_char: ascii_art.Partial(PushableDrape, value=NEG_VALUE) }) elif game_type == "shooter": shape = "diamond" num_objects = SHOOTER_NUM_OBJECTS_PER good_obj = good_color + "_" + shape bad_obj = bad_color + "_" + shape good_char = OBJECTS[good_obj]["char"] bad_char = OBJECTS[bad_obj]["char"] these_drapes.update({ good_char: ascii_art.Partial(ShootableDrape, value=1.), bad_char: ascii_art.Partial(ShootableDrape, value=NEG_VALUE) }) elif game_type == "sequence_imitation": shape = "triangle" num_objects = 1 good_obj = good_color + "_" + shape bad_obj = bad_color + "_" + shape good_char = OBJECTS[good_obj]["char"] bad_char = OBJECTS[bad_obj]["char"] these_sprites.update({ good_char: ascii_art.Partial(DancerSprite, value=SEQ_IMIT_VALUE_PER), bad_char: ascii_art.Partial(DancerSprite, value=SEQ_IMIT_VALUE_PER * NEG_VALUE) }) else: raise ValueError("Unknown game type: {}".format(game_type)) update_schedule = [AGENT_CHAR, good_char, bad_char] if game_type == "pusher": update_schedule = [good_char, bad_char, AGENT_CHAR] grid = [] grid.append(["#"] * (GRID_SIZE + 2)) for _ in range(GRID_SIZE): grid.append(["#"] + ([" "] * GRID_SIZE) + ["#"]) grid.append(["#"] * (GRID_SIZE + 2)) if game_type in ["pick_up", "pusher", "shooter"]: locations = [(i, j) for i in range(1, GRID_SIZE + 1) for j in range(1, GRID_SIZE + 1)] elif game_type == "sequence_imitation": quarts = [1 + GRID_SIZE // 4, GRID_SIZE - GRID_SIZE // 4] locations = [(x, y) for x in quarts for y in quarts] np.random.shuffle(locations) for _ in range(num_objects): bad_loc = locations.pop() grid[bad_loc[0]][bad_loc[1]] = bad_char good_loc = locations.pop() grid[good_loc[0]][good_loc[1]] = good_char agent_start = locations.pop() grid[agent_start[0]][agent_start[1]] = AGENT_CHAR these_sprites.update( {'A': ascii_art.Partial(PlayerSprite, game_type=game_type)}) grid = [''.join(l) for l in grid] game = ascii_art.ascii_art_to_game(grid, what_lies_beneath=' ', sprites=these_sprites, drapes=these_drapes, update_schedule=update_schedule) if game_type == "pick_up": game.the_plot["num_picked_up"] = 0 elif game_type == "pusher": game.the_plot["num_pushed_off"] = 0 game.the_plot["player_blocked"] = False elif game_type == "shooter": game.the_plot["num_shot"] = 0 game.the_plot["heading"] = 0 elif game_type == "sequence_imitation": game.the_plot["good_move"] = -1 # no valid move on first turn game.the_plot["bad_move"] = -1 return game