Module moog.maze_lib.maze

Maze class.

This file contains the Maze class, which is a class to mediate between a binary maze array and sprites, including methods to infer a maze from sprites, convert a maze to sprites, and sample points or connected blobs in the maze.

Expand source code
"""Maze class.

This file contains the Maze class, which is a class to mediate between a binary
maze array and sprites, including methods to infer a maze from sprites, convert
a maze to sprites, and sample points or connected blobs in the maze.
"""

import numpy as np
from moog import sprite

# Small error tolerance for checking whether wall vertex values are multiples of
# a grid size
_EPSILON = 1e-4

# Sanity check to prevent infinite while looping. No maze should have grid size
# larger than this
_MAX_MAZE_SIZE = 100


class Maze():
    """Maze class."""

    def __init__(self, maze):
        """Constructor.

        Args:
            maze: Square numpy array with binary values (may be a boolean array,
                or an int/float array with 0/1 values). The 1's are the walls of
                the maze.
        """
        self.maze = maze
        self.maze_size = maze.shape[0]
        self.grid_side = 1. / self.maze_size
        self.half_grid_side = 0.5 * self.grid_side
        self.side_vertices = np.linspace(
            self.half_grid_side, 1. - self.half_grid_side, self.maze_size)

    @classmethod
    def from_state(cls, state, maze_layer='walls'):
        """Get a maze object from environment state.

        This method works by first inferring the maze size by looking for the
        smallest integer N such that 1/N divides the vertex values of the
        sprites in the maze values. Then it checks whether each of the
        centerpoints of an NxN grid are contained in a maze wall sprite to
        compute the maze matrix.
        
        Args:
            state: OrderedDict of sprites. State of the environment.
            maze_layer: String. Layer in the environment containing the maze
                wall sprites.
        """
        wall_vertices = np.array([s.vertices for s in state[maze_layer]])
        
        # Find the smallest maze size N such that all wall vertices are a
        # multiple of 1/N
        maze_size = 1
        valid_maze_size = False
        while not valid_maze_size:
            if maze_size > _MAX_MAZE_SIZE:
                raise ValueError(
                    'Cannot find a maze grid size. Your maze sprites are '
                    'invalid.')
            rounded_vertices = np.round(wall_vertices * maze_size) / maze_size
            if np.allclose(rounded_vertices, wall_vertices, atol=_EPSILON):
                valid_maze_size = True
            else:
                maze_size += 1
        
        # Get a matrix of grid square centerpoints
        half_grid_side = 1. / (2 * maze_size)
        edge_centers = np.linspace(
            half_grid_side, 1 - half_grid_side, maze_size)
        grid_centers = np.stack(np.meshgrid(edge_centers, edge_centers), axis=2)
        flat_grid_centers = np.reshape(grid_centers, (maze_size * maze_size, 2))

        # Now check which grid square centerpoints are inside walls
        maze = np.zeros(maze_size * maze_size, dtype=bool)
        for s in state[maze_layer]:
            contained = s.contains_points(flat_grid_centers)
            maze = np.logical_or(maze, contained)

        maze = np.reshape(maze, (maze_size, maze_size)).astype(int)
        return cls(maze)

    def to_sprites(self, **color):
        """Turn a maze matrix into a list of sprites.
        
        Each sprite is a single square for one brick in the maze walls.

        Args:
            color: Dict. May contain keys {'c0', 'c1', 'c2'} for the wall sprite
                color factors.

        Returns:
            sprites: List of maze wall sprites.
        """
        maze_size = self.maze_size
        vertex_linspace = np.linspace(0., 1., maze_size + 1)
        sprites = []
        for x in range(maze_size):
            for y in range(maze_size):
                if self.maze[y, x]:
                    shape = np.array([
                        [vertex_linspace[x], vertex_linspace[y]],
                        [vertex_linspace[x], vertex_linspace[y + 1]],
                        [vertex_linspace[x + 1], vertex_linspace[y + 1]],
                        [vertex_linspace[x + 1], vertex_linspace[y]],
                    ])
                    new_sprite = sprite.Sprite(x=0., y=0., shape=shape, **color)
                    sprites.append(new_sprite)
        return sprites

    def open_vertex(self, i, j):
        """Returns whether the (i, j) cell is open, i.e. not a maze wall."""
        if i < 0 or j < 0 or i >= self.maze_size or j >= self.maze_size:
            return False
        else:
            return not self.maze[j, i]

    def valid_directions(self, i, j):
        """Computes the open neighbords of the (i, j) cell."""
        valid_directions = np.array([
            [self.open_vertex(k, j) for k in [i - 1, i + 1]],
            [self.open_vertex(i, k) for k in [j - 1, j + 1]],
        ])
        return valid_directions

    def sample_random_position(self, off_intersection=True):
        """Sample random open position on the edges of the maze."""
        # First find edges
        neg_maze = 1 - self.maze
        v_edges = np.logical_and(neg_maze[1:], neg_maze[:-1])
        h_edges = np.logical_and(neg_maze[:, 1:], neg_maze[:, :-1])
        v_edges = np.stack(np.nonzero(v_edges)[::-1]).T
        h_edges = np.stack(np.nonzero(h_edges)[::-1]).T
        num_h_edges = len(h_edges)
        num_v_edges = len(v_edges)
        num_edges = num_h_edges + num_v_edges
        if np.random.rand() < float(num_h_edges) / num_edges:
            # Pick a horizontal edge
            edge = h_edges[np.random.choice(num_h_edges)]
            position = edge
            if off_intersection:
                position = edge + np.random.rand() * np.array([1., 0.])
        else:
            # Pick a vertical edge
            edge = v_edges[np.random.choice(num_v_edges)]
            position = edge
            if off_intersection:
                position = edge + np.random.rand() * np.array([0., 1.])
        position = self.half_grid_side + position * self.grid_side
        return position

    def to_background_grid(self, line_thickness=0.01, **color):
        """Get static grid of background lines as list of sprites.

        These lines are in the channels of the maze and when rendered can
        provide a nice visual effect.
        
        Args:
            line_thickness: Float. How thick the grid lines should be.
            color: Dict. May contain keys {'c0', 'c1', 'c2'} for the background
                line sprite color factors.

        Returns:
            grid_sprites: List of sprites, the grid lines.
        """
        grid_sprites = []
        def _add_sprite(min_x, max_x, min_y, max_y):
            shape = np.array([
                [min_x, min_y], [max_x, min_y], [max_x, max_y], [min_x, max_y]
            ])
            grid_sprites.append(sprite.Sprite(x=0., y=0., shape=shape, **color))
        
        for x in self.side_vertices:
            min_x = x - 0.5 * line_thickness
            max_x = x + 0.5 * line_thickness
            _add_sprite(min_x, max_x, 0., 1.)

        for y in self.side_vertices:
            min_y = y - 0.5 * line_thickness
            max_y = y + 0.5 * line_thickness
            _add_sprite(0., 1., min_y, max_y)

        return grid_sprites

    def sample_open_point(self):
        """Sample an open point in the maze.

        Returns:
            Tuple of integers (i, j), coordinates of open point.
        """
        if np.sum(1 - self.maze) == 0:
            raise ValueError('Maze has no open point.')
        candidates = np.argwhere(self.maze == 0)
        point = candidates[np.random.randint(len(candidates))]
        return tuple(point)

    def sample_distinct_open_points(self, num_points):
        """Sample distinct open points in the maze.

        Args:
            num_points: Int. Number of distinct open points to sample.

        Returns:
            Tuple of integers (i, j), coordinates of open point.
        """
        if np.sum(1 - self.maze) < num_points:
            raise ValueError('Maze has no open point.')
        candidates = np.argwhere(self.maze == 0)
        inds = np.random.choice(len(candidates), size=num_points, replace=False)
        points = [tuple(candidates[i]) for i in inds]
        return points
    
    def get_neighbors(self, i, j):
        """Get list of neighbors of point (i, j)."""
        neighbors = []
        if i > 0 and not self.maze[i - 1, j]:
            neighbors.append((i - 1, j))
        if i < self.maze_size - 1 and not self.maze[i + 1, j]:
            neighbors.append((i + 1, j))
        if j > 0 and not self.maze[i, j - 1]:
            neighbors.append((i, j - 1))
        if j < self.maze_size - 1 and not self.maze[i, j + 1]:
            neighbors.append((i, j + 1))
        return neighbors
    
    def get_neighbor_dict(self):
        """Get dictionary of neighbors of all points.
        
        Returns:
            neighbor_dict: Keys are int tuples (i, j) and values are lists of
                int tuples for all open neighbors of (i, j).
        """
        neighbor_dict = {
            (i, j): self.get_neighbors(i, j)
            for i in range(self.maze_size)
            for j in range(self.maze_size)
        }
        return neighbor_dict

    def add_wall(self, x_range, y_range):
        """Add a rectangular wall to the maze.

        Args:
            x_range: start and end point of the wall in the x-coordinate
            y_range: start and end point of the wall in the y-coordinate
        """
        self.maze[x_range[0] : x_range[1] + 1, y_range[0] : y_range[1] + 1] = 1

    def add_outer_walls(self):
        """Make the maze borders walls.
        
        Warning: This could distrupt properties of the maze. For example, if the
        maze originally had no dead ends, this could introduce dead ends.
        """
        self.maze[0, :] = 1
        self.maze[-1, :] = 1
        self.maze[:, 0] = 1
        self.maze[:, -1] = 1

