Skip to content

Instantly share code, notes, and snippets.

@Ovid

Ovid/mazy.py Secret

Last active December 29, 2023 12:27
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Ovid/6f67ebb40980f2a729b8ebf7c843bb0f to your computer and use it in GitHub Desktop.
Save Ovid/6f67ebb40980f2a729b8ebf7c843bb0f to your computer and use it in GitHub Desktop.
A simple ASCII maze program
import random
from dataclasses import dataclass
from enum import StrEnum
"""
Prints out a random ASCII maze, such as:
_______________________________
|_ _ |_ _ _ | _ _ | _|
| |_ _ |_ |_ _| | _| |_ |
| |_| |_ _ _| | _ _|_ _ _| |
| | _| | | | | _ _ | _|
| _| |_ _|_ _|_ _| |_ _ |_ |
| | |_ _ _ _ _ _ |_ _ |
| |_| |_ _ |_ | _ |_ _ | |
|_ _ _|_ |_ |_ _|_ | _ _| |
| _ _ _ _|_ _ _ |_| | _ _|
|_ _|_ _ _ _ _ _ _ _ _ _|_ _ _|
Nothing fancy here. I'm just learning Python.
"""
# For a program this simple, dataclasses are overkill, but I wanted
# to understand pylance better
@dataclass
class MazeCell:
cell: dict[str, bool]
def __len__(self) -> int:
return len(self.cell)
def get(self, direction: str) -> bool | None:
return self.cell[direction]
def __setitem__(self, direction: str, value: bool) -> None:
self.cell[direction] = value
@dataclass
class MazeRow:
row: list[MazeCell]
def __len__(self) -> int:
return len(self.row)
def __getitem__(self,index: int) -> MazeCell:
return self.row[index]
@dataclass
class Maze:
maze: list[MazeRow]
def __len__(self) -> int:
return len(self.maze)
def __getitem__(self,index: int) -> MazeRow:
return self.maze[index]
class Direction(StrEnum):
NORTH = "north"
SOUTH = "south"
EAST = "east"
WEST = "west"
def render_maze(maze: Maze) -> str:
height = len(maze)
width = len(maze[0])
as_string = "_" * (1 + width * 2) + "\n"
for y in range(height):
as_string += "|"
for x in range(width):
cell = maze[y][x]
as_string += " " if cell.get(Direction.SOUTH) else "_"
as_string += " " if cell.get(Direction.EAST) else "|"
as_string += "\n"
return as_string
def have_not_visited(x: int, y: int, maze: Maze) -> bool:
height = len(maze)
width = len(maze[0])
if x < 0 or y < 0 or x > width - 1 or y > height - 1:
# we're out of bounds
return False
# if there are no keys in the dictionary, we haven't visited
return len(maze[y][x]) == 0
def create_tunnel_from(x: int, y: int, maze: Maze) -> None:
opposite = {
Direction.NORTH: Direction.SOUTH,
Direction.SOUTH: Direction.NORTH,
Direction.WEST: Direction.EAST,
Direction.EAST: Direction.WEST,
}
directions = list(opposite.keys())
random.shuffle(directions)
for direction in directions:
new_x, new_y = x, y
match direction:
case Direction.EAST:
new_x += 1
case Direction.WEST:
new_x -= 1
case Direction.SOUTH:
new_y += 1
case Direction.NORTH:
new_y -= 1
if have_not_visited(new_x, new_y, maze):
# make a two way "path" between the cells
maze[y][x][direction] = True
maze[new_y][new_x][opposite[direction]] = True
create_tunnel_from(new_x, new_y, maze)
def create_maze(height: int, width: int) -> Maze:
maze: Maze
# why can't I make this type warning go away?
maze = [[{} for x in range(width)] for y in range(height)] # type: ignore
# note that create_tunnel_from() is recursive and will mutate the maze
# reference
create_tunnel_from(0, 0, maze)
return maze
def main():
width = 15
height = 10
maze = create_maze(height, width)
print(render_maze(maze))
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment