mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-12 07:40:50 -08:00
188 lines
5.4 KiB
Python
188 lines
5.4 KiB
Python
import enum
|
|
import random
|
|
from dataclasses import dataclass
|
|
from typing import List, Tuple
|
|
|
|
# Python translation by Frank Palazzolo - 2/2021
|
|
|
|
|
|
class Maze:
|
|
def __init__(self, width: int, length: int) -> None:
|
|
assert width >= 2 and length >= 2
|
|
used: List[List[int]] = []
|
|
walls: List[List[int]] = []
|
|
for _ in range(length):
|
|
used.append([0] * width)
|
|
walls.append([0] * width)
|
|
|
|
# Pick a random entrance, mark as used
|
|
enter_col = random.randint(0, width - 1)
|
|
used[0][enter_col] = 1
|
|
|
|
self.used = used
|
|
self.walls = walls
|
|
self.enter_col = enter_col
|
|
self.width = width
|
|
self.length = length
|
|
|
|
def add_exit(self) -> None:
|
|
"""Modifies 'walls' to add an exit to the maze."""
|
|
col = random.randint(0, self.width - 1)
|
|
row = self.length - 1
|
|
self.walls[row][col] = self.walls[row][col] + 1
|
|
|
|
def display(self) -> None:
|
|
for col in range(self.width):
|
|
if col == self.enter_col:
|
|
print(". ", end="")
|
|
else:
|
|
print(".--", end="")
|
|
print(".")
|
|
for row in range(self.length):
|
|
print("I", end="")
|
|
for col in range(self.width):
|
|
if self.walls[row][col] < 2:
|
|
print(" I", end="")
|
|
else:
|
|
print(" ", end="")
|
|
print()
|
|
for col in range(self.width):
|
|
if self.walls[row][col] in [0, 2]:
|
|
print(":--", end="")
|
|
else:
|
|
print(": ", end="")
|
|
print(".")
|
|
|
|
|
|
class Direction(enum.Enum):
|
|
LEFT = 0
|
|
UP = 1
|
|
RIGHT = 2
|
|
DOWN = 3
|
|
|
|
|
|
@dataclass
|
|
class Position:
|
|
row: int
|
|
col: int
|
|
|
|
|
|
# Give Exit directions nice names
|
|
EXIT_DOWN = 1
|
|
EXIT_RIGHT = 2
|
|
|
|
|
|
def main() -> None:
|
|
print_intro()
|
|
width, length = get_maze_dimensions()
|
|
maze = build_maze(width, length)
|
|
maze.display()
|
|
|
|
|
|
def print_intro() -> None:
|
|
print(" " * 28 + "AMAZING PROGRAM")
|
|
print(" " * 15 + "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n\n")
|
|
|
|
|
|
def build_maze(width: int, length: int) -> Maze:
|
|
"""Build two 2D arrays."""
|
|
#
|
|
# used:
|
|
# Initially set to zero, unprocessed cells
|
|
# Filled in with consecutive non-zero numbers as cells are processed
|
|
#
|
|
# walls:
|
|
# Initially set to zero, (all paths blocked)
|
|
# Remains 0 if there is no exit down or right
|
|
# Set to 1 if there is an exit down
|
|
# Set to 2 if there is an exit right
|
|
# Set to 3 if there are exits down and right
|
|
assert width >= 2 and length >= 2
|
|
|
|
maze = Maze(width, length)
|
|
position = Position(row=0, col=maze.enter_col)
|
|
count = 2
|
|
|
|
while count != width * length + 1:
|
|
possible_dirs = get_possible_directions(maze, position)
|
|
|
|
# If we can move in a direction, move and make opening
|
|
if len(possible_dirs) != 0:
|
|
position, count = make_opening(maze, possible_dirs, position, count)
|
|
# otherwise, move to the next used cell, and try again
|
|
else:
|
|
while True:
|
|
if position.col != width - 1:
|
|
position.col += 1
|
|
elif position.row != length - 1:
|
|
position.row, position.col = position.row + 1, 0
|
|
else:
|
|
position.row, position.col = 0, 0
|
|
if maze.used[position.row][position.col] != 0:
|
|
break
|
|
|
|
maze.add_exit()
|
|
return maze
|
|
|
|
|
|
def make_opening(
|
|
maze: Maze,
|
|
possible_dirs: List[Direction],
|
|
pos: Position,
|
|
count: int,
|
|
) -> Tuple[Position, int]:
|
|
"""
|
|
Attention! This modifies 'used' and 'walls'
|
|
"""
|
|
direction = random.choice(possible_dirs)
|
|
if direction == Direction.LEFT:
|
|
pos.col = pos.col - 1
|
|
maze.walls[pos.row][pos.col] = EXIT_RIGHT
|
|
elif direction == Direction.UP:
|
|
pos.row = pos.row - 1
|
|
maze.walls[pos.row][pos.col] = EXIT_DOWN
|
|
elif direction == Direction.RIGHT:
|
|
maze.walls[pos.row][pos.col] = maze.walls[pos.row][pos.col] + EXIT_RIGHT
|
|
pos.col = pos.col + 1
|
|
elif direction == Direction.DOWN:
|
|
maze.walls[pos.row][pos.col] = maze.walls[pos.row][pos.col] + EXIT_DOWN
|
|
pos.row = pos.row + 1
|
|
maze.used[pos.row][pos.col] = count
|
|
count += 1
|
|
return pos, count
|
|
|
|
|
|
def get_possible_directions(maze: Maze, pos: Position) -> List[Direction]:
|
|
"""
|
|
Get a list of all directions that are not blocked.
|
|
|
|
Also ignore hit cells that we have already processed
|
|
"""
|
|
possible_dirs = list(Direction)
|
|
if pos.col == 0 or maze.used[pos.row][pos.col - 1] != 0:
|
|
possible_dirs.remove(Direction.LEFT)
|
|
if pos.row == 0 or maze.used[pos.row - 1][pos.col] != 0:
|
|
possible_dirs.remove(Direction.UP)
|
|
if pos.col == maze.width - 1 or maze.used[pos.row][pos.col + 1] != 0:
|
|
possible_dirs.remove(Direction.RIGHT)
|
|
if pos.row == maze.length - 1 or maze.used[pos.row + 1][pos.col] != 0:
|
|
possible_dirs.remove(Direction.DOWN)
|
|
return possible_dirs
|
|
|
|
|
|
def get_maze_dimensions() -> Tuple[int, int]:
|
|
while True:
|
|
input_str = input("What are your width and length?")
|
|
if input_str.count(",") == 1:
|
|
width_str, length_str = input_str.split(",")
|
|
width = int(width_str)
|
|
length = int(length_str)
|
|
if width > 1 and length > 1:
|
|
break
|
|
print("Meaningless dimensions. Try again.")
|
|
return width, length
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|