Classes

class Maze (maze)

Maze class.

Constructor.

Args

maze
Square numpy array with binary values (may be a boolean array, or an int/float array with 0/1 values). The 1's are the walls of the maze.
Expand source code
class Maze():
    """Maze class."""

    def __init__(self, maze):
        """Constructor.

        Args:
            maze: Square numpy array with binary values (may be a boolean array,
                or an int/float array with 0/1 values). The 1's are the walls of
                the maze.
        """
        self.maze = maze
        self.maze_size = maze.shape[0]
        self.grid_side = 1. / self.maze_size
        self.half_grid_side = 0.5 * self.grid_side
        self.side_vertices = np.linspace(
            self.half_grid_side, 1. - self.half_grid_side, self.maze_size)

    @classmethod
    def from_state(cls, state, maze_layer='walls'):
        """Get a maze object from environment state.

        This method works by first inferring the maze size by looking for the
        smallest integer N such that 1/N divides the vertex values of the
        sprites in the maze values. Then it checks whether each of the
        centerpoints of an NxN grid are contained in a maze wall sprite to
        compute the maze matrix.
        
        Args:
            state: OrderedDict of sprites. State of the environment.
            maze_layer: String. Layer in the environment containing the maze
                wall sprites.
        """
        wall_vertices = np.array([s.vertices for s in state[maze_layer]])
        
        # Find the smallest maze size N such that all wall vertices are a
        # multiple of 1/N
        maze_size = 1
        valid_maze_size = False
        while not valid_maze_size:
            if maze_size > _MAX_MAZE_SIZE:
                raise ValueError(
                    'Cannot find a maze grid size. Your maze sprites are '
                    'invalid.')
            rounded_vertices = np.round(wall_vertices * maze_size) / maze_size
            if np.allclose(rounded_vertices, wall_vertices, atol=_EPSILON):
                valid_maze_size = True
            else:
                maze_size += 1
        
        # Get a matrix of grid square centerpoints
        half_grid_side = 1. / (2 * maze_size)
        edge_centers = np.linspace(
            half_grid_side, 1 - half_grid_side, maze_size)
        grid_centers = np.stack(np.meshgrid(edge_centers, edge_centers), axis=2)
        flat_grid_centers = np.reshape(grid_centers, (maze_size * maze_size, 2))

        # Now check which grid square centerpoints are inside walls
        maze = np.zeros(maze_size * maze_size, dtype=bool)
        for s in state[maze_layer]:
            contained = s.contains_points(flat_grid_centers)
            maze = np.logical_or(maze, contained)

        maze = np.reshape(maze, (maze_size, maze_size)).astype(int)
        return cls(maze)

    def to_sprites(self, **color):
        """Turn a maze matrix into a list of sprites.
        
        Each sprite is a single square for one brick in the maze walls.

        Args:
            color: Dict. May contain keys {'c0', 'c1', 'c2'} for the wall sprite
                color factors.

        Returns:
            sprites: List of maze wall sprites.
        """
        maze_size = self.maze_size
        vertex_linspace = np.linspace(0., 1., maze_size + 1)
        sprites = []
        for x in range(maze_size):
            for y in range(maze_size):
                if self.maze[y, x]:
                    shape = np.array([
                        [vertex_linspace[x], vertex_linspace[y]],
                        [vertex_linspace[x], vertex_linspace[y + 1]],
                        [vertex_linspace[x + 1], vertex_linspace[y + 1]],
                        [vertex_linspace[x + 1], vertex_linspace[y]],
                    ])
                    new_sprite = sprite.Sprite(x=0., y=0., shape=shape, **color)
                    sprites.append(new_sprite)
        return sprites

    def open_vertex(self, i, j):
        """Returns whether the (i, j) cell is open, i.e. not a maze wall."""
        if i < 0 or j < 0 or i >= self.maze_size or j >= self.maze_size:
            return False
        else:
            return not self.maze[j, i]

    def valid_directions(self, i, j):
        """Computes the open neighbords of the (i, j) cell."""
        valid_directions = np.array([
            [self.open_vertex(k, j) for k in [i - 1, i + 1]],
            [self.open_vertex(i, k) for k in [j - 1, j + 1]],
        ])
        return valid_directions

    def sample_random_position(self, off_intersection=True):
        """Sample random open position on the edges of the maze."""
        # First find edges
        neg_maze = 1 - self.maze
        v_edges = np.logical_and(neg_maze[1:], neg_maze[:-1])
        h_edges = np.logical_and(neg_maze[:, 1:], neg_maze[:, :-1])
        v_edges = np.stack(np.nonzero(v_edges)[::-1]).T
        h_edges = np.stack(np.nonzero(h_edges)[::-1]).T
        num_h_edges = len(h_edges)
        num_v_edges = len(v_edges)
        num_edges = num_h_edges + num_v_edges
        if np.random.rand() < float(num_h_edges) / num_edges:
            # Pick a horizontal edge
            edge = h_edges[np.random.choice(num_h_edges)]
            position = edge
            if off_intersection:
                position = edge + np.random.rand() * np.array([1., 0.])
        else:
            # Pick a vertical edge
            edge = v_edges[np.random.choice(num_v_edges)]
            position = edge
            if off_intersection:
                position = edge + np.random.rand() * np.array([0., 1.])
        position = self.half_grid_side + position * self.grid_side
        return position

    def to_background_grid(self, line_thickness=0.01, **color):
        """Get static grid of background lines as list of sprites.

        These lines are in the channels of the maze and when rendered can
        provide a nice visual effect.
        
        Args:
            line_thickness: Float. How thick the grid lines should be.
            color: Dict. May contain keys {'c0', 'c1', 'c2'} for the background
                line sprite color factors.

        Returns:
            grid_sprites: List of sprites, the grid lines.
        """
        grid_sprites = []
        def _add_sprite(min_x, max_x, min_y, max_y):
            shape = np.array([
                [min_x, min_y], [max_x, min_y], [max_x, max_y], [min_x, max_y]
            ])
            grid_sprites.append(sprite.Sprite(x=0., y=0., shape=shape, **color))
        
        for x in self.side_vertices:
            min_x = x - 0.5 * line_thickness
            max_x = x + 0.5 * line_thickness
            _add_sprite(min_x, max_x, 0., 1.)

        for y in self.side_vertices:
            min_y = y - 0.5 * line_thickness
            max_y = y + 0.5 * line_thickness
            _add_sprite(0., 1., min_y, max_y)

        return grid_sprites

    def sample_open_point(self):
        """Sample an open point in the maze.

        Returns:
            Tuple of integers (i, j), coordinates of open point.
        """
        if np.sum(1 - self.maze) == 0:
            raise ValueError('Maze has no open point.')
        candidates = np.argwhere(self.maze == 0)
        point = candidates[np.random.randint(len(candidates))]
        return tuple(point)

    def sample_distinct_open_points(self, num_points):
        """Sample distinct open points in the maze.

        Args:
            num_points: Int. Number of distinct open points to sample.

        Returns:
            Tuple of integers (i, j), coordinates of open point.
        """
        if np.sum(1 - self.maze) < num_points:
            raise ValueError('Maze has no open point.')
        candidates = np.argwhere(self.maze == 0)
        inds = np.random.choice(len(candidates), size=num_points, replace=False)
        points = [tuple(candidates[i]) for i in inds]
        return points
    
    def get_neighbors(self, i, j):
        """Get list of neighbors of point (i, j)."""
        neighbors = []
        if i > 0 and not self.maze[i - 1, j]:
            neighbors.append((i - 1, j))
        if i < self.maze_size - 1 and not self.maze[i + 1, j]:
            neighbors.append((i + 1, j))
        if j > 0 and not self.maze[i, j - 1]:
            neighbors.append((i, j - 1))
        if j < self.maze_size - 1 and not self.maze[i, j + 1]:
            neighbors.append((i, j + 1))
        return neighbors
    
    def get_neighbor_dict(self):
        """Get dictionary of neighbors of all points.
        
        Returns:
            neighbor_dict: Keys are int tuples (i, j) and values are lists of
                int tuples for all open neighbors of (i, j).
        """
        neighbor_dict = {
            (i, j): self.get_neighbors(i, j)
            for i in range(self.maze_size)
            for j in range(self.maze_size)
        }
        return neighbor_dict

    def add_wall(self, x_range, y_range):
        """Add a rectangular wall to the maze.

        Args:
            x_range: start and end point of the wall in the x-coordinate
            y_range: start and end point of the wall in the y-coordinate
        """
        self.maze[x_range[0] : x_range[1] + 1, y_range[0] : y_range[1] + 1] = 1

    def add_outer_walls(self):
        """Make the maze borders walls.
        
        Warning: This could distrupt properties of the maze. For example, if the
        maze originally had no dead ends, this could introduce dead ends.
        """
        self.maze[0, :] = 1
        self.maze[-1, :] = 1
        self.maze[:, 0] = 1
        self.maze[:, -1] = 1

