├── FUNDING.yml ├── images ├── maze.png ├── path.png ├── hexmaze.png ├── maskmaze.png ├── mazespsd.psd ├── polygon.png ├── colormode1.png ├── colormode2.png ├── ellersMaze.png ├── heuristics.png ├── kruskalMaze.png ├── polarmaze.png ├── primsMaze.png ├── wilsonMaze.png ├── aldousBroderMaze.png ├── binarytreeMaze.png ├── growingtreeMaze.png ├── huntandkillMaze.png ├── sidewinderMaze.png ├── simplePrimesMaze.png └── recursiveBacktracker.png ├── constants.py ├── ui ├── __pycache__ │ ├── ui.cpython-39.pyc │ ├── colors.cpython-39.pyc │ └── setup.cpython-39.pyc ├── colors.py ├── setup.py └── ui.py ├── __pycache__ └── constants.cpython-39.pyc ├── classes ├── __pycache__ │ ├── cell.cpython-39.pyc │ ├── grid.cpython-39.pyc │ ├── mask.cpython-39.pyc │ ├── color.cpython-39.pyc │ ├── ellers.cpython-39.pyc │ ├── hexCell.cpython-39.pyc │ ├── hexGrid.cpython-39.pyc │ ├── kruskal.cpython-39.pyc │ ├── prims.cpython-39.pyc │ ├── wilson.cpython-39.pyc │ ├── __init__.cpython-39.pyc │ ├── heuristic.cpython-39.pyc │ ├── polarCell.cpython-39.pyc │ ├── polarGrid.cpython-39.pyc │ ├── aldousBroder.cpython-39.pyc │ ├── binaryTree.cpython-39.pyc │ ├── growingTree.cpython-39.pyc │ ├── huntandkill.cpython-39.pyc │ ├── sideWinder.cpython-39.pyc │ ├── weightedCell.cpython-39.pyc │ ├── weightedGrid.cpython-39.pyc │ └── recursiveBacktracker.cpython-39.pyc ├── __init__.py ├── polarCell.py ├── weightedCell.py ├── mask.py ├── color.py ├── weightedGrid.py ├── heuristic.py ├── aldousBroder.py ├── binaryTree.py ├── growingTree.py ├── wilson.py ├── polarGrid.py ├── sideWinder.py ├── huntandkill.py ├── hexGrid.py ├── hexCell.py ├── recursiveBacktracker.py ├── cell.py ├── kruskal.py ├── prims.py ├── ellers.py └── grid.py ├── utils └── resizeImage.py ├── weightedMaze.py ├── hexMaze.py ├── polarMaze.py ├── imageMaze.py ├── main.py └── README.md /FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: auctux 2 | ko_fi: auctux 3 | -------------------------------------------------------------------------------- /images/maze.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/images/maze.png -------------------------------------------------------------------------------- /images/path.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/images/path.png -------------------------------------------------------------------------------- /images/hexmaze.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/images/hexmaze.png -------------------------------------------------------------------------------- /images/maskmaze.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/images/maskmaze.png -------------------------------------------------------------------------------- /images/mazespsd.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/images/mazespsd.psd -------------------------------------------------------------------------------- /images/polygon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/images/polygon.png -------------------------------------------------------------------------------- /images/colormode1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/images/colormode1.png -------------------------------------------------------------------------------- /images/colormode2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/images/colormode2.png -------------------------------------------------------------------------------- /images/ellersMaze.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/images/ellersMaze.png -------------------------------------------------------------------------------- /images/heuristics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/images/heuristics.png -------------------------------------------------------------------------------- /images/kruskalMaze.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/images/kruskalMaze.png -------------------------------------------------------------------------------- /images/polarmaze.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/images/polarmaze.png -------------------------------------------------------------------------------- /images/primsMaze.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/images/primsMaze.png -------------------------------------------------------------------------------- /images/wilsonMaze.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/images/wilsonMaze.png -------------------------------------------------------------------------------- /images/aldousBroderMaze.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/images/aldousBroderMaze.png -------------------------------------------------------------------------------- /images/binarytreeMaze.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/images/binarytreeMaze.png -------------------------------------------------------------------------------- /images/growingtreeMaze.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/images/growingtreeMaze.png -------------------------------------------------------------------------------- /images/huntandkillMaze.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/images/huntandkillMaze.png -------------------------------------------------------------------------------- /images/sidewinderMaze.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/images/sidewinderMaze.png -------------------------------------------------------------------------------- /images/simplePrimesMaze.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/images/simplePrimesMaze.png -------------------------------------------------------------------------------- /constants.py: -------------------------------------------------------------------------------- 1 | width, height = 800, 800 2 | size = (width, height) 3 | 4 | cell_size = 40 5 | rows = width//cell_size 6 | cols = height//cell_size 7 | -------------------------------------------------------------------------------- /images/recursiveBacktracker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/images/recursiveBacktracker.png -------------------------------------------------------------------------------- /ui/__pycache__/ui.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/ui/__pycache__/ui.cpython-39.pyc -------------------------------------------------------------------------------- /__pycache__/constants.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/__pycache__/constants.cpython-39.pyc -------------------------------------------------------------------------------- /ui/__pycache__/colors.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/ui/__pycache__/colors.cpython-39.pyc -------------------------------------------------------------------------------- /ui/__pycache__/setup.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/ui/__pycache__/setup.cpython-39.pyc -------------------------------------------------------------------------------- /classes/__pycache__/cell.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/classes/__pycache__/cell.cpython-39.pyc -------------------------------------------------------------------------------- /classes/__pycache__/grid.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/classes/__pycache__/grid.cpython-39.pyc -------------------------------------------------------------------------------- /classes/__pycache__/mask.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/classes/__pycache__/mask.cpython-39.pyc -------------------------------------------------------------------------------- /classes/__pycache__/color.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/classes/__pycache__/color.cpython-39.pyc -------------------------------------------------------------------------------- /classes/__pycache__/ellers.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/classes/__pycache__/ellers.cpython-39.pyc -------------------------------------------------------------------------------- /classes/__pycache__/hexCell.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/classes/__pycache__/hexCell.cpython-39.pyc -------------------------------------------------------------------------------- /classes/__pycache__/hexGrid.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/classes/__pycache__/hexGrid.cpython-39.pyc -------------------------------------------------------------------------------- /classes/__pycache__/kruskal.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/classes/__pycache__/kruskal.cpython-39.pyc -------------------------------------------------------------------------------- /classes/__pycache__/prims.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/classes/__pycache__/prims.cpython-39.pyc -------------------------------------------------------------------------------- /classes/__pycache__/wilson.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/classes/__pycache__/wilson.cpython-39.pyc -------------------------------------------------------------------------------- /classes/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/classes/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /classes/__pycache__/heuristic.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/classes/__pycache__/heuristic.cpython-39.pyc -------------------------------------------------------------------------------- /classes/__pycache__/polarCell.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/classes/__pycache__/polarCell.cpython-39.pyc -------------------------------------------------------------------------------- /classes/__pycache__/polarGrid.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/classes/__pycache__/polarGrid.cpython-39.pyc -------------------------------------------------------------------------------- /classes/__pycache__/aldousBroder.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/classes/__pycache__/aldousBroder.cpython-39.pyc -------------------------------------------------------------------------------- /classes/__pycache__/binaryTree.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/classes/__pycache__/binaryTree.cpython-39.pyc -------------------------------------------------------------------------------- /classes/__pycache__/growingTree.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/classes/__pycache__/growingTree.cpython-39.pyc -------------------------------------------------------------------------------- /classes/__pycache__/huntandkill.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/classes/__pycache__/huntandkill.cpython-39.pyc -------------------------------------------------------------------------------- /classes/__pycache__/sideWinder.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/classes/__pycache__/sideWinder.cpython-39.pyc -------------------------------------------------------------------------------- /classes/__pycache__/weightedCell.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/classes/__pycache__/weightedCell.cpython-39.pyc -------------------------------------------------------------------------------- /classes/__pycache__/weightedGrid.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/classes/__pycache__/weightedGrid.cpython-39.pyc -------------------------------------------------------------------------------- /classes/__pycache__/recursiveBacktracker.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Josephbakulikira/Procedural-Maze-Generator-Algorithms/HEAD/classes/__pycache__/recursiveBacktracker.cpython-39.pyc -------------------------------------------------------------------------------- /utils/resizeImage.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | from PIL import Image 3 | from constants import * 4 | 5 | im = Image.open("./images/shape0.jpg") 6 | w, h = im.size 7 | 8 | im_resized = im.resize((w, h), Image.ANTIALIAS) 9 | im_resized.save("./images/shape0Resized.png", "PNG") 10 | -------------------------------------------------------------------------------- /classes/__init__.py: -------------------------------------------------------------------------------- 1 | from classes.binaryTree import BinaryTree 2 | from classes.sideWinder import SideWinder 3 | from classes.aldousBroder import AldousBroder 4 | from classes.huntandkill import HuntAndKill 5 | from classes.recursiveBacktracker import RecursiveBacktracker 6 | from classes.kruskal import Kruskals 7 | from classes.wilson import Wilson 8 | from classes.prims import SimplePrims, Prims 9 | from classes.grid import Grid 10 | from classes.growingTree import GrowingTree 11 | from classes.ellers import Ellers -------------------------------------------------------------------------------- /classes/polarCell.py: -------------------------------------------------------------------------------- 1 | from classes.cell import Cell 2 | 3 | class PolarCell(Cell): 4 | def __init__(self, x, y, size): 5 | super().__init__(x, y, size) 6 | self.clockwise = None 7 | self.c_clockwise = None 8 | self.inward = None 9 | self.outward = [] 10 | self.neighbours = [] 11 | 12 | def SetNeighbours(self): 13 | self.neighbours = [] 14 | if self.clockwise: 15 | self.neighbours.append(self.clockwise) 16 | if self.c_clockwise: 17 | self.neighbours.append(self.c_clockwise) 18 | if self.inward: 19 | self.neighbours.append(self.inward) 20 | self.neighbours += self.outward 21 | 22 | return self.neighbours 23 | -------------------------------------------------------------------------------- /ui/colors.py: -------------------------------------------------------------------------------- 1 | white = (255,255,255) 2 | blue = (0,0,255) 3 | green = (0,255,0) 4 | red = (255,0,0) 5 | black = (0,0,0) 6 | orange = (255,100,10) 7 | yellow = (255,255,0) 8 | blue_green = (0,255,170) 9 | marroon = (115,0,0) 10 | lime = (180,255,100) 11 | pink = (255,100,180) 12 | purple = (240,0,255) 13 | gray = (127,127,127) 14 | magenta = (255,0,230) 15 | brown = (100,40,0) 16 | forest_green = (0,50,0) 17 | navy_blue = (0,0,100) 18 | rust = (210,150,75) 19 | dandilion_yellow = (255,200,0) 20 | highlighter = (255,255,100) 21 | sky_blue = (0,255,255) 22 | light_gray = (200,200,200) 23 | dark_gray = (50,50,50) 24 | tan = (230,220,170) 25 | coffee_brown =(200,190,140) 26 | moon_glow = (235,245,255) 27 | -------------------------------------------------------------------------------- /classes/weightedCell.py: -------------------------------------------------------------------------------- 1 | from classes.cell import Cell 2 | from classes.heuristic import Heuristic 3 | 4 | class WeightedCell(Cell): 5 | def __init__(self, x, y, size): 6 | super().__init__(x, y, size) 7 | self.weight = 1 8 | self.neighbours = [] 9 | def CalculateHeuristic(self, rows, cols): 10 | weights = Heuristic(rows, cols) 11 | pending = [self] 12 | while len(pending) > 0: 13 | pending.sort(key=lambda cell: weights.GetRecord(cell)) 14 | this = pending[0] 15 | pending.remove(this) 16 | 17 | for cell in this.connections: 18 | val = 0 if not weights.GetRecord(this) else weights.GetRecord(this) 19 | total = val + cell.weight 20 | if not weights.GetRecord(cell) or total < weights.GetRecord(cell): 21 | pending.append(cell) 22 | weights.SetRecord(cell, total) 23 | weights.SetRecord(self, 0) 24 | return weights 25 | -------------------------------------------------------------------------------- /ui/setup.py: -------------------------------------------------------------------------------- 1 | from ui.ui import * 2 | from ui.colors import * 3 | from constants import width, height 4 | 5 | # panel = Panel() 6 | # 7 | # StartButton = Button("Start", (width - 270, 180), 150, 50, 0, (10, 10, 30), (255, 255, 255)) 8 | PressEnter = TextUI("press 'Enter or Return' to Start", (width//2, height//2), white) 9 | PressEnter.fontSize = 25 10 | 11 | # AlgorithmChoice = DropDownButton("Select", (width - 350, 400), 300, 50, 11, 2, (10, 10, 30), (255, 255, 255)) 12 | # AlgorithmChoice.childs[0].text = "Wilson" 13 | # AlgorithmChoice.childs[1].text = "Binary Tree" 14 | # AlgorithmChoice.childs[2].text = "Kruskal's" 15 | # AlgorithmChoice.childs[3].text = "SideWinder" 16 | # AlgorithmChoice.childs[4].text = "Hund and Kill" 17 | # AlgorithmChoice.childs[5].text = "Aldous Broder" 18 | # AlgorithmChoice.childs[6].text = "Recursive Backtracker" 19 | # AlgorithmChoice.childs[7].text = "Simplified Prims" 20 | # AlgorithmChoice.childs[8].text = "Prims" 21 | # AlgorithmChoice.childs[9].text = "Growing Tree" 22 | # AlgorithmChoice.childs[10].text = "Eller's" 23 | 24 | # AlgorithmChoice.currentIndex = 1 25 | -------------------------------------------------------------------------------- /classes/mask.py: -------------------------------------------------------------------------------- 1 | import random 2 | from classes.grid import Grid 3 | 4 | class Mask: 5 | def __init__(self, rows, cols): 6 | self.rows = rows 7 | self.cols = cols 8 | self.boolGrid = [[True for y in range(rows)] for x in range(cols)] 9 | self.count = 0 10 | 11 | def Count(self): 12 | count = 0 13 | for x in range(self.cols): 14 | for y in range(self.rows): 15 | if self.boolGrid[x][y]: 16 | count += 1 17 | self.count = count 18 | return count 19 | 20 | def Random(self): 21 | while True: 22 | x = random.randint(0, self.cols-1) 23 | y = random.randint(0, self.rows-1) 24 | if self.boolGrid[x][y]: 25 | return x, y 26 | 27 | class GridMask(Grid): 28 | def __init__(self, rows, cols, cell_size, mask): 29 | super().__init__(rows, cols, cell_size) 30 | self.mask = mask 31 | 32 | def UpdateGrid(self): 33 | for x in range(self.cols): 34 | for y in range(self.rows): 35 | if self.mask.boolGrid[x][y] == False: 36 | self.cells[x][y] = None 37 | 38 | self.PrepareGrid() 39 | 40 | def GetRandomCell(self): 41 | x , y = self.mask.Random() 42 | return self.cells[x][y] 43 | 44 | def GetSize(self): 45 | return self.mask.Count() 46 | -------------------------------------------------------------------------------- /weightedMaze.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from constants import * 3 | from ui.colors import * 4 | from classes.recursiveBacktracker import RecursiveBacktracker 5 | from classes.weightedGrid import WeightedGrid 6 | 7 | # Initialize pygame 8 | pygame.init() 9 | screen = pygame.display.set_mode(size) 10 | clock = pygame.time.Clock() 11 | fps = 30 12 | 13 | grid = WeightedGrid(rows, cols, cell_size) 14 | grid.ConfigureCells() 15 | recursive_backtracker = RecursiveBacktracker(grid, "GREEN") 16 | recursive_backtracker.starting_node = grid.cells[0][0] 17 | recursive_backtracker.end_node = grid.cells[cols-1][rows-1] 18 | recursive_backtracker.starting_node.isStartingNode = True 19 | recursive_backtracker.end_node.isgoalNode = True 20 | 21 | show_text = False 22 | color_mode = False 23 | show_path = False 24 | 25 | run = True 26 | while run: 27 | screen.fill(black) 28 | # Set Caption and fps 29 | clock.tick(fps) 30 | frame_rate = int(clock.get_fps()) 31 | pygame.display.set_caption(f"Maze Generator - FPS: {frame_rate}") 32 | 33 | # Handle events 34 | for event in pygame.event.get(): 35 | if event.type == pygame.QUIT: 36 | run = False 37 | if event.type == pygame.KEYDOWN: 38 | if event.key == pygame.K_ESCAPE: 39 | run = False 40 | elif event.key == pygame.K_h: 41 | show_text = not show_text 42 | elif event.key == pygame.K_SPACE: 43 | color_mode = not color_mode 44 | elif event.key == pygame.K_s: 45 | show_path = not show_path 46 | 47 | recursive_backtracker.Generate(screen, show_text, color_mode, show_path) 48 | # hexGrid.Show(screen, show_text, color_mode, show_path) 49 | # pygame.display.flip() 50 | 51 | pygame.quit() 52 | -------------------------------------------------------------------------------- /classes/color.py: -------------------------------------------------------------------------------- 1 | from ui.colors import * 2 | import pygame 3 | import colorsys 4 | 5 | def hsv_to_rgb(h, s, v): 6 | return tuple(round(i * 255) for i in colorsys.hsv_to_rgb(h, s, v)) 7 | 8 | class GridColor: 9 | heuristics = None 10 | farthest = None 11 | max_distance = None 12 | 13 | def __init__(self, color_str = "RED"): 14 | self.color = color_str 15 | # print(self.color) 16 | def distances(self, heuristics, goal, start, grid): 17 | self.heuristics = heuristics 18 | self.farthest, self.max_distance = self.heuristics.GetFarthest(goal, start, grid) 19 | 20 | def UpdateColor(self, cell): 21 | if self.color != "HSV": 22 | val = self.heuristics.GetRecord(cell) 23 | distance = 0 if val == None else val 24 | intensity = (self.max_distance - distance)/self.max_distance 25 | dark = min(int(255 * intensity), 255) 26 | bright = min( int(128 + (127 * intensity)),255) 27 | 28 | colors = { 29 | "RED": (bright, dark, dark), 30 | "BLUE": (dark, dark, bright), 31 | "GREEN": (dark, bright, dark), 32 | "YELLOW": (bright, bright, dark), 33 | "CYAN": (dark, bright, bright), 34 | "PURPLE": (bright, dark, bright), 35 | "PURPLE_E": (bright, dark, bright), 36 | "GREEN_E":(0, bright, 0), 37 | "BLUE_E": (0, 0, bright), 38 | "RED_E": (bright, 0, 0) 39 | } 40 | 41 | return tuple(colors[self.color]) 42 | else: 43 | val = self.heuristics.GetRecord(cell) 44 | hue = 0 if val == None else val 45 | _color = hsv_to_rgb((hue/360), 1, 1) 46 | # print(_color) 47 | return _color 48 | -------------------------------------------------------------------------------- /hexMaze.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from constants import * 3 | from ui.colors import * 4 | from classes.recursiveBacktracker import RecursiveBacktracker 5 | from classes.hexGrid import HexGrid 6 | 7 | # Initialize pygame 8 | pygame.init() 9 | screen = pygame.display.set_mode(size) 10 | clock = pygame.time.Clock() 11 | fps = 30 12 | 13 | hexGrid = HexGrid(13, 11, cell_size) 14 | # hexGrid.PrepareGrid() 15 | hexGrid.ConfigureCells() 16 | recursive_backtracker = RecursiveBacktracker(hexGrid, "RED") 17 | recursive_backtracker.starting_node = hexGrid.cells[0][0] 18 | recursive_backtracker.end_node = hexGrid.cells[10][12] 19 | recursive_backtracker.starting_node.isStartingNode = True 20 | recursive_backtracker.end_node.isgoalNode = True 21 | 22 | show_text = False 23 | color_mode = False 24 | show_path = False 25 | 26 | run = True 27 | while run: 28 | screen.fill(black) 29 | # Set Caption and fps 30 | clock.tick(fps) 31 | frame_rate = int(clock.get_fps()) 32 | pygame.display.set_caption(f"Maze Generator - FPS: {frame_rate}") 33 | 34 | # Handle events 35 | for event in pygame.event.get(): 36 | if event.type == pygame.QUIT: 37 | run = False 38 | if event.type == pygame.KEYDOWN: 39 | if event.key == pygame.K_ESCAPE: 40 | run = False 41 | elif event.key == pygame.K_h: 42 | show_text = not show_text 43 | elif event.key == pygame.K_SPACE: 44 | color_mode = not color_mode 45 | elif event.key == pygame.K_s: 46 | show_path = not show_path 47 | 48 | recursive_backtracker.Generate(screen, show_text, color_mode, show_path) 49 | # hexGrid.Show(screen, show_text, color_mode, show_path) 50 | # pygame.display.flip() 51 | 52 | pygame.image.save(screen, "./images/hexmaze.png") 53 | pygame.quit() 54 | -------------------------------------------------------------------------------- /polarMaze.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from constants import * 3 | from ui.colors import * 4 | from classes.recursiveBacktracker import RecursiveBacktracker 5 | from classes.polarGrid import PolarGrid 6 | # Initialize pygame 7 | pygame.init() 8 | screen = pygame.display.set_mode(size) 9 | clock = pygame.time.Clock() 10 | fps = 30 11 | 12 | polarGrid = PolarGrid(8, 1, 50) 13 | polarGrid.cells = polarGrid.PrepareGrid() 14 | polarGrid.ConfigureCells() 15 | recursive_backtracker = RecursiveBacktracker(polarGrid, "PURPLE") 16 | recursive_backtracker.starting_node = polarGrid.cells[0][0] 17 | recursive_backtracker.starting_node.isStartingNode = True 18 | recursive_backtracker.end_node = polarGrid.cells[0][0] 19 | recursive_backtracker.end_node.isgoalNode = True 20 | 21 | show_text = False 22 | color_mode = False 23 | show_path = False 24 | 25 | run = True 26 | while run: 27 | screen.fill(black) 28 | # Set Caption and fps 29 | clock.tick(fps) 30 | frame_rate = int(clock.get_fps()) 31 | pygame.display.set_caption(f"Maze Generator - FPS: {frame_rate}") 32 | 33 | # Handle events 34 | for event in pygame.event.get(): 35 | if event.type == pygame.QUIT: 36 | run = False 37 | if event.type == pygame.KEYDOWN: 38 | if event.key == pygame.K_ESCAPE: 39 | run = False 40 | elif event.key == pygame.K_h: 41 | show_text = not show_text 42 | elif event.key == pygame.K_SPACE: 43 | color_mode = not color_mode 44 | elif event.key == pygame.K_s: 45 | show_path = not show_path 46 | 47 | recursive_backtracker.Generate(screen, show_text, color_mode, show_path) 48 | # polarGrid.Show(screen, show_text, color_mode, show_path) 49 | pygame.display.flip() 50 | 51 | pygame.image.save(screen, "./images/polarmaze.png") 52 | pygame.quit() 53 | -------------------------------------------------------------------------------- /classes/weightedGrid.py: -------------------------------------------------------------------------------- 1 | from classes.grid import Grid 2 | from classes.weightedCell import WeightedCell 3 | 4 | class WeightedGrid(Grid): 5 | def __init__(self, rows, cols, cell_size): 6 | super().__init__(rows, cols, cell_size) 7 | self.farthest = None 8 | self.maximum = None 9 | 10 | def Distances(self, start, goal, distance): 11 | self.heuristics = distance 12 | self.farthest, self.maximum = self.heuristics.GetFarthest(goal, start, self) 13 | 14 | def PrepareGrid(self): 15 | self.cells = [[None for i in range(self.rows)] for j in range(self.cols)] 16 | for x in range(self.cols): 17 | for y in range(self.rows): 18 | self.cells[x][y] = WeightedCell(x, y, self.cell_size) 19 | 20 | def ConfigureCells(self): 21 | for x in range(self.cols): 22 | for y in range(self.rows): 23 | if self.cells[x][y]: 24 | self.cells[x][y].neighbours = [] 25 | # East neighbour cell 26 | if x+1 < self.cols and self.cells[x+1][y]: 27 | self.cells[x][y].East = self.cells[x+1][y] 28 | self.cells[x][y].neighbours.append(self.cells[x+1][y]) 29 | # West neightbour cell 30 | if x-1 >= 0 and self.cells[x-1][y]: 31 | self.cells[x][y].West = self.cells[x-1][y] 32 | self.cells[x][y].neighbours.append(self.cells[x-1][y]) 33 | 34 | # North neighbour cell 35 | if y-1 >= 0 and self.cells[x][y-1]: 36 | self.cells[x][y].North = self.cells[x][y-1] 37 | self.cells[x][y].neighbours.append(self.cells[x][y-1]) 38 | # South neighbour cell 39 | if y+1 < self.rows and self.cells[x][y+1]: 40 | self.cells[x][y].South = self.cells[x][y+1] 41 | self.cells[x][y].neighbours.append(self.cells[x][y+1]) 42 | -------------------------------------------------------------------------------- /classes/heuristic.py: -------------------------------------------------------------------------------- 1 | class Heuristic: 2 | def __init__(self, rows, cols): 3 | self.rows = rows 4 | self.cols = cols 5 | self.cells_record = [[None for y in range(rows)] for x in range(cols)] 6 | 7 | def SetRecord(self, cell, distance): 8 | self.cells_record[cell.x][cell.y] = distance 9 | 10 | def GetFarthest(self, goal, start, grid): 11 | max_distance = 0 12 | farthest= start 13 | for x in range(self.cols): 14 | for y in range(self.rows): 15 | dist = self.cells_record[x][y] if self.cells_record[x][y] else 0 16 | cell = grid.cells[x][y] 17 | if dist > max_distance: 18 | farthest = cell 19 | max_distance = dist 20 | 21 | return farthest, max_distance 22 | 23 | def Merge(self, other): 24 | new_distances = Heuristic(self.rows, self.cols) 25 | for x in range(self.cols): 26 | for y in range(self.rows): 27 | if other.cells_record[x][y] != None: 28 | new_distances.cells_record[x][y] = other.cells_record[x][y] 29 | else: 30 | new_distances.cells_record[x][y] = self.cells_record[x][y] 31 | 32 | return new_distances 33 | 34 | def BacktrackPath(self, goal, start): 35 | current = goal 36 | path_track = Heuristic(self.rows, self.cols) 37 | path_track.SetRecord(current, self.GetRecord(current)) 38 | max_counter = self.rows * self.cols 39 | counter = 0 40 | while current != start: 41 | for cell in current.connections: 42 | if self.GetRecord(cell) < self.GetRecord(current): 43 | path_track.SetRecord(cell, self.GetRecord(cell)) 44 | current = cell 45 | counter += 1 46 | if counter > max_counter: 47 | break 48 | return path_track 49 | 50 | def GetRecord(self, cell): 51 | return self.cells_record[cell.x][cell.y] 52 | -------------------------------------------------------------------------------- /imageMaze.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from constants import * 3 | from ui.colors import * 4 | from classes.recursiveBacktracker import RecursiveBacktracker 5 | from classes.grid import Grid 6 | from classes.mask import Mask, GridMask 7 | import cv2 8 | 9 | # Initialize pygame 10 | pygame.init() 11 | screen = pygame.display.set_mode(size) 12 | clock = pygame.time.Clock() 13 | fps = 30 14 | 15 | image = cv2.imread("./images/polygon.png") 16 | 17 | show_text = False 18 | color_mode = False 19 | show_path = False 20 | 21 | mask = Mask(rows, cols) 22 | for i in range(rows): 23 | for j in range(cols): 24 | 25 | if image[j*cell_size][i*cell_size][0] == 255: 26 | mask.boolGrid[i][j] = False 27 | 28 | maskGrid = GridMask(rows, cols, cell_size, mask) 29 | maskGrid.UpdateGrid() 30 | 31 | recursive_backtracker = RecursiveBacktracker(maskGrid, "PURPLE_E") 32 | recursive_backtracker.starting_node = maskGrid.cells[cols//2][rows//2] 33 | recursive_backtracker.starting_node.isStartingNode = True 34 | recursive_backtracker.end_node = maskGrid.cells[cols//2][0] 35 | recursive_backtracker.end_node.isgoalNode = True 36 | 37 | run = True 38 | while run: 39 | #screen.fill(black) 40 | 41 | # Set Caption and fps 42 | clock.tick(fps) 43 | frame_rate = int(clock.get_fps()) 44 | pygame.display.set_caption(f"Maze Generator - FPS: {frame_rate}") 45 | 46 | # Handle events 47 | for event in pygame.event.get(): 48 | if event.type == pygame.QUIT: 49 | run = False 50 | if event.type == pygame.KEYDOWN: 51 | if event.key == pygame.K_ESCAPE: 52 | run = False 53 | elif event.key == pygame.K_h: 54 | show_text = not show_text 55 | elif event.key == pygame.K_SPACE: 56 | color_mode = not color_mode 57 | elif event.key == pygame.K_s: 58 | show_path = not show_path 59 | 60 | recursive_backtracker.Generate(screen, show_text, color_mode, show_path) 61 | 62 | 63 | pygame.image.save(screen, "./images/maskmaze.png") 64 | pygame.quit() 65 | -------------------------------------------------------------------------------- /classes/aldousBroder.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | import random 3 | from classes.grid import Grid, Update 4 | from ui.colors import * 5 | 6 | """ 7 | STEPS: 8 | 9 | 1. Choose a Cell. Any cell. 10 | 2. Choose a connected neighbor of the cell and travel to it. 11 | If the neighbor has not yet been visited, 12 | add the traveled edge to the spanning tree. 13 | 3. Repeat step 2 until all vertexes have been visited. 14 | """ 15 | class AldousBroder: 16 | def __init__(self, grid, path_color="YELLOW"): 17 | self.grid = grid 18 | self.rows = grid.rows 19 | self.cols = grid.cols 20 | self.starting_node = grid.cells[0][0] 21 | self.starting_node.isStartingNode = True 22 | self.end_node = grid.cells[self.cols-1][self.rows-1] 23 | self.end_node.isgoalNode = True 24 | self.isDone = False 25 | self.shortest_path = None 26 | self.path_color = path_color 27 | if path_color == "HSV": 28 | self.grid.path_color = white 29 | 30 | def Generate(self, screen, show_heuristic, show_color_map, show_path): 31 | if not self.isDone: 32 | randomX = random.randint(0, self.cols-1) 33 | randomY = random.randint(0, self.rows-1) 34 | current = self.grid.cells[randomX][randomY] 35 | temp_color = current.color 36 | unvisited = self.rows * self.cols - 1 37 | 38 | while unvisited > 0: 39 | 40 | neighbour = random.choice(current.neighbours) 41 | if len(neighbour.connections) == 0: 42 | Grid.JoinAndDestroyWalls(current, neighbour) 43 | unvisited -= 1 44 | 45 | current.color = navy_blue 46 | self.grid.Show(screen, show_heuristic, show_color_map) 47 | pygame.display.flip() 48 | current.color = temp_color 49 | current = neighbour 50 | self.isDone = True 51 | Update(self, screen, show_heuristic, show_color_map, show_path) 52 | 53 | if show_path: 54 | self.grid.Show(screen, show_heuristic, show_color_map,self.shortest_path) 55 | else: 56 | self.grid.Show(screen, show_heuristic, show_color_map, None) 57 | 58 | -------------------------------------------------------------------------------- /classes/binaryTree.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from classes.grid import Grid, Update 3 | from ui.colors import * 4 | import random 5 | 6 | """ 7 | STEPS: 8 | 9 | This has to be the simplest 10 | This is how it works: 11 | - for every cell in the grid, randomly carve a passage either north, or west. 12 | That's it 13 | """ 14 | 15 | class BinaryTree: 16 | def __init__(self, grid, path_color="BLUE"): 17 | self.grid = grid 18 | self.isDone = False 19 | self.starting_node = grid.cells[0][0] 20 | self.starting_node.isStartingNode = True 21 | self.end_node = grid.cells[self.grid.cols-1][self.grid.rows-1] 22 | self.end_node.isgoalNode = True 23 | self.show_path = True 24 | self.path_color = path_color 25 | self.shortest_path = None 26 | if path_color == "HSV": 27 | self.grid.path_color = white 28 | 29 | def Generate(self, screen, show_heuristic, show_color_map,show_path): 30 | if not self.isDone: 31 | for x in range(self.grid.cols): 32 | for y in range(self.grid.rows): 33 | neighbours = [] 34 | self.grid.cells[x][y].isCurrent = True 35 | # Check two neighbours 36 | # check for north and south Neighbours 37 | if self.grid.cells[x][y].North != None: 38 | neighbours.append(self.grid.cells[x][y].North) 39 | if self.grid.cells[x][y].East != None: 40 | neighbours.append(self.grid.cells[x][y].East) 41 | 42 | # pick a random neighbour 43 | # if any in the neighbours list 44 | choice = None 45 | if len(neighbours) > 0: 46 | choice = random.choice(neighbours) 47 | 48 | if choice != None: 49 | Grid.JoinAndDestroyWalls(self.grid.cells[x][y], choice) 50 | 51 | self.grid.Show(screen, show_heuristic, show_color_map) 52 | self.grid.cells[x][y].isCurrent = False 53 | pygame.display.flip() 54 | 55 | self.isDone = True 56 | # self.grid.Braid(0.5) 57 | 58 | Update(self, screen, show_heuristic, show_color_map, show_path) 59 | 60 | if show_path: 61 | self.grid.Show(screen, show_heuristic, show_color_map, self.shortest_path) 62 | else: 63 | self.grid.Show(screen, show_heuristic, show_color_map) 64 | 65 | -------------------------------------------------------------------------------- /classes/growingTree.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | import random 3 | from classes.grid import Grid, Update 4 | 5 | """ 6 | STEPS: 7 | 8 | 1. Let C be a list of cells, initially empty. Add one cell to C, at random. 9 | 2. Choose a cell from C, and carve a passage to any unvisited neighbor of that cell, 10 | adding that neighbor to C as well. If there are no unvisited neighbors, remove the cell from C. 11 | 3. Repeat #2 until C is empty. 12 | """ 13 | 14 | class GrowingTree: 15 | def __init__(self, grid, path_color="BLUE"): 16 | self.grid = grid 17 | self.cols = grid.cols 18 | self.rows = grid.rows 19 | self.isDone = False 20 | self.starting_node = grid.cells[0][0] 21 | self.starting_node.isStartingNode = True 22 | self.end_node = grid.cells[self.cols-1][self.rows-1] 23 | self.end_node.isgoalNode = True 24 | self.show_path = True 25 | self.path_color = path_color 26 | self.shortest_path = None 27 | if path_color == "HSV": 28 | self.grid.path_color = white 29 | 30 | def Generate(self, screen, show_heuristic, show_color_map,show_path): 31 | if not self.isDone: 32 | active = [] 33 | rx = random.randint(0, self.cols-1) 34 | ry = random.randint(0, self.rows-1) 35 | start_at = self.grid.cells[rx][ry] 36 | active.append(start_at) 37 | 38 | while len(active) > 0: 39 | current = active[-1] 40 | available_neighbours = [] 41 | for cell in current.neighbours: 42 | if len(cell.connections) == 0: 43 | available_neighbours.append(cell) 44 | 45 | if len(available_neighbours) > 0: 46 | neighbour = random.choice(available_neighbours) 47 | 48 | neighbour.isCurrent = True 49 | Grid.JoinAndDestroyWalls(current, neighbour) 50 | self.grid.Show(screen, show_heuristic, show_color_map) 51 | pygame.display.flip() 52 | neighbour.isCurrent = False 53 | 54 | active.append(neighbour) 55 | else: 56 | active.remove(current) 57 | self.isDone = True 58 | Update(self, screen, show_heuristic, show_color_map, show_path) 59 | 60 | if show_path: 61 | self.grid.Show(screen, show_heuristic, show_color_map, self.shortest_path) 62 | else: 63 | self.grid.Show(screen, show_heuristic, show_color_map) 64 | 65 | -------------------------------------------------------------------------------- /classes/wilson.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from classes.grid import Grid, Update 3 | from ui.colors import * 4 | import random 5 | 6 | 7 | """ 8 | STEPS: 9 | 10 | 1. Choose any vertex at random and add it to the UST. 11 | 2. Select any vertex that is not already in the UST 12 | and perform a random walk until you encounter a vertex that is in the UST. 13 | 3. Add the vertices and edges touched in the random walk to the UST. 14 | 4. Repeat 2 and 3 until all vertices have been added to the UST. 15 | """ 16 | 17 | class Wilson: 18 | def __init__(self, grid, path_color="BLUE_E"): 19 | self.grid = grid 20 | self.rows = grid.rows 21 | self.cols =grid.cols 22 | self.path_color = path_color 23 | self.isDone = False 24 | self.starting_node = grid.cells[0][0] 25 | self.starting_node.isStartingNode = True 26 | self.end_node = grid.cells[self.cols-1][self.rows-1] 27 | self.end_node.isgoalNode = True 28 | self.shortest_path = None 29 | if path_color == "HSV": 30 | self.grid.path_color = white 31 | 32 | def Generate(self, screen, show_heuristic, show_color_map, show_path): 33 | if not self.isDone: 34 | unvisited = self.grid.Flatten() 35 | random_cell = random.choice(unvisited) 36 | unvisited.remove(random_cell) 37 | while len(unvisited) > 0: 38 | current = random.choice(unvisited) 39 | path = [current] 40 | 41 | while current in unvisited: 42 | current = random.choice(current.neighbours) 43 | if current in path: 44 | _index = path.index(current) 45 | path = path[:_index+1] 46 | else: 47 | path.append(current) 48 | 49 | if len(path) > 1: 50 | for i in range(len(path)-1): 51 | path[i+1].isCurrent = True 52 | 53 | Grid.JoinAndDestroyWalls(path[i], path[i+1], "Normal") 54 | unvisited.remove(path[i]) 55 | 56 | self.grid.Show(screen, show_heuristic, show_color_map) 57 | path[i+1].isCurrent = False 58 | pygame.display.flip() 59 | self.isDone = True 60 | Update(self, screen, show_heuristic, show_color_map, show_path) 61 | 62 | if show_path: 63 | self.grid.Show(screen, show_heuristic, show_color_map, self.shortest_path) 64 | else: 65 | self.grid.Show(screen, show_heuristic, show_color_map) 66 | 67 | -------------------------------------------------------------------------------- /classes/polarGrid.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | import random 3 | from classes.grid import Grid 4 | from classes.polarCell import PolarCell 5 | from ui.colors import * 6 | from constants import width ,height 7 | import math 8 | 9 | class PolarGrid(Grid): 10 | def __init__(self, rows, cols, cell_size): 11 | super().__init__(rows, cols, cell_size) 12 | 13 | def PrepareGrid(self): 14 | rows = [None for i in range(self.rows)] 15 | row_height = 1/self.rows 16 | rows[0] = [PolarCell(0, 0, self.cell_size)] 17 | 18 | for i in range(self.rows): 19 | if i > 0: 20 | radius = i / self.rows 21 | circumference = (2 * math.pi) * radius 22 | 23 | previous_count = len(rows[i-1]) 24 | estimated_cell_width = circumference / previous_count 25 | ratio = int(estimated_cell_width / row_height) 26 | 27 | cells = previous_count * ratio 28 | rows[i] = [PolarCell(i, j, self.cell_size) for j in range(cells)] 29 | 30 | return rows 31 | 32 | def ConfigureCells(self): 33 | for i in range(len(self.cells)): 34 | for j in range(len(self.cells[i])): 35 | current = self.cells[i][j] 36 | x, y = current.x, current.y 37 | if x > 0: 38 | _index = (y+1) % (len(self.cells[x])) 39 | if _index < len(self.cells[x]): 40 | current.clockwise = self.cells[x][_index] 41 | _index = y-1 42 | if _index >= 0: 43 | current.c_clockwise = self.cells[x][_index] 44 | 45 | ratio = len(self.cells[x]) / len(self.cells[x-1]) 46 | parent = self.cells[x-1][int(y/ratio)] 47 | 48 | parent.outward.append(current) 49 | current.inward = parent 50 | 51 | def GetRandomCell(self): 52 | x = random.randint(0, len(self.cells)-1) 53 | y = 0 54 | if len(self.cells[x]) > 0: 55 | y = random.randint(0, len(self.cells[x])-1) 56 | 57 | return self.cells[x][y] 58 | 59 | def Show(self, screen, show_heuristic=None, show_color_map=None, shortest_path = None): 60 | centerX = width//2 61 | centerY = height//2 62 | for x in range(len(self.cells)): 63 | for y in range(len(self.cells[x])): 64 | cell = self.cells[x][y] 65 | if x > 0: 66 | theta = (2 * math.pi) / len(self.cells[cell.x]) 67 | 68 | inner_radius = cell.x * self.cell_size 69 | outer_radius = (cell.x + 1) * self.cell_size 70 | 71 | theta_counter_clockwise = cell.y * theta 72 | theta_clockwise = (cell.y + 1) * theta 73 | 74 | ax = centerX + (inner_radius * math.cos(theta_counter_clockwise)) 75 | ay = centerY + (inner_radius * math.sin(theta_counter_clockwise)) 76 | 77 | bx = centerX + (outer_radius * math.cos(theta_counter_clockwise)) 78 | by = centerY + (outer_radius * math.sin(theta_counter_clockwise)) 79 | 80 | cx = centerX + (inner_radius * math.cos(theta_clockwise)) 81 | cy = centerY + (inner_radius * math.sin(theta_clockwise)) 82 | 83 | dx = centerX + (outer_radius * math.cos(theta_clockwise)) 84 | dy = centerY + (outer_radius * math.sin(theta_clockwise)) 85 | if cell.inward : 86 | pygame.draw.line(screen, white, (ax, ay), (cx, cy), 5) 87 | if cell.clockwise : 88 | pygame.draw.line(screen, white, (cx, cy), (dx, dy), 5) 89 | pygame.draw.circle(screen, white, (centerX, centerY), self.rows * self.cell_size, 5) 90 | -------------------------------------------------------------------------------- /classes/sideWinder.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from classes.grid import Grid, Update 3 | from ui.colors import * 4 | import random 5 | import time 6 | from ui.colors import * 7 | 8 | """ 9 | STEPS: 10 | 11 | 1. Work through the grid row-wise, starting with the cell at 0,0. 12 | Initialize the “run” set to be empty. 13 | 2. Add the current cell to the “run” set. 14 | 3. For the current cell, randomly decide whether to carve east or not. 15 | 4. If a passage was carved, make the new cell the current cell and repeat steps 2-4. 16 | 5. If a passage was not carved, choose any one of the cells in the run set and carve a passage north. 17 | Then empty the run set, set the next cell in the row to be the current cell, and repeat steps 2-5. 18 | 6. Continue until all rows have been processed. 19 | """ 20 | 21 | class SideWinder: 22 | def __init__(self, grid, path_color="RED"): 23 | self.grid = grid 24 | self.starting_node = grid.cells[0][0] 25 | self.starting_node.isStartingNode = True 26 | self.end_node = grid.cells[self.grid.cols-1][self.grid.rows-1] 27 | self.end_node.isgoalNode = True 28 | self.isDone = False 29 | self.speed = 1 30 | self.max_speed = 1 31 | self.show_path = True 32 | self.path_color = path_color 33 | self.shortest_path = None 34 | if path_color == "HSV": 35 | self.grid.path_color = white 36 | 37 | def Generate(self, screen, show_heuristic, show_color_map, show_path): 38 | if not self.isDone: 39 | for y in range(self.grid.rows): 40 | history = [] 41 | 42 | for x in range(self.grid.cols): 43 | current = self.grid.cells[x][y] 44 | 45 | current.isCurrent = True 46 | history.append(current) 47 | 48 | at_eastern_edge = False 49 | at_northern_edge = False 50 | 51 | if current.East == None: 52 | at_eastern_edge = True 53 | if current.North == None: 54 | at_northern_edge = True 55 | 56 | if at_eastern_edge or (at_northern_edge == False and random.randint(0, 1)==1): 57 | random_cell = random.choice(history) 58 | if random_cell.North: 59 | Grid.JoinAndDestroyWalls(random_cell, random_cell.North) 60 | history.clear() 61 | else: 62 | Grid.JoinAndDestroyWalls(current, current.East) 63 | 64 | self.grid.Show(screen, show_heuristic, show_color_map) 65 | current.isCurrent = False 66 | pygame.display.flip() 67 | 68 | if self.speed < 1: 69 | time.sleep(self.max_speed - min(self.speed, self.max_speed)) 70 | self.isDone = True 71 | 72 | Update(self, screen, show_heuristic, show_color_map, show_path) 73 | 74 | if show_path: 75 | self.grid.Show(screen, show_heuristic, show_color_map,self.shortest_path) 76 | else: 77 | self.grid.Show(screen, show_heuristic, show_color_map, None) 78 | 79 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from constants import * 3 | from ui.colors import * 4 | import classes 5 | 6 | from ui.setup import * 7 | # Initialize pygame 8 | pygame.init() 9 | screen = pygame.display.set_mode(size) 10 | clock = pygame.time.Clock() 11 | fps = 30 12 | 13 | binary_tree = classes.BinaryTree(classes.Grid(rows, cols, cell_size), "GREEN") 14 | wilson = classes.Wilson(classes.Grid(rows, cols, cell_size), "PURPLE_E") 15 | side_winder = classes.SideWinder(classes.Grid(rows, cols, cell_size), "BLUE") 16 | hunt_and_kill = classes.HuntAndKill(classes.Grid(rows, cols, cell_size), "RED") 17 | aldous_broder = classes.AldousBroder(classes.Grid(rows, cols, cell_size), "GREEN") 18 | recursive_backtracker = classes.RecursiveBacktracker(classes.Grid(rows, cols, cell_size), "BLUE") 19 | kruskal = classes.Kruskals(classes.Kruskals.State(classes.Grid(rows, cols, cell_size))) 20 | simplePrims = classes.SimplePrims(classes.Grid(rows, cols, cell_size), "CYAN") 21 | prims = classes.Prims(classes.Grid(rows, cols, cell_size)) 22 | growingTree = classes.GrowingTree(classes.Grid(rows, cols, cell_size), "GREEN") 23 | ellers = classes.Ellers(classes.Grid(rows, cols, cell_size), 0, "RED") 24 | 25 | show_text = False 26 | color_mode = False 27 | show_path = False 28 | start = False 29 | run = True 30 | while run: 31 | #screen.fill(black) 32 | # Set Caption and fps 33 | clock.tick(fps) 34 | frame_rate = int(clock.get_fps()) 35 | pygame.display.set_caption(f"Maze Generator - FPS: {frame_rate}") 36 | 37 | # Handle events 38 | for event in pygame.event.get(): 39 | if event.type == pygame.QUIT: 40 | run = False 41 | if event.type == pygame.KEYDOWN: 42 | if event.key == pygame.K_ESCAPE: 43 | run = False 44 | if event.key == pygame.K_RETURN: 45 | start = not start 46 | elif event.key == pygame.K_h: 47 | show_text = not show_text 48 | elif event.key == pygame.K_SPACE: 49 | color_mode = not color_mode 50 | elif event.key == pygame.K_s: 51 | show_path = not show_path 52 | if event.type == pygame.MOUSEBUTTONDOWN: 53 | if event.button == 1: 54 | rightMouseClicked = True 55 | 56 | if start: 57 | # wilson.Generate(screen, show_text, color_mode, show_path) 58 | # binary_tree.Generate(screen, show_text, color_mode, show_path) 59 | kruskal.Generate(screen, show_text, color_mode, show_path) 60 | # side_winder.Generate(screen, show_text, color_mode, show_path) 61 | # hunt_and_kill.Generate(screen, show_text, color_mode, show_path) 62 | # aldous_broder.Generate(screen, show_text, color_mode, show_path) 63 | # recursive_backtracker.Generate(screen, show_text, color_mode, show_path) 64 | # simplePrims.Generate(screen, show_text, color_mode, show_path) 65 | # prims.Generate(screen, show_text, color_mode, show_path) 66 | # growingTree.Generate(screen, show_text, color_mode, show_path) 67 | # ellers.Generate(screen, show_text, color_mode, show_path) 68 | else: 69 | PressEnter.Render(screen) 70 | 71 | pygame.display.flip() 72 | 73 | pygame.image.save(screen, "./images/path.png") 74 | pygame.quit() 75 | -------------------------------------------------------------------------------- /classes/huntandkill.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | import random 3 | from classes.grid import Grid, Update 4 | from ui.colors import * 5 | 6 | """ 7 | STEPS: 8 | 9 | 1. Choose a starting cell. 10 | 2. Perform a random walk, carving passages to unvisited neighbors, 11 | until the current cell has no unvisited neighbors. 12 | 3. Enter “hunt” mode, where you scan the grid looking for an unvisited cell that is adjacent to a visited cell. 13 | If found, carve a passage between the two and let the formerly unvisited cell be the new starting cell. 14 | 4. Repeat steps 2 and 3 until the hunt mode scans the entire grid and finds no unvisited cells. 15 | """ 16 | 17 | class HuntAndKill: 18 | def __init__(self, grid, path_color): 19 | self.grid = grid 20 | self.rows = grid.rows 21 | self.cols = grid.cols 22 | self.path_color = path_color 23 | self.isDone = False 24 | self.starting_node = grid.cells[0][0] 25 | self.starting_node.isStartingNode = True 26 | self.end_node = grid.cells[self.grid.cols-1][self.grid.rows-1] 27 | self.end_node.isgoalNode = True 28 | if path_color == "HSV": 29 | self.grid.path_color = white 30 | self.shortest_path = None 31 | 32 | def Generate(self, screen, show_heuristic, show_color_map, show_path): 33 | if not self.isDone: 34 | randomX, randomY = random.randint(0, self.cols-1), random.randint(0, self.rows-1) 35 | current = self.grid.cells[randomX][randomY] 36 | while current: 37 | unvisited_neighbours = [cell for cell in current.neighbours if len(cell.connections) == 0] 38 | if len(unvisited_neighbours) > 0: 39 | neighbour = random.choice(unvisited_neighbours) 40 | neighbour.isCurrent = True 41 | Grid.JoinAndDestroyWalls(current, neighbour) 42 | 43 | self.grid.Show(screen, show_heuristic, show_color_map) 44 | pygame.display.flip() 45 | 46 | neighbour.isCurrent = False 47 | current = neighbour 48 | 49 | else: 50 | current = None 51 | for x in range(self.cols): 52 | for y in range(self.rows): 53 | this = self.grid.cells[x][y] 54 | visited_neighbours = [cell for cell in this.neighbours if len(cell.connections) > 0] 55 | if len(this.connections) == 0 and len(visited_neighbours) > 0: 56 | current = this 57 | neighbour = random.choice(visited_neighbours) 58 | neighbour.isCurrent = True 59 | Grid.JoinAndDestroyWalls(current, neighbour) 60 | 61 | self.grid.Show(screen, show_heuristic, show_color_map) 62 | neighbour.isCurrent = False 63 | 64 | pygame.display.flip() 65 | break 66 | 67 | self.isDone = True 68 | Update(self, screen, show_heuristic, show_color_map, show_path) 69 | 70 | if show_path: 71 | self.grid.Show(screen, show_heuristic, show_color_map,self.shortest_path) 72 | else: 73 | self.grid.Show(screen, show_heuristic, show_color_map, None) 74 | 75 | -------------------------------------------------------------------------------- /classes/hexGrid.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | import math 3 | from ui.colors import * 4 | from classes.grid import Grid 5 | from classes.hexCell import HexCell 6 | from constants import * 7 | 8 | class HexGrid(Grid): 9 | def __init__(self, rows, cols, cell_size): 10 | super().__init__(rows, cols, cell_size) 11 | 12 | def PrepareGrid(self): 13 | for x in range(self.cols): 14 | for y in range(self.rows): 15 | self.cells[x][y] = HexCell(x, y, self.cell_size) 16 | 17 | def ConfigureCells(self): 18 | for x in range(self.cols): 19 | for y in range(self.rows): 20 | current = self.cells[x][y] 21 | row, col = current.x, current.y 22 | north_diagonal, south_diagonal = 0, 0 23 | 24 | if (col%2) == 0: 25 | north_diagonal = row - 1 26 | south_diagonal = row 27 | else: 28 | north_diagonal = row 29 | south_diagonal = row + 1 30 | 31 | if row - 1 >= 0: 32 | current.North = self.cells[row - 1][col] 33 | if north_diagonal >= 0 and north_diagonal < self.cols: 34 | if col - 1 >= 0 : 35 | current.NorthWest = self.cells[north_diagonal][col-1] 36 | if north_diagonal >= 0 and north_diagonal < self.cols: 37 | if col + 1 < self.rows: 38 | current.NorthEast = self.cells[north_diagonal][col+1] 39 | 40 | if row + 1 < self.cols: 41 | current.South = self.cells[row+1][col] 42 | if south_diagonal >= 0 and south_diagonal < self.cols: 43 | if col + 1 < self.rows : 44 | current.SouthEast = self.cells[south_diagonal][col+1] 45 | if south_diagonal >= 0 and south_diagonal < self.cols: 46 | if col - 1 >= 0 : 47 | current.SouthWest = self.cells[south_diagonal][col-1] 48 | 49 | def Show(self, screen, show_heuristic, show_color_map, shortest_path = None): 50 | a_size = self.cell_size /2 51 | b_size = self.cell_size * math.sqrt(3)/2 52 | 53 | w = self.cell_size * 2 54 | h = b_size * 2 55 | 56 | if not self.isSorted and shortest_path: 57 | for x in range(self.cols): 58 | for y in range(self.rows): 59 | if shortest_path.cells_record[x][y]: 60 | val = shortest_path.cells_record[x][y] 61 | self.path_values.append(val) 62 | self.path[str(val)] = self.cells[x][y] 63 | self.path_values = sorted(self.path_values) 64 | self.isSorted = True 65 | 66 | for x in range(self.cols): 67 | for y in range(self.rows): 68 | self.cells[x][y].show_text = show_heuristic 69 | self.cells[x][y].show_highlight = show_color_map 70 | self.cells[x][y].Draw(screen, show_color_map, a_size, b_size, w, h, self.rows, self.cols) 71 | 72 | if shortest_path: 73 | for i in range(len(self.path_values)-1): 74 | pygame.draw.line(screen, orange, self.path[str(self.path_values[i])].GetCenter(), self.path[str(self.path_values[i+1])].GetCenter(), 2) 75 | pygame.draw.circle(screen, orange, self.path[str(self.path_values[i])].GetCenter(), self.cell_size//6) 76 | -------------------------------------------------------------------------------- /classes/hexCell.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from ui.colors import * 3 | from classes.cell import Cell 4 | from constants import * 5 | 6 | pygame.font.init() 7 | text_font = pygame.font.SysFont("Arial", cell_size//3) 8 | 9 | class HexCell(Cell): 10 | def __init__(self, x, y, size): 11 | super().__init__(x, y, size) 12 | self.NorthEast = None 13 | self.NorthWest = None 14 | self.SouthEast = None 15 | self.SouthWest = None 16 | self.centerX = 0 17 | self.centerY = 0 18 | 19 | def SetNeighbours(self): 20 | self.neighbours = [] 21 | if self.North: 22 | self.neighbours.append(self.North) 23 | if self.South: 24 | self.neighbours.append(self.South) 25 | if self.NorthEast: 26 | self.neighbours.append(self.NorthEast) 27 | if self.NorthWest: 28 | self.neighbours.append(self.NorthWest) 29 | if self.SouthEast: 30 | self.neighbours.append(self.SouthEast) 31 | if self.SouthWest: 32 | self.neighbours.append(self.SouthWest) 33 | 34 | def GetCenter(self): 35 | return (self.centerX, self.centerY) 36 | 37 | def Draw(self, screen, show_color_map, a_size, b_size, w, h, r, c): 38 | cx = self.size + 3 * self.y * a_size 39 | cy = b_size + self.x * h 40 | cy += b_size if (self.y%2)!= 0 else 0 41 | 42 | x_farWest = (cx -self.size) 43 | x_nearWest = (cx-a_size) 44 | x_nearEast = (cx+a_size) 45 | x_farEast = (cx+self.size) 46 | 47 | y_north = (cy-b_size) 48 | y_middle = cy 49 | y_south = (cy+b_size) 50 | 51 | self.centerY = cy 52 | self.centerX = cx 53 | 54 | color = self.color 55 | if self.isStartingNode: 56 | color = yellow 57 | if self.isCurrent: 58 | color = navy_blue 59 | elif self.isgoalNode: 60 | color = blue 61 | 62 | 63 | if self.show_highlight and self.visited: 64 | points = [(x_farWest, y_middle), (x_nearWest, y_north), 65 | (x_nearEast, y_north), (x_farEast, y_middle), 66 | (x_nearEast, y_south), (x_nearWest, y_south)] 67 | pygame.draw.polygon(screen, self.highlight, points) 68 | elif self.visited: 69 | points = [(x_farWest, y_middle), (x_nearWest, y_north), 70 | (x_nearEast, y_north), (x_farEast, y_middle), 71 | (x_nearEast, y_south), (x_nearWest, y_south)] 72 | pygame.draw.polygon(screen, color, points) 73 | else: 74 | points = [(x_farWest, y_middle), (x_nearWest, y_north), 75 | (x_nearEast, y_north), (x_farEast, y_middle), 76 | (x_nearEast, y_south), (x_nearWest, y_south)] 77 | pygame.draw.polygon(screen, black, points) 78 | 79 | if self.SouthWest or self.y == 0 or (self.x == c-1 and self.y % 2 != 0): 80 | pygame.draw.line(screen, black, (x_farWest, y_middle), (x_nearWest, y_south), 4) 81 | if self.NorthWest or self.y == 0 or (self.x == 0 and self.y % 2 == 0): 82 | pygame.draw.line(screen, black, (x_farWest, y_middle), (x_nearWest, y_north), 4) 83 | if self.North or self.x == 0: 84 | pygame.draw.line(screen, black, (x_nearWest, y_north), (x_nearEast, y_north), 4) 85 | 86 | if self.NorthEast or self.y == r-1 or (self.x == 0 and self.y%2==0): 87 | pygame.draw.line(screen, black, (x_nearEast, y_north), (x_farEast, y_middle), 4) 88 | if self.SouthEast or self.y == r-1 or (self.x == c-1 and self.y%2!=0): 89 | pygame.draw.line(screen, black, (x_farEast, y_middle), (x_nearEast, y_south), 4) 90 | if self.South or self.x == c-1: 91 | pygame.draw.line(screen, black, (x_nearEast, y_south), (x_nearWest, y_south), 4) 92 | 93 | if self.show_text: 94 | text_surface = text_font.render(str(int(self.cost)), True, self.textColor) 95 | text_rect = text_surface.get_rect(center=(self.centerX , self.centerY)) 96 | screen.blit(text_surface, text_rect) 97 | -------------------------------------------------------------------------------- /classes/recursiveBacktracker.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | import time 3 | from classes.grid import Grid, Update 4 | from classes.mask import Mask, GridMask 5 | from classes.polarGrid import PolarGrid 6 | from classes.weightedGrid import WeightedGrid 7 | from classes.hexGrid import HexGrid 8 | from ui.colors import * 9 | import random 10 | 11 | """ 12 | STEPS: 13 | 14 | 1. Choose a starting point in the field. 15 | 2. Randomly choose a wall at that point and carve a passage through to the adjacent cell, 16 | but only if the adjacent cell has not been visited yet. This becomes the new current cell. 17 | 3. If all adjacent cells have been visited, back up to the last cell that has uncarved walls and repeat. 18 | 4. The algorithm ends when the process has backed all the way up to the starting point. 19 | """ 20 | 21 | class RecursiveBacktracker: 22 | def __init__(self, grid, path_color): 23 | self.grid = grid 24 | self.rows = grid.rows 25 | self.cols = grid.cols 26 | self.path_color = path_color 27 | self.isDone = False 28 | self.starting_node = None 29 | self.end_node = None 30 | 31 | if type(self.grid) == Grid: 32 | self.starting_node = grid.cells[0][0] 33 | self.starting_node.isStartingNode = True 34 | self.end_node = grid.cells[self.cols-1][self.rows-1] 35 | self.end_node.isgoalNode = True 36 | if path_color == "HSV": 37 | self.grid.path_color = white 38 | self.shortest_path = None 39 | 40 | def Generate(self, screen, show_heuristic, show_color_map, show_path): 41 | if not self.isDone: 42 | gridtype = "Normal" 43 | stack = [] 44 | initial_cell = None 45 | if type(self.grid) == Grid or type(self.grid) == WeightedGrid: 46 | randomX = random.randint(0, self.cols-1) 47 | randomY = random.randint(0, self.rows-1) 48 | initial_cell = self.grid.cells[randomX][randomY] 49 | elif type(self.grid) == PolarGrid: 50 | gridtype = "Polar" 51 | initial_cell = self.grid.GetRandomCell() 52 | elif type(self.grid) == GridMask: 53 | initial_cell = self.grid.GetRandomCell() 54 | elif type(self.grid) == HexGrid: 55 | gridtype = "Hex" 56 | randomX = random.randint(0, self.cols-1) 57 | randomY = random.randint(0, self.rows-1) 58 | initial_cell = self.grid.cells[randomX][randomY] 59 | 60 | stack.append(initial_cell) 61 | 62 | while len(stack) > 0: 63 | current = stack[-1] 64 | neighbours = [] 65 | if type(self.grid) == PolarGrid or type(self.grid) == HexGrid: 66 | current.SetNeighbours() 67 | neighbours = [cell for cell in current.neighbours if len(cell.connections) == 0] 68 | else: 69 | neighbours = [cell for cell in current.neighbours if len(cell.connections) == 0] 70 | 71 | if len(neighbours) == 0: 72 | stack.pop() 73 | else: 74 | neighbour = random.choice(neighbours) 75 | Grid.JoinAndDestroyWalls(current, neighbour, gridtype) 76 | neighbour.isCurrent = True 77 | self.grid.Show(screen, show_heuristic, show_color_map) 78 | pygame.display.flip() 79 | neighbour.isCurrent = False 80 | 81 | stack.append(neighbour) 82 | 83 | self.isDone = True 84 | if type(self.grid) != PolarGrid: 85 | Update(self, screen, show_heuristic, show_color_map, show_path) 86 | if type(self.grid) != PolarGrid: 87 | if show_path: 88 | self.grid.Show(screen, show_heuristic, show_color_map,self.shortest_path) 89 | else: 90 | self.grid.Show(screen, show_heuristic, show_color_map, None) 91 | else: 92 | self.grid.Show(screen, show_heuristic, show_color_map, None) 93 | pygame.display.flip() 94 | -------------------------------------------------------------------------------- /classes/cell.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from ui.colors import * 3 | from classes.heuristic import Heuristic 4 | from constants import * 5 | 6 | pygame.font.init() 7 | text_font = pygame.font.SysFont("Arial", cell_size//3) 8 | offset = 0 9 | 10 | class Cell: 11 | def __init__(self, x, y, size=40): 12 | self.x = x 13 | self.y = y 14 | 15 | self.cost = 0 16 | self.isgoalNode = False 17 | self.isStartingNode = False 18 | self.isCurrent = False 19 | self.isPath = False 20 | self.show_path = False 21 | self.highlight = white 22 | 23 | self.show_highlight = False 24 | self.size = size 25 | self.color= white 26 | self.wall_color= black 27 | self.wall_thickness = 4 28 | self.visited = False 29 | self.connections = [] 30 | self.neighbours = [] 31 | self.isAvailable = True 32 | # Walls -- neighbours 33 | self.North = None 34 | self.South = None 35 | self.East = None 36 | self.West = None 37 | 38 | self.textColor = (0, 0, 0) 39 | self.show_text = True 40 | 41 | def CalculateHeuristic(self, rows, cols): 42 | h_distances = Heuristic(rows, cols) 43 | frontier = [self] 44 | while len(frontier) > 0: 45 | new_frontier = [] 46 | for c in frontier: 47 | for cell in c.connections: 48 | if h_distances.GetRecord(cell): 49 | continue 50 | val = 0 if h_distances.GetRecord(c) == None else h_distances.GetRecord(c) 51 | h_distances.SetRecord(cell, val+1) 52 | new_frontier.append(cell) 53 | 54 | frontier = new_frontier 55 | h_distances.SetRecord(self, 0) 56 | return h_distances 57 | 58 | def IsConneted(self, cell): 59 | if cell != None: 60 | if cell in self.connections and self in cell.connections: 61 | return True 62 | else: 63 | return False 64 | 65 | def Draw(self, screen, rows, cols): 66 | x = self.x * self.size 67 | y = self.y * self.size 68 | 69 | if not self.visited or not self.isAvailable: 70 | pygame.draw.rect(screen, black, [x, y, self.size-offset, self.size-offset]) 71 | else: 72 | color = self.color 73 | if self.isStartingNode: 74 | color = yellow 75 | if self.isCurrent: 76 | color = navy_blue 77 | elif self.isgoalNode: 78 | color = blue 79 | pygame.draw.rect(screen, color, [x, y, self.size-offset, self.size-offset]) 80 | 81 | if self.show_highlight: 82 | pygame.draw.rect(screen, self.highlight, [x, y, self.size-offset, self.size-offset]) 83 | 84 | if self.North != None or self.y - 1 < 0: 85 | A = (x, y) 86 | B = (x + self.size, y) 87 | pygame.draw.line(screen, self.wall_color, A, B, self.wall_thickness) 88 | if self.South != None or self.y + 1 >= rows: 89 | A = (x, y + self.size) 90 | B = (x + self.size, y + self.size) 91 | pygame.draw.line(screen, self.wall_color, A, B, self.wall_thickness) 92 | if self.East != None or self.x + 1 >= cols: 93 | A = (x + self.size, y) 94 | B = (x + self.size, y + self.size) 95 | pygame.draw.line(screen, self.wall_color, A, B, self.wall_thickness) 96 | if self.West != None or self.x - 1 < 0: 97 | A = (x, y) 98 | B = (x, y + self.size) 99 | pygame.draw.line(screen, self.wall_color, A, B, self.wall_thickness) 100 | 101 | if self.show_text: 102 | text_surface = text_font.render(str(int(self.cost)), True, self.textColor) 103 | text_rect = text_surface.get_rect(center=(x + self.size//2, y + self.size/2)) 104 | screen.blit(text_surface, text_rect) 105 | 106 | def __repr__(self): 107 | # DEBUG 108 | return f"({self.x}, {self.y}, {id(self)})" 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Procedural-Maze-Generator-Algorithms and Maze solver 2 | 3 | Check out my youtube channel : [Auctux](https://www.youtube.com/c/Auctux) 4 | 5 | ### Ressources 6 | - Thanks to Jamis Buck Book : [Mazes for programmers](https://www.amazon.in/gp/product/B013HA1UY4/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=auctux-21&creative=24630&linkCode=as2&creativeASIN=B013HA1UY4&linkId=cd1ab49c7f656324511f624fefe1ecda) 7 | 8 | ### Requirements 9 | > ***PYGAME*** : pip install pygame 10 | 11 | ### Commands Controls: 12 | - `Esc` to close the program 13 | - `Enter` to start to genrate the maze 14 | - `H` to show the heuristic cost value 15 | - `S` to show the path from the starting to the goal node 16 | - `Space` to switch between the color modes 17 | 18 | ## files 19 | - run ***main.py*** for grid mazes 20 | - run ***polarMaze.py*** for polarGrid maze 21 | - run ***hexMaze.py*** for hexGrid maze 22 | - run ***imageMaze.py*** for maskGrid maze 23 | - run ***weightedMaze.py*** for weightedGrid maze 24 | 25 | > you can also ***braid*** your mazes when it done , by calling the function `grid.Braid()`. 26 | ***braiding a maze*** simply assures that the maze doens't have any deadends 27 | 28 | ## colors modes 29 | Mode 1 : 30 | 31 | Mode 2 : 32 | 33 | 34 | ## other Maze Grid: 35 | Polar Grid : 36 | 37 | HexGrid : 38 | 39 | maskGrid : 40 | 41 | 42 | ## Show heuristic and path 43 | - heuristic : 44 | - showPath : 45 | 46 | ## Bugs & Unsolved Issues 47 | - The code need a lot of refactoring 48 | - the visualization of the polar maze need a lot of improvement 49 | - the the imagemaze.py file need to be refactored for it to be able to work on every size of images , for now it's only working for images wich has the same size with the screen size 50 | - the recursive backtracker class need a little bit of cleaning 51 | - add Comments 52 | - implement the ui interface 53 | - the djikistra algorithm need to take the weights of cells in consideration for weightedGrid 54 | ## mazes 55 | --- 56 | ```python:main.py 57 | initialize: 58 | binary_tree = BinaryTree(Grid(rows, cols, cell_size), "GREEN") 59 | wilson = Wilson(Grid(rows, cols, cell_size), "PURPLE_E") 60 | side_winder = SideWinder(Grid(rows, cols, cell_size), "BLUE") 61 | hunt_and_kill = HuntAndKill(Grid(rows, cols, cell_size), "RED") 62 | aldous_broder = AldousBroder(Grid(rows, cols, cell_size), "GREEN") 63 | recursive_backtracker = RecursiveBacktracker(Grid(rows, cols, cell_size), "BLUE") 64 | kruskal = Kruskals(Kruskals.State(Grid(rows, cols, cell_size))) 65 | simplePrims = SimplePrims(Grid(rows, cols, cell_size), "CYAN") 66 | prims = Prims(Grid(rows, cols, cell_size)) 67 | growingTree = GrowingTree(Grid(rows, cols, cell_size), "GREEN") 68 | ellers = Ellers(Grid(rows, cols, cell_size), 0, "RED") 69 | mainloop(): 70 | wilson.Generate(screen, show_text, color_mode, show_path) 71 | binary_tree.Generate(screen, show_text, color_mode, show_path) 72 | kruskal.Generate(screen, show_text, color_mode, show_path) 73 | side_winder.Generate(screen, show_text, color_mode, show_path) 74 | hunt_and_kill.Generate(screen, show_text, color_mode, show_path) 75 | aldous_broder.Generate(screen, show_text, color_mode, show_path) 76 | recursive_backtracker.Generate(screen, show_text, color_mode, show_path) 77 | simplePrims.Generate(screen, show_text, color_mode, show_path) 78 | prims.Generate(screen, show_text, color_mode, show_path) 79 | growingTree.Generate(screen, show_text, color_mode, show_path) 80 | ellers.Generate(screen, show_text, color_mode, show_path) 81 | 82 | ``` 83 | --- 84 | 85 | - Aldous Broder 86 | 87 | - Binary Tree 88 | 89 | - Eller's Algorithm 90 | 91 | - Growing Tree 92 | 93 | - Hunt And kill 94 | 95 | - Kruskal's algorithm 96 | 97 | - Prims Algorithm 98 | 99 | - Simplified Prims 100 | 101 | - Wilson algorithm 102 | 103 | - Sidewinder Algorithm 104 | 105 | - Recursive Backtracker 106 | 107 | 108 | 109 | Enjoy ✌️ 110 | 111 | -------------------------------------------------------------------------------- /classes/kruskal.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from classes.grid import Grid, Update 3 | import random 4 | 5 | """ 6 | STEPS: 7 | 8 | 1. Throw all of the edges in the graph into a big burlap sack. 9 | 2. Pull out the edge with the lowest weight. If the edge connects two disjoint trees, 10 | join the trees. Otherwise, throw that edge away. 11 | 3. Repeat until there are no more edges left. 12 | 13 | """ 14 | 15 | class Kruskals: 16 | def __init__(self, state, path_color='BLUE'): 17 | self.state = state 18 | self.grid = state.grid 19 | self.isDone = False 20 | self.cols = self.grid.cols 21 | self.rows = self.grid.rows 22 | self.starting_node = self.grid.cells[0][0] 23 | self.starting_node.isStartingNode = True 24 | self.end_node = self.grid.cells[self.grid.cols-1][self.grid.rows-1] 25 | self.end_node.isgoalNode = True 26 | self.show_path = True 27 | self.path_color = path_color 28 | self.shortest_path = None 29 | if self.path_color == "HSV": 30 | self.grid.path_color = white 31 | class State: 32 | def __init__(self, grid): 33 | self.grid = grid 34 | self.cols = grid.cols 35 | self.rows = grid.rows 36 | self.neighbours = [] 37 | self.cells_set = {} 38 | self.cells_in_set = {} 39 | for x in range(self.cols): 40 | for y in range(self.rows): 41 | set = len(self.cells_set) 42 | self.cells_set[self.grid.cells[x][y]] = set 43 | self.cells_in_set[set] = [self.grid.cells[x][y]] 44 | 45 | if self.grid.cells[x][y].South: 46 | self.neighbours.append([self.grid.cells[x][y], self.grid.cells[x][y].South]) 47 | if self.grid.cells[x][y].East: 48 | self.neighbours.append([self.grid.cells[x][y], self.grid.cells[x][y].East]) 49 | def CanMerge(self, left, right): 50 | return (self.cells_set[left] != self.cells_set[right]) 51 | 52 | def Merge(self, left, right): 53 | Grid.JoinAndDestroyWalls(left ,right) 54 | winner = self.cells_set[left] 55 | loser = self.cells_set[right] 56 | losers = None 57 | if loser in self.cells_in_set: 58 | losers = self.cells_in_set[loser] 59 | else: 60 | losers = [right] 61 | 62 | for l in losers: 63 | self.cells_in_set[winner].append(l) 64 | self.cells_set[l] = winner 65 | del self.cells_in_set[loser] 66 | 67 | def AddCrossing(self, cell): 68 | if (len(cell.connections) > 0 or 69 | not self.CanMerge(cell.East, cell.West) or 70 | not self.CanMerge(cell.North, cell.South)): 71 | return False 72 | 73 | for c in self.neighbours: 74 | left, right = c[0], c[1] 75 | if left == cell or right == cell: 76 | self.neighbours.remove(c) 77 | 78 | if random.randint(0, 1) == 0: 79 | if cell.West != None: 80 | self.Merge(cell.West, cell) 81 | if cell.East != None: 82 | self.Merge(cell, cell.East) 83 | 84 | if cell.North and cell.North.East: 85 | self.Merge(cell.North, cell.North.East) 86 | if cell.South and cell.South.North: 87 | self.Merge(cell.South, cell.South.North) 88 | else: 89 | if cell.North: 90 | self.Merge(cell.North, cell) 91 | if cell.South: 92 | self.Merge(cell, cell.South) 93 | 94 | if cell.West and cell.West.East: 95 | self.Merge(cell.West, cell.West.East) 96 | self.Merge(cell.East, cell.East.West) 97 | 98 | def Generate(self, screen, show_heuristic, show_color_map,show_path): 99 | if not self.isDone: 100 | neighbours = self.state.neighbours 101 | random.shuffle(neighbours) 102 | while len(neighbours) > 0: 103 | poped = neighbours.pop() 104 | left, right = poped[0], poped[1] 105 | 106 | if self.state.CanMerge(left, right): 107 | self.state.Merge(left, right) 108 | self.grid.Show(screen, show_heuristic, show_color_map) 109 | pygame.display.flip() 110 | self.isDone = True 111 | Update(self, screen, show_heuristic, show_color_map, show_path) 112 | 113 | if show_path: 114 | self.grid.Show(screen, show_heuristic, show_color_map, self.shortest_path) 115 | else: 116 | self.grid.Show(screen, show_heuristic, show_color_map) 117 | 118 | -------------------------------------------------------------------------------- /classes/prims.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from classes.grid import Grid, Update 3 | import random 4 | 5 | """ 6 | STEPS: 7 | 8 | 1. Choose an arbitrary cell from G (the grid), 9 | and add it to some (initially empty) set V. 10 | 2. Choose the edge with the smallest weight from G, 11 | that connects a vertex in V with another cell not in V. 12 | 3. Add that edge to the minimal spanning tree, and the edge’s other cell to V. 13 | 4. Repeat steps 2 and 3 until V includes every vertex in G. 14 | 15 | """ 16 | 17 | class SimplePrims: 18 | def __init__(self, grid, path_color="BLUE"): 19 | self.grid = grid 20 | self.cols = grid.cols 21 | self.rows = grid.rows 22 | self.isDone = False 23 | self.starting_node = grid.cells[0][0] 24 | self.starting_node.isStartingNode = True 25 | self.end_node = grid.cells[self.cols-1][self.rows-1] 26 | self.end_node.isgoalNode = True 27 | self.show_path = True 28 | self.path_color = path_color 29 | self.shortest_path = None 30 | if path_color == "HSV": 31 | self.grid.path_color = white 32 | 33 | def Generate(self, screen, show_heuristic, show_color_map,show_path): 34 | if not self.isDone: 35 | active = [] 36 | rx = random.randint(0, self.cols-1) 37 | ry = random.randint(0, self.rows-1) 38 | start_at = self.grid.cells[rx][ry] 39 | active.append(start_at) 40 | 41 | while len(active) > 0: 42 | cell = random.choice(active) 43 | available_neighbours = [] 44 | for c in cell.neighbours: 45 | if len(c.connections) == 0: 46 | available_neighbours.append(c) 47 | 48 | if len(available_neighbours) > 0: 49 | neighbour = random.choice(available_neighbours) 50 | 51 | neighbour.isCurrent = True 52 | Grid.JoinAndDestroyWalls(cell, neighbour) 53 | self.grid.Show(screen, show_heuristic, show_color_map) 54 | pygame.display.flip() 55 | neighbour.isCurrent = False 56 | active.append(neighbour) 57 | else: 58 | active.remove(cell) 59 | self.isDone = True 60 | Update(self, screen, show_heuristic, show_color_map, show_path) 61 | 62 | if show_path: 63 | self.grid.Show(screen, show_heuristic, show_color_map, self.shortest_path) 64 | else: 65 | self.grid.Show(screen, show_heuristic, show_color_map) 66 | 67 | 68 | class Prims(SimplePrims): 69 | def __init__(self, grid, path_color="RED"): 70 | super().__init__(grid, path_color) 71 | 72 | def Generate(self, screen, show_heuristic, show_color_map,show_path): 73 | if not self.isDone: 74 | active = [] 75 | rx = random.randint(0, self.cols-1) 76 | ry = random.randint(0, self.rows-1) 77 | start_at = self.grid.cells[rx][ry] 78 | active.append(start_at) 79 | 80 | costs = {} 81 | for x in range(self.cols): 82 | for y in range(self.rows): 83 | costs[self.grid.cells[x][y]] = random.randint(0, 99) 84 | while len(active) > 0: 85 | current = None 86 | min1 = float("inf") 87 | for i in range(len(active)): 88 | if costs[active[i]] < min1: 89 | current = active[i] 90 | min1 = costs[active[i]] 91 | available_neighbours = [] 92 | for c in current.neighbours: 93 | if len(c.connections) == 0: 94 | available_neighbours.append(c) 95 | if len(available_neighbours) > 0: 96 | neighbour = None 97 | min2 = float("inf") 98 | for i in range(len(available_neighbours)): 99 | if costs[available_neighbours[i]] < min2: 100 | neighbour = available_neighbours[i] 101 | min2 = costs[available_neighbours[i]] 102 | 103 | neighbour.isCurrent = True 104 | Grid.JoinAndDestroyWalls(current, neighbour) 105 | self.grid.Show(screen, show_heuristic, show_color_map) 106 | pygame.display.flip() 107 | neighbour.isCurrent = False 108 | 109 | active.append(neighbour) 110 | else: 111 | active.remove(current) 112 | self.isDone = True 113 | Update(self, screen, show_heuristic, show_color_map, show_path) 114 | 115 | if show_path: 116 | self.grid.Show(screen, show_heuristic, show_color_map, self.shortest_path) 117 | else: 118 | self.grid.Show(screen, show_heuristic, show_color_map) 119 | 120 | -------------------------------------------------------------------------------- /classes/ellers.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | import random 3 | from classes.grid import Grid, Update 4 | 5 | """ 6 | STEPS: 7 | 1. Initialize the cells of the first row to each exist in their own set. 8 | 2. Randomly join adjacent cells, but only if they are not in the same set. 9 | When joining adjacent cells, merge the cells of both sets into a single set, 10 | indicating that all cells in both sets are now connected. 11 | 3. For each set, randomly create vertical connections downward to the next row. 12 | Each remaining set must have at least one vertical connection. 13 | The cells in the next row thus connected must share the set of the cell above them. 14 | 4. Flesh out the next row by putting any remaining cells into their own sets. 15 | 5. Repeat until the last row is reached. 16 | 6. For the last row, join all adjacent cells that do not share a set, and omit the vertical connections, and you’re done! 17 | """ 18 | 19 | class Ellers: 20 | def __init__(self, grid,starting_set=0, path_color="BLUE"): 21 | self.grid = grid 22 | self.cols = grid.cols 23 | self.rows = grid.rows 24 | self.isDone = False 25 | self.starting_node = grid.cells[0][0] 26 | self.starting_node.isStartingNode = True 27 | self.end_node = grid.cells[self.cols-1][self.rows-1] 28 | self.end_node.isgoalNode = True 29 | self.show_path = True 30 | self.path_color = path_color 31 | self.shortest_path = None 32 | if path_color == "HSV": 33 | self.grid.path_color = white 34 | self.starting_set = starting_set 35 | 36 | def Generate(self, screen, show_heuristic, show_color_map,show_path): 37 | if not self.isDone: 38 | row_state = State(self.starting_set, self.cols) 39 | 40 | for y in range(self.rows): 41 | for x in range(self.cols): 42 | cell = self.grid.cells[x][y] 43 | if not cell.West: 44 | continue 45 | 46 | _set = row_state.SetFor(cell) 47 | old_set = row_state.SetFor(cell.West) 48 | 49 | if _set != old_set: 50 | if cell.South == None or random.randint(0, 1) == 0: 51 | temp = cell.West 52 | temp.isCurrent = True 53 | Grid.JoinAndDestroyWalls(cell, temp) 54 | self.grid.Show(screen, show_heuristic, show_color_map) 55 | pygame.display.flip() 56 | temp.isCurrent = False 57 | 58 | row_state.Merge(old_set, _set) 59 | 60 | if self.grid.cells[0][y].South: 61 | next_row = row_state.Next() 62 | 63 | for set in row_state.cells_in_set: 64 | cells = row_state.cells_in_set[set].copy() 65 | random.shuffle(cells) 66 | for _index, _cell in enumerate(cells): 67 | if _index == 0 or random.randint(0,2) == 0: 68 | if _cell.South: 69 | temp = _cell.South 70 | temp.isCurrent = True 71 | Grid.JoinAndDestroyWalls(_cell, temp) 72 | self.grid.Show(screen, show_heuristic, show_color_map) 73 | pygame.display.flip() 74 | temp.isCurrent = False 75 | 76 | next_row.Record(row_state.SetFor(_cell), temp) 77 | row_state = next_row 78 | self.isDone = True 79 | Update(self, screen, show_heuristic, show_color_map, show_path) 80 | 81 | if show_path: 82 | self.grid.Show(screen, show_heuristic, show_color_map, self.shortest_path) 83 | else: 84 | self.grid.Show(screen, show_heuristic, show_color_map) 85 | pygame.display.flip() 86 | 87 | class State: 88 | def __init__(self, starting_set, rows): 89 | self.cols = rows 90 | self.cells_in_set = {} 91 | self.set_for_cell = [None for x in range(self.cols)] 92 | self.next_set = starting_set 93 | 94 | def Record(self, set, cell): 95 | self.set_for_cell[cell.x] = set 96 | if set not in self.cells_in_set: 97 | self.cells_in_set[set] = [] 98 | self.cells_in_set[set].append(cell) 99 | 100 | def SetFor(self, cell): 101 | if not self.set_for_cell[cell.x]: 102 | self.Record(self.next_set, cell) 103 | self.next_set += 1 104 | 105 | return self.set_for_cell[cell.x] 106 | 107 | def Merge(self, winner, loser): 108 | for cell in self.cells_in_set[loser]: 109 | self.set_for_cell[cell.x] = winner 110 | self.cells_in_set[winner].append(cell) 111 | 112 | del self.cells_in_set[loser] 113 | def Next(self): 114 | return State(self.next_set, self.cols) 115 | -------------------------------------------------------------------------------- /classes/grid.py: -------------------------------------------------------------------------------- 1 | from classes.cell import * 2 | from ui.colors import * 3 | from classes.color import GridColor 4 | import random 5 | 6 | class Grid: 7 | def __init__(self, rows, cols, cell_size): 8 | self.rows = rows 9 | self.cols = cols 10 | self.cell_size = cell_size 11 | # Initialize cells 12 | self.cells = [[Cell(x, y, cell_size) for y in range(rows)] for x in range(cols)] 13 | self.PrepareGrid() 14 | self.heuristics = None 15 | self.path_color = orange 16 | self.isSorted = False 17 | self.path = {} 18 | self.path_values = [] 19 | 20 | def Flatten(self): 21 | flat_grid = [] 22 | for x in range(self.cols): 23 | for y in range(self.rows): 24 | if self.cells[x][y]: 25 | flat_grid.append(self.cells[x][y]) 26 | return flat_grid 27 | 28 | def PrepareGrid(self): 29 | for x in range(self.cols): 30 | for y in range(self.rows): 31 | if self.cells[x][y]: 32 | self.cells[x][y].neighbours = [] 33 | # East neighbour cell 34 | if x+1 < self.cols and self.cells[x+1][y]: 35 | self.cells[x][y].East = self.cells[x+1][y] 36 | self.cells[x][y].neighbours.append(self.cells[x+1][y]) 37 | # West neightbour cell 38 | if x-1 >= 0 and self.cells[x-1][y]: 39 | self.cells[x][y].West = self.cells[x-1][y] 40 | self.cells[x][y].neighbours.append(self.cells[x-1][y]) 41 | 42 | # North neighbour cell 43 | if y-1 >= 0 and self.cells[x][y-1]: 44 | self.cells[x][y].North = self.cells[x][y-1] 45 | self.cells[x][y].neighbours.append(self.cells[x][y-1]) 46 | # South neighbour cell 47 | if y+1 < self.rows and self.cells[x][y+1]: 48 | self.cells[x][y].South = self.cells[x][y+1] 49 | self.cells[x][y].neighbours.append(self.cells[x][y+1]) 50 | 51 | def JoinAndDestroyWalls(A, B, gridType="Normal"): 52 | if A.isAvailable and B.isAvailable: 53 | A.visited = True 54 | B.visited = True 55 | A.connections.append(B) 56 | B.connections.append(A) 57 | # ---- Grid & Mask Grid ----- 58 | if gridType == "Normal": 59 | if A.North == B: 60 | A.North, B.South = None, None 61 | elif A.South == B: 62 | A.South, B.North = None, None 63 | elif A.East == B: 64 | A.East, B.West = None, None 65 | elif A.West == B: 66 | A.West, B.East = None, None 67 | # ---- Polar Grid ------- 68 | elif gridType == "Polar": 69 | if A.inward == B : 70 | A.inward = None 71 | B.outward.remove(A) 72 | elif B.inward == A: 73 | B.inward = None 74 | A.outward.remove(B) 75 | elif A.clockwise == B or B.c_clockwise == A: 76 | A.clockwise = None 77 | B.c_clockwise = None 78 | elif A.c_clockwise == B or B.clockwise == A: 79 | A.c_clockwise = None 80 | B.clockwise = None 81 | # ----- Hex Grid ------- 82 | elif gridType == "Hex": 83 | if A.North == B: 84 | A.North, B.South = None, None 85 | elif A.South == B: 86 | A.South, B.North = None, None 87 | elif A.SouthEast == B: 88 | A.SouthEast, B.NorthWest = None, None 89 | elif A.SouthWest == B: 90 | A.SouthWest, B.NorthEast = None, None 91 | elif A.NorthEast == B: 92 | A.NorthEast, B.SouthWest = None, None 93 | elif A.NorthWest == B: 94 | A.NorthWest, B.SouthEast = None, None 95 | 96 | def Deadends(self): 97 | deadends_set = [] 98 | for x in range(len(self.cells)): 99 | for y in range(len(self.cells[x])): 100 | if len(self.cells[x][y].connections) == 1: 101 | deadends_set.append(self.cells[x][y]) 102 | return deadends_set 103 | 104 | def Braid(self, p=1): 105 | deadends = self.Deadends() 106 | random.shuffle(deadends) 107 | 108 | for cell in deadends: 109 | if len(cell.connections) != 1 or random.uniform(0, 1) > p: 110 | continue 111 | neighbours = [c for c in cell.neighbours if c not in cell.connections] 112 | best = [] 113 | for c in neighbours: 114 | if len(c.connections ) == 1: 115 | best.append(c) 116 | if len(best) == 0: 117 | best = neighbours 118 | 119 | neighbour = random.choice(best) 120 | Grid.JoinAndDestroyWalls(cell, neighbour) 121 | 122 | 123 | def Show(self, screen, show_heuristic, show_color_map, shortest_path = None): 124 | if not self.isSorted and shortest_path: 125 | for x in range(self.cols): 126 | for y in range(self.rows): 127 | if shortest_path.cells_record[x][y]: 128 | val = shortest_path.cells_record[x][y] 129 | self.path_values.append(val) 130 | self.path[str(val)] = ((x+0.5)*self.cell_size, (y+0.5)*self.cell_size) 131 | self.path_values = sorted(self.path_values) 132 | self.isSorted = True 133 | 134 | for x in range(self.cols): 135 | for y in range(self.rows): 136 | if self.cells[x][y]: 137 | self.cells[x][y].show_text = show_heuristic 138 | self.cells[x][y].show_highlight = show_color_map 139 | self.cells[x][y].Draw(screen, self.rows, self.cols) 140 | 141 | if shortest_path: 142 | for i in range(len(self.path_values)-1): 143 | pygame.draw.line(screen, orange, self.path[str(self.path_values[i])], self.path[str(self.path_values[i+1])], 2) 144 | pygame.draw.circle(screen, orange, self.path[str(self.path_values[i])], self.cell_size//6) 145 | 146 | def Update(self, screen, show_heuristic, show_color_map, show_path): 147 | # Calculate the step of each cell from the starting node 148 | # it's gonna initialize a grid that store the cost of each cell 149 | # from the starting node 150 | h_distances = self.starting_node.CalculateHeuristic(self.grid.rows, self.grid.cols) 151 | self.grid.heuristics = h_distances 152 | for x in range(len(self.grid.cells)): 153 | for y in range(len(self.grid.cells[x])): 154 | if self.grid.cells[x][y]: 155 | self.grid.cells[x][y].cost = 0 if self.grid.heuristics.cells_record[x][y] == None else self.grid.heuristics.cells_record[x][y] 156 | 157 | # get the path from the goad node to the starting node 158 | shortest_path = h_distances.BacktrackPath(self.end_node, self.starting_node) 159 | for x in range(self.grid.cols): 160 | for y in range(self.grid.rows): 161 | if self.grid.cells[x][y]: 162 | # check if the cell is in the path grid 163 | # If it is then set it as path 164 | if shortest_path.GetRecord(self.grid.cells[x][y]): 165 | self.grid.cells[x][y].isPath = True 166 | 167 | colorGridShortestPath = GridColor(self.path_color) 168 | colorGridShortestPath.distances(shortest_path, self.end_node, self.starting_node, self.grid) 169 | 170 | temp_path = h_distances.Merge(shortest_path) 171 | 172 | colorGridMap = GridColor(self.path_color) 173 | colorGridMap.distances(temp_path, self.end_node, self.starting_node, self.grid) 174 | 175 | for x in range(self.grid.cols): 176 | for y in range(self.grid.rows): 177 | if self.grid.cells[x][y]: 178 | self.grid.cells[x][y].highlight = colorGridShortestPath.UpdateColor(self.grid.cells[x][y]) 179 | self.grid.cells[x][y].color = colorGridMap.UpdateColor(self.grid.cells[x][y]) 180 | 181 | self.grid.Show(screen, show_heuristic, show_color_map) 182 | pygame.display.flip() 183 | self.shortest_path = shortest_path 184 | -------------------------------------------------------------------------------- /ui/ui.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from constants import width, height 3 | 4 | def translate(value, min1, max1, min2, max2): 5 | return min2 + (max2-min2)*((value-min1)/(max1-min1)) 6 | 7 | class Button: 8 | def __init__(self, text, position = (width-230, 400) , w = 100, h= 50, border=0, color = (0, 0, 0), borderColor = (0, 0, 0)): 9 | self.text = text 10 | self.position = position 11 | self.w = w 12 | self.h = h 13 | self.border = border 14 | self.temp = color 15 | self.color = color 16 | self.borderColor = borderColor 17 | self.font = 'freesansbold.ttf' 18 | self.fontSize = 25 19 | self.textColor = (255, 255, 255) 20 | self.state = False 21 | self.action = None 22 | self.clicked = False 23 | 24 | def HandleMouse(self,mouseClicked, HoverColor = (50, 120, 140)): 25 | m = pygame.mouse.get_pos() 26 | if m[0] >= self.position[0] and m[0] <= self.position[0] + self.w: 27 | if m[1] >= self.position[1] and m[1] <= self.position[1] + self.h: 28 | self.color = HoverColor 29 | if mouseClicked : 30 | self.color = (200, 200, 200) 31 | if self.action == None and mouseClicked == True: 32 | self.state = not self.state 33 | else: 34 | self.color = self.temp 35 | else: 36 | self.color = self.temp 37 | 38 | 39 | def Render(self, screen, mouseClicked, checker=True): 40 | if checker: 41 | self.HandleMouse(mouseClicked) 42 | font = pygame.font.Font(self.font, self.fontSize) 43 | text = font.render(self.text, True, self.textColor) 44 | textRect = text.get_rect() 45 | textRect.center = (self.position[0]+self.w//2, self.position[1]+self.h//2) 46 | if self.border > 0: 47 | pygame.draw.rect(screen, self.borderColor, pygame.Rect(self.position[0] - self.border//2, self.position[1] - self.border//2, self.w + self.border, self.h + self.border)) 48 | pygame.draw.rect(screen, self.color, pygame.Rect(self.position[0], self.position[1], self.w, self.h)) 49 | 50 | screen.blit(text, textRect) 51 | 52 | class Panel: 53 | def __init__(self, position = (width-400, 90), w= 385, h= 800, color=(2, 3, 12), alpha=100): 54 | self.position = position 55 | self.w = w 56 | self.h = h 57 | self.color = color 58 | self.alpha = alpha 59 | 60 | def Render(self, screen): 61 | s = pygame.Surface((self.w, self.h)) 62 | s.set_alpha(self.alpha) 63 | s.fill(self.color) 64 | screen.blit(s, (self.position[0], self.position[1])) 65 | #pygame.draw.rect(screen, self.color, pygame.Rect(self.position[0], self.position[1], self.w, self.h)) 66 | 67 | class ToggleButton: 68 | def __init__(self, position= ((width-200, 400)), w = 30, h=30, state=False, color=(20, 40, 50), activeColor=(240, 140, 200)): 69 | self.position = position 70 | self.w = w 71 | self.h = h 72 | self.clicked = False 73 | self.state = state 74 | self.temp = (activeColor, color) 75 | self.activeColor = activeColor 76 | self.color = color 77 | 78 | def HandleMouse(self, HoverColor = (50, 120, 140)): 79 | m = pygame.mouse.get_pos() 80 | 81 | if m[0] >= self.position[0] and m[0] <= self.position[0] + self.w: 82 | if m[1] >= self.position[1] and m[1] <= self.position[1] + self.h: 83 | self.color = HoverColor 84 | self.activeColor = HoverColor 85 | if pygame.mouse.get_pressed()[0]: 86 | self.color = (255, 255, 255) 87 | if self.clicked: 88 | self.state = not self.state 89 | else: 90 | self.color = self.temp[1] 91 | self.activeColor =self.temp[0] 92 | else: 93 | self.color = self.temp[1] 94 | self.activeColor =self.temp[0] 95 | 96 | def Render(self, screen, clicked): 97 | self.HandleMouse() 98 | self.clicked = clicked 99 | if self.state == True: 100 | pygame.draw.rect(screen, self.activeColor, pygame.Rect(self.position[0], self.position[1], self.w, self.h)) 101 | else: 102 | pygame.draw.rect(screen, self.color, pygame.Rect(self.position[0], self.position[1], self.w, self.h)) 103 | return self.state 104 | 105 | class TextUI: 106 | def __init__(self,text, position, fontColor, anchor='center'): 107 | self.position = position 108 | self.text = text 109 | self.font = 'freesansbold.ttf' 110 | self.anchor = anchor 111 | self.fontSize = 18 112 | self.fontColor = fontColor 113 | def Render(self, screen): 114 | font = pygame.font.Font(self.font, self.fontSize) 115 | text = font.render(self.text, True, self.fontColor) 116 | textRect = text.get_rect() 117 | setattr(textRect, self.anchor, self.position) 118 | #textRect.center = (self.position[0], self.position[1]) 119 | screen.blit(text, textRect) 120 | 121 | class DigitInput: 122 | def __init__(self,startingValue, position = (width-320, 100), w= 300, h= 600, color=(8, 3, 12)): 123 | self.position = position 124 | self.text = str(startingValue) 125 | self.fontColor = (255, 255, 255) 126 | self.fontSize = 18 127 | self.font = 'freesansbold.ttf' 128 | self.w = w 129 | self.h = h 130 | self.color = color 131 | self.value = int(self.text) 132 | self.hoverEnter = False 133 | 134 | def Check(self, backspace,val): 135 | 136 | if self.hoverEnter == True: 137 | if backspace == True: 138 | 139 | if len(str(self.value)) <= 0 or len(str(self.value))-1 <= 0: 140 | self.value = 0 141 | else: 142 | self.value = int(str(self.value)[:-1]) 143 | 144 | else: 145 | if self.text.isdigit(): 146 | self.value = int(str(self.value) + str(self.text)) 147 | else: 148 | for el in self.text: 149 | if el.isdigit() != True: 150 | self.text = self.text.replace(el, "") 151 | backspace == False 152 | self.text = "" 153 | 154 | def updateText(self, val, pressed): 155 | m = pygame.mouse.get_pos() 156 | if m[0] >= self.position[0] and m[0] <= self.position[0] + self.w: 157 | if m[1] >= self.position[1] and m[1] <= self.position[1] + self.h: 158 | self.hoverEnter = True 159 | if pressed == True: 160 | self.text += val 161 | else: 162 | self.hoverEnter = False 163 | val = "" 164 | else: 165 | self.hoverEnter = False 166 | val = "" 167 | 168 | 169 | def Render(self, screen, val, backspace, pressed): 170 | self.updateText(val, pressed) 171 | self.Check(backspace, val) 172 | font = pygame.font.Font(self.font, self.fontSize) 173 | text = font.render(str(self.value), True, self.fontColor) 174 | textRect = text.get_rect() 175 | textRect.center = (self.position[0]+self.w//2, self.position[1]+self.h//2) 176 | pygame.draw.rect(screen, self.color, pygame.Rect(self.position[0], self.position[1], self.w, self.h)) 177 | screen.blit(text, textRect) 178 | 179 | class Slider: 180 | def __init__(self,x, y, val, min1, max1, length, h, max=500): 181 | self.value = val 182 | self.x = x 183 | self.y = y 184 | self.h = h 185 | self.min1 = min1 186 | self.max1 = max1 187 | self.length = length 188 | self.lineColor = (20, 10, 20) 189 | self.rectradius = 10 190 | self.temp_radius = self.rectradius 191 | self.rectColor = (255, 255, 255) 192 | self.v = 0.2 193 | self.temp = self.lineColor 194 | self.max = max 195 | 196 | def Calculate(self, val): 197 | self.v = translate(val, 0, self.length, 0, 1) 198 | self.value = self.v * self.max 199 | 200 | def HandleMouse(self): 201 | mx, my = pygame.mouse.get_pos() 202 | 203 | if mx >= self.x and mx <= self.x + self.length: 204 | if my >= self.y and my <= self.y + self.h: 205 | self.rectradius = 15 206 | if pygame.mouse.get_pressed()[0]: 207 | self.Calculate(mx - self.x) 208 | else: 209 | self.lineColor = self.temp 210 | self.rectradius = self.temp_radius 211 | else: 212 | self.lineColor = self.temp 213 | self.rectradius = self.temp_radius 214 | 215 | def Render(self,screen): 216 | self.HandleMouse() 217 | pygame.draw.rect(screen, self.lineColor, pygame.Rect(self.x, self.y, self.length, self.h)) 218 | x = int((self.v * self.length) + self.x) 219 | pygame.draw.rect(screen, self.rectColor, pygame.Rect(self.x, self.y, int( self.v * self.length), self.h)) 220 | pygame.draw.circle(screen, (130, 213, 151), (x, self.y + (self.rectradius//2)), self.rectradius) 221 | return self.value 222 | 223 | 224 | 225 | ## Don't Mind the code below it's an absolute mess 🍝 226 | class DropDownButton: 227 | def __init__(self, text="Select", position = (width-230, 400) , w = 100, h= 50, children_size=2, border=0, color = (0, 0, 0), borderColor = (0, 0, 0)): 228 | self.text = text 229 | self.position = position 230 | self.w = w 231 | self.h = h 232 | self.border = border 233 | self.temp = color 234 | self.color = color 235 | self.children_size = children_size 236 | self.childs = [] 237 | for i in range(self.children_size): 238 | button = Button("button " +str(i), (position[0], position[1] + h + h*i+ 2), w, h, border, color, borderColor) 239 | self.childs.append(button) 240 | self.borderColor = borderColor 241 | self.font = 'freesansbold.ttf' 242 | self.fontSize = 25 243 | self.textColor = (255, 255, 255) 244 | self.state = False 245 | self.action = None 246 | self.selected = False 247 | self.folded = False 248 | self.currentIndex = None 249 | 250 | def updateChildren(self, value): 251 | self.children_size = value 252 | for i in range(self.children_size): 253 | button = Button("button " +str(i), (self.position[0], self.position[1] + h + h*i+ 2), self.w, self.h, 0, self.border, self.color, self.borderColor) 254 | self.childs.append(button) 255 | 256 | def HandleMouse(self,mouseClicked, HoverColor = (50, 120, 140)): 257 | if self.folded == False: 258 | m = pygame.mouse.get_pos() 259 | if m[0] >= self.position[0] and m[0] <= self.position[0] + self.w: 260 | if m[1] >= self.position[1] and m[1] <= self.position[1] + self.h: 261 | self.color = HoverColor 262 | if mouseClicked == True: 263 | self.folded = not self.folded 264 | self.color = (200, 200, 200) 265 | if self.action == None and mouseClicked == True: 266 | self.state = not self.state 267 | else: 268 | self.color = self.temp 269 | else: 270 | self.color = self.temp 271 | else: 272 | m = pygame.mouse.get_pos() 273 | if m[0] >= self.position[0] and m[0] <= self.position[0] + self.w: 274 | if m[1] >= self.position[1] and m[1] <= self.position[1] + self.h: 275 | self.color = HoverColor 276 | if mouseClicked == True: 277 | self.folded = not self.folded 278 | self.color = (200, 200, 200) 279 | if self.action == None and mouseClicked == True: 280 | self.state = not self.state 281 | else: 282 | self.color = self.temp 283 | else: 284 | self.color = self.temp 285 | for child in self.childs: 286 | m = pygame.mouse.get_pos() 287 | if m[0] >= child.position[0] and m[0] <= child.position[0] + child.w: 288 | if m[1] >= child.position[1] and m[1] <= child.position[1] + child.h: 289 | child.color = HoverColor 290 | if mouseClicked==True: 291 | child.color = (200, 200, 200) 292 | self.text = child.text 293 | self.currentIndex = self.childs.index(child) 294 | self.folded = not self.folded 295 | if child.action == None and mouseClicked == True: 296 | child.state = not child.state 297 | else: 298 | child.color = child.temp 299 | else: 300 | child.color = child.temp 301 | 302 | 303 | def Render(self, screen, mouseClicked, checker = True): 304 | if checker: 305 | self.HandleMouse(mouseClicked) 306 | font = pygame.font.Font(self.font, self.fontSize) 307 | text = font.render(self.text, True, self.textColor) 308 | textRect = text.get_rect() 309 | textRect.center = (self.position[0]+self.w//2, self.position[1]+self.h//2) 310 | if self.border > 0: 311 | pygame.draw.rect(screen, self.borderColor, pygame.Rect(self.position[0] - self.border//2, self.position[1] - self.border//2, self.w + self.border, self.h + self.border)) 312 | pygame.draw.rect(screen, self.color, pygame.Rect(self.position[0], self.position[1], self.w, self.h)) 313 | 314 | screen.blit(text, textRect) 315 | if self.folded == True: 316 | for child in self.childs: 317 | child.Render(screen, mouseClicked, False) 318 | --------------------------------------------------------------------------------