diff --git a/mazes/MazeGenSolve/MazeGenSolve.pyde b/mazes/MazeGenSolve/MazeGenSolve.pyde new file mode 100644 index 0000000..cbc282f --- /dev/null +++ b/mazes/MazeGenSolve/MazeGenSolve.pyde @@ -0,0 +1,276 @@ +import random, time +from timeit import default_timer as timer + +class Cell: + def __init__(self, x, y): + self.x, self.y = x, y + self.right, self.bottom, self.visited, self.current, self.start, self.end = True, True, False, False, False, False + self.parent = None + self.marked = False + + def __str__(self): + return 'Cell({}, {}, bottom: {}, right: {}, visited: {})'.format(self.x, self.y, self.bottom, self.right, self.visited) + + def getNeighbor(self): + possible = self.neighbors() + possible = [thing for thing in possible if thing != None] + if possible: + choice = random.choice(possible) + return grid[choice[0]][choice[1]] + return None + + def pathingNeighbors(self): + pass + + def neighbors(self): + global offsets + neighbors = [] + for offset in offsets: + neighbor = (self.x + offset[0], self.y + offset[1]) + if not valid(neighbor): + neighbors.append(None) + continue + if grid[neighbor[0]][neighbor[1]].visited: + neighbors.append(None) + continue + neighbors.append(neighbor) + return neighbors + + def crender(self): + global divX, divY + translate(self.x * divX, self.y * divY) + # Drawing Cell Background + # Visited, Unvisited, Highlighted + + + if self.start: + fill(190, 10, 10) + elif self.end: + fill(190, 10, 10) + elif self.marked: + fill(35, 155, 165) + elif self.visited: + fill(0, 42, 135) + else: + fill(0, 2, 30) + + if self.current: + fill(0, 127, 196) + self.current = False + noStroke() + rect(0, 0, divX, divY) + + # Drawing Cell Lines + stroke(255) + fill(255) + strokeWeight(2.5) + if not self.visited: + noStroke() + if self.bottom: + line(0, divY, divX, divY) + if self.right: + line(divX, 0, divX, divY) + resetMatrix() + +class PathingCell: + def __init__(self, coords, parent=None): + self.coords = coords + self.parent = parent + self.h, self.g, self.f = 0, 0, 0 + + def __eq__(self, other): + return self.coords == other.coords + +def openWalls(x1, y1, x2, y2): + global offsets + # Bottom, Right, Left, Top + offset = (x2 - x1, y2 - y1) + if offset == offsets[0]: + grid[x1][y1].bottom = False + if offset == offsets[1]: + grid[x1][y1].right = False + if offset == offsets[2]: + grid[x2][y2].right = False + if offset == offsets[3]: + grid[x2][y2].bottom = False + + +def valid(coordinate): + global columns, rows + return not (coordinate[0] < 0 or coordinate[0] >= columns or coordinate[1] < 0 or coordinate[1] >= rows) + +def generate(): + global complete, columns, rows, grid, divX, divY, current, offsets, start, end + # Bottom, Right, Left, Top + complete = False + offsets = [(0, 1), (1, 0), (-1, 0), (0, -1)] + columns, rows = 100, 100 + divX, divY = width / float(columns), height / float(rows) + grid = [[Cell(x, y) for y in range(rows)] for x in range(columns)] + current = grid[random.randint(0, columns-1)][random.randint(0, rows-1)] + start = current + ends = [(random.randint(0, columns-1), random.randint(0, rows-1)) for _ in range(5)] + end = ends.pop(0) + prevDist = 0 + for coord in ends: + dist = distance(start.x, start.y, coord[0], coord[1]) + if dist > prevDist: + prevDist = dist + end = coord + end = grid[end[0]][end[1]] + end.end = True + current.visited = True + current.start = True + +def setup(): + size(750, 750) + # random.seed(1) + generate() + # noLoop() + +def astar(grid, start, end): + start_cell = PathingCell(start, None) + end_cell = PathingCell(end, None) + + open_list = [start_cell] + closed_list = [] + + # While the open list is not empty + while len(open_list) > 0: + # Find the best cell to loop no + current_cell, current_index = open_list[0], 0 + for index, cell in enumerate(open_list): + if current_cell.f > cell.f: + current_cell = cell + current_index = index + + # Remvoe the new current cell from the open list and to the closed list + closed_list.append(current_cell) + open_list.pop(current_index) + + # Check if the current cell is the end cell (we've found the exit) + if current_cell == end_cell: + current = current_cell + path = [] + while current is not None: + path.append(current.coords) + current = current.parent + return path[::-1] + + children = [] + # Bottom, Right, Left, Top + # Find neighbor cells we can move into + for index, offset in enumerate(offsets): + # Calculate the coordinate of the adjacent neighbor we're looking at + new_coord = (current_cell.coords[0] + offset[0], current_cell.coords[1] + offset[1]) + # If the coordinate we're looking at is valid + if valid(new_coord): + # Create a new pathing cell + new_cell = PathingCell(new_coord, current_cell) + # is the bottom open? + if index == 0: + if not grid[current_cell.coords[0]][current_cell.coords[1]].bottom: + children.append(new_cell) + # is the right open? + elif index == 1: + if not grid[current_cell.coords[0]][current_cell.coords[1]].right: + children.append(new_cell) + # is the left side open? + elif index == 2: + if not grid[new_coord[0]][new_coord[1]].right: + children.append(new_cell) + # is the top open? + elif index == 3: + if not grid[new_coord[0]][new_coord[1]].bottom: + children.append(new_cell) + + for child in children: + # is the child we looked at already closed? + skip = False + for closed_child in closed_list: + if child == closed_child: + skip = True + if skip: + continue + + # calculate algorithmic values + child.g = current_cell.g + 1 + child.h = ((current_cell.coords[0] - end_cell.coords[0]) ** 2) + ((current_cell.coords[1] - end_cell.coords[1]) ** 2) + child.f = child.g + child.h + + # is the cell already open? is the cell we made worst in path iterations? + for open_cell in open_list: + if child == open_cell and child.g > open_cell.g: + continue + + # open the new cell + open_list.append(child) + +def randomizeOpen(chance=0.05): + global grid + for row in grid: + for cell in row: + if random.random() < chance: + if random.choice([True, False]): + cell.right = False + else: + cell.bottom = False + +def mazeGenTick(): + global current, next, i + for _ in range(500): + next = current.getNeighbor() + if next: + next.parent = current + openWalls(current.x, current.y, next.x, next.y) + current = next + grid[current.x][current.y].current = True + current.visited = True + else: + if current.parent != None: + current.parent + current = current.parent + grid[current.x][current.y].current = True + else: + global start + if current == start and current.getNeighbor() == None: + global complete + complete = True + print('Done') + +def distance(x1, y1, x2, y2): + return sqrt(((x2 - x1) ** 2) + ((y2 - y1) ** 2)) + +# Render the maze itself +def render(): + background(0) + for column in grid: + for cell in column: + cell.crender() + +# Render the path of coordinates connecting them with ellipses and lines +def renderpath(path): + fill(48, 255, 103) + stroke(48, 255, 103) + for index, coord in enumerate(path[:-1]): + ellipse(coord[0] * divX + (divX * 0.5), coord[1] * divY + (divY * 0.5), divX / 3, divY / 3) + line(coord[0] * divX + (divX * 0.5), coord[1] * divY + (divY * 0.5), path[index + 1][0] * divX + (divX * 0.5), path[index + 1][1] * divY + (divY * 0.5)) + ellipse(path[-1][0] * divX + (divX * 0.5), path[-1][1] * divY + (divY * 0.5), divX / 3, divY / 3) + +def draw(): + render() + global complete + if complete: + # randomizeOpen(0.15) + startTime = timer() + path = astar(grid, (start.x, start.y), (end.x, end.y)) + endTime = timer() + # for coord in path: + # grid[coord[0]][coord[1]].marked = True + render() + print('Maze is {} x {} tiles with a solve path length of {} tiles.'.format(columns, rows, len(path))) + print('The solve path took about {}s to complete.'.format(round(endTime - startTime, 3))) + renderpath(path) + noLoop() + else: + mazeGenTick() diff --git a/mazes/MazeGenSolve/sketch.properties b/mazes/MazeGenSolve/sketch.properties new file mode 100644 index 0000000..2456b0a --- /dev/null +++ b/mazes/MazeGenSolve/sketch.properties @@ -0,0 +1,2 @@ +mode=Python +mode.id=jycessing.mode.PythonMode diff --git a/mazes/MazeGenV2/MazeGenV2.pyde b/mazes/MazeGenV2/MazeGenV2.pyde new file mode 100644 index 0000000..939c8ee --- /dev/null +++ b/mazes/MazeGenV2/MazeGenV2.pyde @@ -0,0 +1,146 @@ +import random, time + +class Cell: + def __init__(self, x, y): + self.x, self.y = x, y + self.right, self.bottom, self.visited, self.current, self.start = True, True, False, False, False + def __str__(self): + return 'Cell({}, {}, bottom: {}, right: {}, visited: {})'.format(self.x, self.y, self.bottom, self.right, self.visited) + + def getNeighbor(self): + possible = self.neighbors() + possible = [thing for thing in possible if thing != None] + if possible: + choice = random.choice(possible) + return grid[choice[0]][choice[1]] + return None + + # def getNeighbor(self): + # possible = self.neighbors() + # if any([thing != None for thing in possible]): + # xchoice = None + # while xchoice == None: + # xchoice = random.choices(possible, weights=[2, 1, 1, 2], k=1)[0] + # return grid[xchoice[0]][xchoice[1]] + # else: + # return None + + def neighbors(self): + global offsets + neighbors = [] + for offset in offsets: + neighbor = (self.x + offset[0], self.y + offset[1]) + if not valid(neighbor): + neighbors.append(None) + continue + if grid[neighbor[0]][neighbor[1]].visited: + neighbors.append(None) + continue + neighbors.append(neighbor) + return neighbors + + def render(self): + global divX, divY + translate(self.x * divX, self.y * divY) + # Drawing Cell Background + # Visited, Unvisited, Highlighted + + if self.start: + fill(28, 147, 158) + elif self.visited: + fill(0, 42, 135) + else: + fill(0, 2, 30) + + if self.current: + fill(0, 127, 196) + self.current = False + noStroke() + rect(0, 0, divX, divY) + + # Drawing Cell Lines + stroke(255) + fill(255) + strokeWeight(2.5) + if not self.visited: + noStroke() + if self.bottom: + line(0, divY, divX, divY) + if self.right: + line(divX, 0, divX, divY) + resetMatrix() + +def openWalls(x1, y1, x2, y2): + global offsets + # Bottom, Right, Left, Top + offset = (x2 - x1, y2 - y1) + if offset == offsets[0]: + grid[x1][y1].bottom = False + if offset == offsets[1]: + grid[x1][y1].right = False + if offset == offsets[2]: + grid[x2][y2].right = False + if offset == offsets[3]: + grid[x2][y2].bottom = False + + +def valid(coordinate): + global columns, rows + return not (coordinate[0] < 0 or coordinate[0] >= columns or coordinate[1] < 0 or coordinate[1] >= rows) + +def generate(): + global complete, columns, rows, grid, divX, divY, current, offsets + # Bottom, Right, Left, Top + complete = 0 + offsets = [(0, 1), (1, 0), (-1, 0), (0, -1)] + columns, rows = 50, 50 + divX, divY = width / float(columns), height / float(rows) + grid = [[Cell(x, y) for y in range(rows)] for x in range(columns)] + current = grid[random.randint(0, columns-1)][random.randint(0, rows-1)] + current.visited = True + current.start = True + +def setup(): + size(750, 750) + frameRate(10000) + generate() + +def mazeGenTick(loops=500): + global current, next, i + for _ in range(loops): + next = current.getNeighbor() + if next: + i = 0 + next.parent = current + openWalls(current.x, current.y, next.x, next.y) + current = next + grid[current.x][current.y].current = True + current.visited = True + else: + try: + current.parent + current = current.parent + grid[current.x][current.y].current = True + except: + global complete + complete += 1 + +def render(): + background(0) + for column in grid: + for cell in column: + cell.render() + +i = 0 +def draw(): + render() + global complete + if complete > 10: + # saveFrame("maze-###.png") + time.sleep(2.0) + generate() + if complete > 1: + mazeGenTick() + render() + else: + mazeGenTick(1) diff --git a/mazes/MazeGenV2/sketch.properties b/mazes/MazeGenV2/sketch.properties new file mode 100644 index 0000000..2456b0a --- /dev/null +++ b/mazes/MazeGenV2/sketch.properties @@ -0,0 +1,2 @@ +mode=Python +mode.id=jycessing.mode.PythonMode diff --git a/mazes/README.md b/mazes/README.md new file mode 100644 index 0000000..4035302 --- /dev/null +++ b/mazes/README.md @@ -0,0 +1,21 @@ +# mazes + +## About + +Projects that generate mazes using various algorithms. Developed primarily in 2018-2019. All of these are generating simple square mazes, just using different algorithms and occaisionally a little twist. + +## Sketches + +- **MazeGenV2** Second maze generation attempt. First *working* maze implementation. Recursive Backtracker algorithm. + +- **MazeGenSolve** - Maze gen with a completed path shown after. Recursive Backtracker algorithm for Generation, A* algorithm for pathfinding (very similar in theory). + +- **MazeGenBinaryTree** - Binary Tree algorithm + +- **MazeGenGrowingTree** - Growing Tree algorithm + +- **MazeGenKruskalTree** - Kruskal Tree algorithm + +- **MazeGenSidewinder** - Sidewinder algorithm. A little bit buggy at the end, but properly implemented none the less. + +- **MazeClustersCreator** - Not so much a maze as a interesting side-project when developing my maze generators. Somewhat buggy. \ No newline at end of file