Static methods

def from_state(state, maze_layer='walls')

Get a maze object from environment state.

This method works by first inferring the maze size by looking for the smallest integer N such that 1/N divides the vertex values of the sprites in the maze values. Then it checks whether each of the centerpoints of an NxN grid are contained in a maze wall sprite to compute the maze matrix.

Args

state
OrderedDict of sprites. State of the environment.
maze_layer
String. Layer in the environment containing the maze wall sprites.
Expand source code
@classmethod
def from_state(cls, state, maze_layer='walls'):
    """Get a maze object from environment state.

    This method works by first inferring the maze size by looking for the
    smallest integer N such that 1/N divides the vertex values of the
    sprites in the maze values. Then it checks whether each of the
    centerpoints of an NxN grid are contained in a maze wall sprite to
    compute the maze matrix.
    
    Args:
        state: OrderedDict of sprites. State of the environment.
        maze_layer: String. Layer in the environment containing the maze
            wall sprites.
    """
    wall_vertices = np.array([s.vertices for s in state[maze_layer]])
    
    # Find the smallest maze size N such that all wall vertices are a
    # multiple of 1/N
    maze_size = 1
    valid_maze_size = False
    while not valid_maze_size:
        if maze_size > _MAX_MAZE_SIZE:
            raise ValueError(
                'Cannot find a maze grid size. Your maze sprites are '
                'invalid.')
        rounded_vertices = np.round(wall_vertices * maze_size) / maze_size
        if np.allclose(rounded_vertices, wall_vertices, atol=_EPSILON):
            valid_maze_size = True
        else:
            maze_size += 1
    
    # Get a matrix of grid square centerpoints
    half_grid_side = 1. / (2 * maze_size)
    edge_centers = np.linspace(
        half_grid_side, 1 - half_grid_side, maze_size)
    grid_centers = np.stack(np.meshgrid(edge_centers, edge_centers), axis=2)
    flat_grid_centers = np.reshape(grid_centers, (maze_size * maze_size, 2))

    # Now check which grid square centerpoints are inside walls
    maze = np.zeros(maze_size * maze_size, dtype=bool)
    for s in state[maze_layer]:
        contained = s.contains_points(flat_grid_centers)
        maze = np.logical_or(maze, contained)

    maze = np.reshape(maze, (maze_size, maze_size)).astype(int)
    return cls(maze)

