From b90f8326ba23057624a558cc926014052bb10eec Mon Sep 17 00:00:00 2001 From: Xevion Date: Mon, 14 Oct 2019 12:49:28 -0500 Subject: [PATCH] kruskal and sidewinder maze generation sketches --- mazes/MazeGenKruskal/MazeGenKruskal.pyde | 224 ++++++++++++++++++ mazes/MazeGenKruskal/sketch.properties | 2 + .../MazeGenSidewinder/MazeGenSidewinder.pyde | 159 +++++++++++++ mazes/MazeGenSidewinder/sketch.properties | 2 + 4 files changed, 387 insertions(+) create mode 100644 mazes/MazeGenKruskal/MazeGenKruskal.pyde create mode 100644 mazes/MazeGenKruskal/sketch.properties create mode 100644 mazes/MazeGenSidewinder/MazeGenSidewinder.pyde create mode 100644 mazes/MazeGenSidewinder/sketch.properties diff --git a/mazes/MazeGenKruskal/MazeGenKruskal.pyde b/mazes/MazeGenKruskal/MazeGenKruskal.pyde new file mode 100644 index 0000000..183812c --- /dev/null +++ b/mazes/MazeGenKruskal/MazeGenKruskal.pyde @@ -0,0 +1,224 @@ +import random, time, string + +class Cell: + def __init__(self, x, y): + self.x, self.y = x, y + self.identity = None + 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 neighbors(self, identityCheck=True): + global offsets + neighbors = [] + for offset in offsets: + neighbor = (self.x + offset[0], self.y + offset[1]) + # If the neighbor isn't real + if not valid(neighbor): + continue + # If the neighbor hasn't been claimed + elif not grid[neighbor[0]][neighbor[1]].visited: + neighbors.append(neighbor) + continue + # We're checking for their identity + elif identityCheck: + # If the neighbor isn't like me + if grid[neighbor[0]][neighbor[1]].identity != self.identity: + neighbors.append(neighbor) + continue + return neighbors + + def render(self): + global divX, divY, showNumbers + translate(self.x * divX, self.y * divY) + # Drawing Cell Background + # Visited, Unvisited, Highlighted + if 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+1, divY+1) + + # Drawing Cell Lines + stroke(255) + fill(255) + strokeWeight(2.5) + if showNumbers: + textSize(50) + textAlign(CENTER, CENTER) + text(symbolize(self.identity), divX / 2.0, divY / 2.0) + 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, grid + # 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 symbolize(n): + stri = '' + while n > 0: + curLetter = (n - 1) % 26 + stri += string.ascii_uppercase[curLetter] + n = (n - (curLetter + 1)) / 26 + return stri + + +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 columns, rows, grid, divX, divY, offsets + # Bottom, Right, Left, Top + offsets = [(0, 1), (1, 0), (-1, 0), (0, -1)] + columns, rows = 10, 10 + divX, divY = width / float(columns), height / float(rows) + grid = [[Cell(x, y) for y in range(rows)] for x in range(columns)] + + global treeSet, i, nodesAdded, showNumbers + try: + showNumbers + except: + showNumbers = False + i = 0 + nodesAdded = 0 + treeSet = [] + +# Tree Class. But it's really not a tree. +class Tree: + def __init__(self, identity): + self.tree = [] + self.identifier = identity + + def append(self, node): + self.tree.append(node) + grid[node[0]][node[1]].identity = self.identifier + grid[node[0]][node[1]].visited = True + + # Merge another tree with this tree + def merge(self, other): + for node in other.tree: + grid[node[0]][node[1]].identity = self.identifier + self.tree.append(node) + + # Find a node in the list with an edge + def getEdge(self, debug=False): + random.shuffle(self.tree) + # For every node in the tree + for index, node in enumerate(self.tree): + # if the node has any neighbors that aren't like us, return it + if len(grid[node[0]][node[1]].neighbors()) > 0: + return node + print('Couldn\'t find an edge') + +def setup(): + size(1000, 1000) + noLoop() + generate() + frameRate(10) + +def checkMaze(): + for row in grid: + for cell in row: + if not cell.visited: + return False + return True + +# Kruskall's algorithm +def tick(): + # choose a node, find it's tree + # if it has no tree, create a tree with a unvisited neighbor and open a wall between them + # if it's in a tree + # get the tree to return a node on the edge (has a neighbor that isn't in it's own tree) + # merge the two trees together (the neighboring node's tree is added to the edge node's tree) + # open the wall between the two nodes and the two trees have been merged properly + # continue until no edges can be found in any tree (how to implement this without guesswork or expensive iterating?) + + global treeSet, i, nodesAdded + # choose a random cell to work on, whether it's in a 'tree' or is unvisited + choice = random.randint(0, columns-1), random.randint(0, rows-1) + + if nodesAdded == columns * rows: + print('Maze completed ({} cells)'.format(nodesAdded)) + noLoop() + return True + + # If the node is unclaimed + if grid[choice[0]][choice[1]].identity == None: + # Find it's unclaimed neighbors (if any) + neighbors = grid[choice[0]][choice[1]].neighbors() + neighbors = [neighbor for neighbor in neighbors if grid[neighbor[0]][neighbor[1]].identity == None] + # We got a unclaimed lone node, just skip it for now (crap implementation, but needless work) + if len(neighbors) == 0: + return + else: + tree = Tree(i) + i += 1 + nodesAdded += 2 + neighbor = random.choice(neighbors) + openWalls(choice[0], choice[1], neighbor[0], neighbor[1]) + tree.append(choice) + tree.append(neighbor) + treeSet.append(tree) + else: + # get the identifier of our choice + choiceIdentity = grid[choice[0]][choice[1]].identity + # find an edge of the treeset and change our choice cell to that edge + choice = treeSet[choiceIdentity].getEdge() + neighbors = grid[choice[0]][choice[1]].neighbors() + # get the neighbors of it that are different + neighbor = random.choice(neighbors) + # If the neighbor to the chosen cell's treeSet's edge cell is in another treeSet + if grid[neighbor[0]][neighbor[1]].identity != None: + get = treeSet[grid[neighbor[0]][neighbor[1]].identity] + treeSet[grid[neighbor[0]][neighbor[1]].identity] = None + treeSet[choiceIdentity].merge(get) + # If it's just an unclaimed cell + else: + nodesAdded += 1 + treeSet[choiceIdentity].append(neighbor) + openWalls(choice[0], choice[1], neighbor[0], neighbor[1]) + +# Render the maze +def render(): + background(0) + for column in grid: + for cell in column: + cell.render() + +def draw(): + for _ in range(columns): + # if the maze is complete, it'll return True instead of None, if so, break the loop + if tick(): + break + render() + +# Switch off the number display +def keyPressed(): + global showNumbers + showNumbers = not showNumbers + redraw() + +# Regenerate the maze +def mouseClicked(): + loop() + generate() diff --git a/mazes/MazeGenKruskal/sketch.properties b/mazes/MazeGenKruskal/sketch.properties new file mode 100644 index 0000000..2456b0a --- /dev/null +++ b/mazes/MazeGenKruskal/sketch.properties @@ -0,0 +1,2 @@ +mode=Python +mode.id=jycessing.mode.PythonMode diff --git a/mazes/MazeGenSidewinder/MazeGenSidewinder.pyde b/mazes/MazeGenSidewinder/MazeGenSidewinder.pyde new file mode 100644 index 0000000..680397f --- /dev/null +++ b/mazes/MazeGenSidewinder/MazeGenSidewinder.pyde @@ -0,0 +1,159 @@ +import random + +class Cell: + def __init__(self, x, y): + self.x, self.y = x, y + self.right, self.bottom, self.visited, self.live = True, True, False, False + + # Identify the neighbors of the cell + def neighbors(self): + global offsets + neighbors = [] + for offset in offsets: + neighbor = (self.x + offset[0], self.y + offset[1]) + if not valid(neighbor): + continue + if grid[neighbor[0]][neighbor[1]].visited: + continue + neighbors.append(neighbor) + return neighbors + + # Render the single cell + def render(self): + global divX, divY + translate(self.x * divX, self.y * divY) + # Drawing Cell Background + # Visited, Unvisited, Highlighted + + if self.live: + fill(244, 117, 117) + elif self.visited: + fill(255) + else: + fill(204) + noStroke() + rect(0, 0, divX, divY) + + # Drawing Cell Lines + stroke(0) + fill(255) + strokeWeight(2.5) + if self.bottom: + line(0, divY, divX, divY) + if self.right: + line(divX, 0, divX, divY) + resetMatrix() + +# Open walls between two cells on the grid +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 + +# Validates whether a coordinate is valid with the curret columns and rows set +def valid(coordinate): + global columns, rows + return not (coordinate[0] < 0 or coordinate[0] >= columns or coordinate[1] < 0 or coordinate[1] >= rows) + +# Generates a new grid and cellList (with start) for the maze generation. +# Serves mostly to ease the process of regenerating a maze without restarting the Sketch +def generate(xx=None, yy=None): + global columns, rows, offsets + # Bottom, Right, Left, Top + offsets = [(0, 1), (1, 0), (-1, 0), (0, -1)] + columns, rows = 25, 25 + + global grid, divX, divY + divX, divY = width / float(columns), height / float(rows) + grid = [[Cell(x, y) for y in range(rows)] for x in range(columns)] + + global current, runSet, runSetActive + current = 0, 0 + runSet = [] + runSetActive = False + +def pixelToCoordinates(x, y): + return int(x / float + (divX)), int(y / float(divY)) + +def setup(): + size(750, 750) + generate() + +# Runs the cell.render() action on every cell +def render(): + background(0) + for row in grid: + for cell in row: + cell.render() + +def tick(): + global current, runSet, runSetActive + if runSetActive: + if len(runSet) > 0: + print('a') + get = runSet[0] + if get == []: + return + print('b') + print(runSet, get) + choice = random.choice(get) + print('c') + openWalls(choice[0], choice[1], choice[0], choice[1] - 1) + print('d') + del runSet[0] + return + else: + runSet = [[]] + runSetActive = False + + # Are we done with the maze? + if current[0] == columns and current[1] == rows - 1: + if len(runSet) > 0: + runSetActive = True + return + print('Done') + render() + noLoop() + # Are we on the horozontal edge? + elif current[0] == columns: + if not runSetActive: + if current[1] > 0: + for coord in runSet[-1]: + grid[coord[0]][coord[1]].live = False + runSetActive = True + current = 0, current[1] + 1 + # Keep carving runSets + else: + # If you're on the ceiling of the maze, just carve and skip the whole thing. + if current[1] == 0: + openWalls(current[0], current[1], current[0] + 1, current[1]) + # Keep carving east + elif current[0] == 0 or not random.choice([True, False]): + runSet[-1].append((current[0], current[1])) + grid[current[0]][current[1]].live = True + openWalls(current[0], current[1], current[0] + 1, current[1]) + # Break out new runSet + else: + for coord in runSet[-1]: + grid[coord[0]][coord[1]].live = False + runSet.append([(current[0], current[1])]) + if not runSetActive: + current = current[0] + 1, current[1] + +def draw(): + for _ in range(columns): + tick() + render() + +def mouseClicked(): + loop() + generate(mouseX, mouseY) diff --git a/mazes/MazeGenSidewinder/sketch.properties b/mazes/MazeGenSidewinder/sketch.properties new file mode 100644 index 0000000..2456b0a --- /dev/null +++ b/mazes/MazeGenSidewinder/sketch.properties @@ -0,0 +1,2 @@ +mode=Python +mode.id=jycessing.mode.PythonMode