kruskal and sidewinder maze generation sketches

This commit is contained in:
Xevion
2019-10-14 12:49:28 -05:00
parent cd70c32f85
commit b90f8326ba
4 changed files with 387 additions and 0 deletions

View File

@@ -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()

View File

@@ -0,0 +1,2 @@
mode=Python
mode.id=jycessing.mode.PythonMode

View File

@@ -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)

View File

@@ -0,0 +1,2 @@
mode=Python
mode.id=jycessing.mode.PythonMode