Methods

def add_outer_walls(self)

Make the maze borders walls.

Warning: This could distrupt properties of the maze. For example, if the maze originally had no dead ends, this could introduce dead ends.

Expand source code
def add_outer_walls(self):
    """Make the maze borders walls.
    
    Warning: This could distrupt properties of the maze. For example, if the
    maze originally had no dead ends, this could introduce dead ends.
    """
    self.maze[0, :] = 1
    self.maze[-1, :] = 1
    self.maze[:, 0] = 1
    self.maze[:, -1] = 1
def add_wall(self, x_range, y_range)

Add a rectangular wall to the maze.

Args

x_range
start and end point of the wall in the x-coordinate
y_range
start and end point of the wall in the y-coordinate
Expand source code
def add_wall(self, x_range, y_range):
    """Add a rectangular wall to the maze.

    Args:
        x_range: start and end point of the wall in the x-coordinate
        y_range: start and end point of the wall in the y-coordinate
    """
    self.maze[x_range[0] : x_range[1] + 1, y_range[0] : y_range[1] + 1] = 1
def get_neighbor_dict(self)

Get dictionary of neighbors of all points.

Returns

neighbor_dict
Keys are int tuples (i, j) and values are lists of int tuples for all open neighbors of (i, j).
Expand source code
def get_neighbor_dict(self):
    """Get dictionary of neighbors of all points.
    
    Returns:
        neighbor_dict: Keys are int tuples (i, j) and values are lists of
            int tuples for all open neighbors of (i, j).
    """
    neighbor_dict = {
        (i, j): self.get_neighbors(i, j)
        for i in range(self.maze_size)
        for j in range(self.maze_size)
    }
    return neighbor_dict
