def test_rng_if_none(): # default call returns library rng rng = None assert get_gv_rng_if_none(rng) is get_gv_rng() # call with an rng returns that rng rng = make_rng() assert get_gv_rng_if_none(rng) is rng
def stochastic_raytracing_visibility( # TODO add test grid: Grid, position: Position, *, rng: Optional[rnd.Generator] = None, ) -> np.ndarray: rng = get_gv_rng_if_none(rng) area = Area((0, grid.height - 1), (0, grid.width - 1)) rays = cached_compute_rays_fancy(position, area) counts_num = np.zeros((area.height, area.width), dtype=int) counts_den = np.zeros((area.height, area.width), dtype=int) for ray in rays: light = True for pos in ray: if light: counts_num[pos.y, pos.x] += 1 counts_den[pos.y, pos.x] += 1 light = light and grid[pos].transparent probs = np.nan_to_num(counts_num / counts_den) visibility = probs <= rng.random(probs.shape) return visibility
def creeping_walls( state: State, action: Action, # pylint: disable=unused-argument *, rng: Optional[rnd.Generator] = None, ) -> None: """randomly chooses a Floor tile and turns it into a Wall tile""" rng = get_gv_rng_if_none(rng) # necessary to use rng object! # all positions associated with a Floor object floor_positions = [ position for position in state.grid.positions() if isinstance(state.grid[position], Floor) ] try: # floor_positions could be an empty list position = rng.choice(floor_positions) except ValueError: # there are no floor positions pass else: # if we were able to sample a position, change the corresponding Floor # into a Wall state.grid[position] = Wall()
def reset_teleport(height: int, width: int, *, rng: Optional[rnd.Generator] = None) -> State: rng = get_gv_rng_if_none(rng) state = reset_empty(height, width) assert isinstance(state.grid[height - 2, width - 2], Goal) # Place agent on top left state.agent.position = (1, 1) # type: ignore state.agent.orientation = rng.choice([Orientation.E, Orientation.S]) num_telepods = 2 telepods = [Telepod(Color.RED) for _ in range(num_telepods)] positions = rng.choice( [ position for position in state.grid.positions() if isinstance(state.grid[position], Floor) and position != state.agent.position ], size=num_telepods, replace=False, ) for position, telepod in zip(positions, telepods): state.grid[position] = telepod # Place agent on top left state.agent.position = (1, 1) # type: ignore state.agent.orientation = rng.choice([Orientation.E, Orientation.S]) return state
def match_key_color( *, rng: Optional[rnd.Generator] = None, # pylint: disable=unused-argument ) -> State: """the agent has to pick the correct key to open a randomly colored door""" rng = get_gv_rng_if_none(rng) # necessary to use rng object! # only consider these colors colors = [Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW] # randomly choose location of keys key1, key2, key3, key4 = rng.permute([Key(color) for color in colors]) # randomly choose color of door door = Door(Door.Status.LOCKED, rng.choice(colors)) # grids can be constructed directly from objects grid = Grid.from_objects([ [Wall(), Wall(), Wall(), Wall(), Wall()], [Wall(), Wall(), Goal(), Wall(), Wall()], [Wall(), Wall(), door, Wall(), Wall()], [Wall(), key1, Floor(), key2, Wall()], [Wall(), key3, Floor(), key4, Wall()], [Wall(), Wall(), Wall(), Wall(), Wall()], ]) # positioning the agent in the above grid agent = Agent((4, 2), Orientation.N) return State(grid, agent)
def reset_keydoor(height: int, width: int, *, rng: Optional[rnd.Generator] = None) -> State: """An environment with a key and a door Creates a height x width (including outer walls) grid with a random column of walls. The agent and a yellow key are randomly dropped left of the column, while the goal is placed in the bottom right. For example:: ######### # @# # # D # #K # G# ######### Args: height (`int`): width (`int`): rng: (`Generator, optional`) Returns: State: """ if height < 3 or width < 5 or (height, width) == (3, 5): raise ValueError( f'Shape must larger than (3, 5), given {(height, width)}') rng = get_gv_rng_if_none(rng) state = reset_empty(height, width) assert isinstance(state.grid[height - 2, width - 2], Goal) # Generate vertical splitting wall x_wall = rng.integers(2, width - 3, endpoint=True) line_wall = draw_line_vertical(state.grid, range(1, height - 1), x_wall, Wall) # Place yellow, locked door pos_wall = rng.choice(line_wall) state.grid[pos_wall] = Door(Door.Status.LOCKED, Color.YELLOW) # Place yellow key left of wall # XXX: potential general function y_key = rng.integers(1, height - 2, endpoint=True) x_key = rng.integers(1, x_wall - 1, endpoint=True) state.grid[y_key, x_key] = Key(Color.YELLOW) # Place agent left of wall # XXX: potential general function y_agent = rng.integers(1, height - 2, endpoint=True) x_agent = rng.integers(1, x_wall - 1, endpoint=True) state.agent.position = (y_agent, x_agent) # type: ignore state.agent.orientation = rng.choice(list(Orientation)) return state
def random_transition( state: State, action: Action, *, transition_function: TransitionFunction, p_success: float, rng: Optional[rnd.Generator] = None, ) -> None: """randomly determines whether to perform a transition or not""" rng = get_gv_rng_if_none(rng) # necessary to use rng object! # flip coin to see whether to run the transition_function success = rng.random() <= p_success if success: transition_function(state, action, rng=rng)
def coinflip_visibility( grid: Grid, position: Position, *, rng: Optional[rnd.Generator] = None, ) -> np.ndarray: """randomly determines tile visibility""" rng = get_gv_rng_if_none(rng) # necessary to use rng object! # sample a random binary visibility matrix visibility = rng.integers(2, size=grid.shape).astype(bool) # the agent tile should always be visible regardless visibility[position.y, position.x] = True return visibility
def reset_dynamic_obstacles( height: int, width: int, num_obstacles: int, random_agent_pos: bool = False, *, rng: Optional[rnd.Generator] = None, ) -> State: """An environment with dynamically moving obstacles Args: height (`int`): height of grid width (`int`): width of grid num_obstacles (`int`): number of dynamic obstacles random_agent (`bool, optional`): position of agent, in corner if False rng: (`Generator, optional`) Returns: State: """ rng = get_gv_rng_if_none(rng) state = reset_empty(height, width, random_agent_pos, rng=rng) vacant_positions = [ position for position in state.grid.positions() if isinstance(state.grid[position], Floor) and position != state.agent.position ] try: sample_positions = rng.choice(vacant_positions, size=num_obstacles, replace=False) except ValueError as e: raise ValueError( f'Too many obstacles ({num_obstacles}) and not enough ' f'vacant positions ({len(vacant_positions)})') from e for pos in sample_positions: assert isinstance(state.grid[pos], Floor) state.grid[pos] = MovingObstacle() return state
def step_telepod( state: State, action: Action, # pylint: disable=unused-argument *, rng: Optional[rnd.Generator] = None, # pylint: disable=unused-argument ) -> None: """Teleports the agent if positioned on the telepod""" rng = get_gv_rng_if_none(rng) telepod = state.grid[state.agent.position] if isinstance(telepod, Telepod): positions = [ position for position in state.grid.positions() if position != state.agent.position and isinstance(state.grid[position], Telepod) and state.grid[position].color == telepod.color ] state.agent.position = rng.choice(positions)
def reset_empty( height: int, width: int, random_agent: bool = False, random_goal: bool = False, *, rng: Optional[rnd.Generator] = None, ) -> State: """An empty environment""" if height < 4 or width < 4: raise ValueError('height and width need to be at least 4') rng = get_gv_rng_if_none(rng) # TODO test creation (e.g. count number of walls, goals, check held item) grid = Grid(height, width) draw_wall_boundary(grid) if random_goal: goal_y = rng.integers(1, height - 2, endpoint=True) goal_x = rng.integers(1, width - 2, endpoint=True) else: goal_y = height - 2 goal_x = width - 2 grid[goal_y, goal_x] = Goal() if random_agent: agent_position = rng.choice([ position for position in grid.positions() if isinstance(grid[position], Floor) ]) agent_orientation = rng.choice(list(Orientation)) else: agent_position = (1, 1) agent_orientation = Orientation.E agent = Agent(agent_position, agent_orientation) return State(grid, agent)
def _step_moving_obstacle(grid: Grid, position: Position, *, rng: Optional[rnd.Generator] = None): """Utility for moving a single MovingObstacle randomly""" assert isinstance(grid[position], MovingObstacle) rng = get_gv_rng_if_none(rng) next_positions = [ next_position for next_position in get_manhattan_boundary(position, distance=1) if next_position in grid and isinstance(grid[next_position], Floor) ] try: next_position = rng.choice(next_positions) except ValueError: pass else: grid.swap(position, next_position)
def step_moving_obstacles( state: State, action: Action, *, rng: Optional[rnd.Generator] = None, ) -> None: """Moves moving obstacles randomly Moves each MovingObstacle only to cells containing _Floor_ objects, and will do so with random walk. In current implementation can only move 1 cell non-diagonally. If (and only if) no open cells are available will it stay put Args: state (`State`): current state action (`Action`): action taken by agent (ignored) """ rng = get_gv_rng_if_none(rng) for position, obj in _unique_object_type_positions(state.grid, MovingObstacle): _step_moving_obstacle(state.grid, position, rng=rng)
def reset_rooms( # pylint: disable=too-many-locals height: int, width: int, layout: Tuple[int, int], *, rng: Optional[rnd.Generator] = None, ) -> State: rng = get_gv_rng_if_none(rng) # TODO test creation (e.g. count number of walls, goals, check held item) layout_height, layout_width = layout y_splits = np.linspace( 0, height - 1, num=layout_height + 1, dtype=int, ) if len(y_splits) != len(set(y_splits)): raise ValueError( f'insufficient height ({height}) for layout ({layout})') x_splits = np.linspace( 0, width - 1, num=layout_width + 1, dtype=int, ) if len(x_splits) != len(set(x_splits)): raise ValueError( f'insufficient width ({height}) for layout ({layout})') grid = Grid(height, width) draw_room_grid(grid, y_splits, x_splits, Wall) # passages in horizontal walls for y in y_splits[1:-1]: for x_from, x_to in mitt.pairwise(x_splits): x = rng.integers(x_from + 1, x_to) grid[y, x] = Floor() # passages in vertical walls for y_from, y_to in mitt.pairwise(y_splits): for x in x_splits[1:-1]: y = rng.integers(y_from + 1, y_to) grid[y, x] = Floor() # sample agent and goal positions agent_position, goal_position = rng.choice( [ position for position in grid.positions() if isinstance(grid[position], Floor) ], size=2, replace=False, ) agent_orientation = rng.choice(list(Orientation)) grid[goal_position] = Goal() agent = Agent(agent_position, agent_orientation) return State(grid, agent)
def reset_crossing( # pylint: disable=too-many-locals height: int, width: int, num_rivers: int, object_type: Type[GridObject], *, rng: Optional[rnd.Generator] = None, ) -> State: """An environment with "rivers" to be crosses Creates a height x width (including wall) grid with random rows/columns of objects called "rivers". The agent needs to navigate river openings to reach the goal. For example:: ######### #@ # # #### #### # # # ## ###### # # # # # # #G# ######### Args: height (`int`): odd height of grid width (`int`): odd width of grid num_rivers (`int`): number of `rivers` object_type (`Type[GridObject]`): river's object type rng: (`Generator, optional`) Returns: State: """ if height < 5 or height % 2 == 0: raise ValueError( f"Crossing environment height must be odd and >= 5, given {height}" ) if width < 5 or width % 2 == 0: raise ValueError( f"Crossing environment width must be odd and >= 5, given {width}") if num_rivers < 0: raise ValueError( f"Crossing environment number of walls must be >= 0, given {height}" ) rng = get_gv_rng_if_none(rng) state = reset_empty(height, width) assert isinstance(state.grid[height - 2, width - 2], Goal) # token `horizontal` and `vertical` objects h, v = object(), object() # all rivers specified by orientation and position rivers = list( itt.chain( ((h, i) for i in range(2, height - 2, 2)), ((v, j) for j in range(2, width - 2, 2)), )) # sample subset of random rivers rng.shuffle(rivers) # NOTE: faster than rng.choice rivers = rivers[:num_rivers] # create horizontal rivers without crossings rivers_h = sorted([pos for direction, pos in rivers if direction is h]) for y in rivers_h: draw_line_horizontal(state.grid, y, range(1, width - 1), object_type) # create vertical rivers without crossings rivers_v = sorted([pos for direction, pos in rivers if direction is v]) for x in rivers_v: draw_line_vertical(state.grid, range(1, height - 1), x, object_type) # sample path to goal path = [h] * len(rivers_v) + [v] * len(rivers_h) rng.shuffle(path) # create crossing limits_h = [0] + rivers_h + [height - 1] # horizontal river boundaries limits_v = [0] + rivers_v + [width - 1] # vertical river boundaries room_i, room_j = 0, 0 # coordinates of current "room" for step_direction in path: if step_direction is h: i = rng.integers(limits_h[room_i] + 1, limits_h[room_i + 1]) j = limits_v[room_j + 1] room_j += 1 elif step_direction is v: i = limits_h[room_i + 1] j = rng.integers(limits_v[room_j] + 1, limits_v[room_j + 1]) room_i += 1 else: assert False state.grid[i, j] = Floor() # Place agent on top left state.agent.position = (1, 1) # type: ignore state.agent.orientation = Orientation.E return state