def get_neighbors(self, i, j)

Get list of neighbors of point (i, j).

Expand source code
def get_neighbors(self, i, j):
    """Get list of neighbors of point (i, j)."""
    neighbors = []
    if i > 0 and not self.maze[i - 1, j]:
        neighbors.append((i - 1, j))
    if i < self.maze_size - 1 and not self.maze[i + 1, j]:
        neighbors.append((i + 1, j))
    if j > 0 and not self.maze[i, j - 1]:
        neighbors.append((i, j - 1))
    if j < self.maze_size - 1 and not self.maze[i, j + 1]:
        neighbors.append((i, j + 1))
    return neighbors
def open_vertex(self, i, j)

Returns whether the (i, j) cell is open, i.e. not a maze wall.

Expand source code
def open_vertex(self, i, j):
    """Returns whether the (i, j) cell is open, i.e. not a maze wall."""
    if i < 0 or j < 0 or i >= self.maze_size or j >= self.maze_size:
        return False
    else:
        return not self.maze[j, i]
def sample_distinct_open_points(self, num_points)

Sample distinct open points in the maze.

Args

num_points
Int. Number of distinct open points to sample.

Returns

Tuple of integers (i, j), coordinates of open point.

Expand source code
def sample_distinct_open_points(self, num_points):
    """Sample distinct open points in the maze.

    Args:
        num_points: Int. Number of distinct open points to sample.

    Returns:
        Tuple of integers (i, j), coordinates of open point.
    """
    if np.sum(1 - self.maze) < num_points:
        raise ValueError('Maze has no open point.')
    candidates = np.argwhere(self.maze == 0)
    inds = np.random.choice(len(candidates), size=num_points, replace=False)
    points = [tuple(candidates[i]) for i in inds]
    return points
def sample_open_point(self)

Sample an open point in the maze.

Returns

Tuple of integers (i, j), coordinates of open point.

Expand source code
def sample_open_point(self):
    """Sample an open point in the maze.

    Returns:
        Tuple of integers (i, j), coordinates of open point.
    """
    if np.sum(1 - self.maze) == 0:
        raise ValueError('Maze has no open point.')
    candidates = np.argwhere(self.maze == 0)
    point = candidates[np.random.randint(len(candidates))]
    return tuple(point)
def sample_random_position(self, off_intersection=True)

Sample random open position on the edges of the maze.

Expand source code
def sample_random_position(self, off_intersection=True):
    """Sample random open position on the edges of the maze."""
    # First find edges
    neg_maze = 1 - self.maze
    v_edges = np.logical_and(neg_maze[1:], neg_maze[:-1])
    h_edges = np.logical_and(neg_maze[:, 1:], neg_maze[:, :-1])
    v_edges = np.stack(np.nonzero(v_edges)[::-1]).T
    h_edges = np.stack(np.nonzero(h_edges)[::-1]).T
    num_h_edges = len(h_edges)
    num_v_edges = len(v_edges)
    num_edges = num_h_edges + num_v_edges
    if np.random.rand() < float(num_h_edges) / num_edges:
        # Pick a horizontal edge
        edge = h_edges[np.random.choice(num_h_edges)]
        position = edge
        if off_intersection:
            position = edge + np.random.rand() * np.array([1., 0.])
    else:
        # Pick a vertical edge
        edge = v_edges[np.random.choice(num_v_edges)]
        position = edge
        if off_intersection:
            position = edge + np.random.rand() * np.array([0., 1.])
    position = self.half_grid_side + position * self.grid_side
    return position
def to_background_grid(self, line_thickness=0.01, **color)

Get static grid of background lines as list of sprites.

These lines are in the channels of the maze and when rendered can provide a nice visual effect.

Args

line_thickness
Float. How thick the grid lines should be.
color
Dict. May contain keys {'c0', 'c1', 'c2'} for the background line sprite color factors.

Returns

grid_sprites
List of sprites, the grid lines.
Expand source code
def to_background_grid(self, line_thickness=0.01, **color):
    """Get static grid of background lines as list of sprites.

    These lines are in the channels of the maze and when rendered can
    provide a nice visual effect.
    
    Args:
        line_thickness: Float. How thick the grid lines should be.
        color: Dict. May contain keys {'c0', 'c1', 'c2'} for the background
            line sprite color factors.

    Returns:
        grid_sprites: List of sprites, the grid lines.
    """
    grid_sprites = []
    def _add_sprite(min_x, max_x, min_y, max_y):
        shape = np.array([
            [min_x, min_y], [max_x, min_y], [max_x, max_y], [min_x, max_y]
        ])
        grid_sprites.append(sprite.Sprite(x=0., y=0., shape=shape, **color))
    
    for x in self.side_vertices:
        min_x = x - 0.5 * line_thickness
        max_x = x + 0.5 * line_thickness
        _add_sprite(min_x, max_x, 0., 1.)

    for y in self.side_vertices:
        min_y = y - 0.5 * line_thickness
        max_y = y + 0.5 * line_thickness
        _add_sprite(0., 1., min_y, max_y)

    return grid_sprites
def to_sprites(self, **color)

Turn a maze matrix into a list of sprites.

Each sprite is a single square for one brick in the maze walls.

Args

color
Dict. May contain keys {'c0', 'c1', 'c2'} for the wall sprite color factors.

Returns

sprites
List of maze wall sprites.
Expand source code
def to_sprites(self, **color):
    """Turn a maze matrix into a list of sprites.
    
    Each sprite is a single square for one brick in the maze walls.

    Args:
        color: Dict. May contain keys {'c0', 'c1', 'c2'} for the wall sprite
            color factors.

    Returns:
        sprites: List of maze wall sprites.
    """
    maze_size = self.maze_size
    vertex_linspace = np.linspace(0., 1., maze_size + 1)
    sprites = []
    for x in range(maze_size):
        for y in range(maze_size):
            if self.maze[y, x]:
                shape = np.array([
                    [vertex_linspace[x], vertex_linspace[y]],
                    [vertex_linspace[x], vertex_linspace[y + 1]],
                    [vertex_linspace[x + 1], vertex_linspace[y + 1]],
                    [vertex_linspace[x + 1], vertex_linspace[y]],
                ])
                new_sprite = sprite.Sprite(x=0., y=0., shape=shape, **color)
                sprites.append(new_sprite)
    return sprites
def valid_directions(self, i, j)

Computes the open neighbords of the (i, j) cell.

Expand source code
def valid_directions(self, i, j):
    """Computes the open neighbords of the (i, j) cell."""
    valid_directions = np.array([
        [self.open_vertex(k, j) for k in [i - 1, i + 1]],
        [self.open_vertex(i, k) for k in [j - 1, j + 1]],
    ])
    return valid_directions