Added "Maze" extension

This commit is contained in:
Mario Voigt 2020-12-09 11:07:01 +01:00
parent 58d97bdca4
commit 4042ceb405
3 changed files with 634 additions and 0 deletions
extensions/fablabchemnitz/maze

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Maze</name>
<id>fablabchemnitz.de.maze</id>
<param name="verti" type="int" min="2" max="1000" gui-text="Height">20</param>
<param name="horiz" type="int" min="2" max="1000" gui-text="Length">20</param>
<param name="size" type="float" max="100.0" gui-text="Cell Size">10.0</param>
<param name="width" type="float" gui-text="Line Width">1.0</param>
<param name="algo" type="optiongroup" appearance="minimal" gui-text="Algorithm">
<option value="kruskal">Kruskal</option>
<option value="recursive_backtrack">Recursive Backtrack</option>
<option value="empty">Empty</option>
<option value="full">Full</option>
</param>
<param name="use2" type="description" xml:space="preserve">This script will generate a maze according to a certain algorithm.</param>
<effect>
<object-type>path</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz">
<submenu name="Shape/Pattern from Generator" />
</submenu>
</effects-menu>
</effect>
<script>
<command reldir="inx" interpreter="python">laby.py</command>
</script>
</inkscape-extension>

View File

@ -0,0 +1,70 @@
#! /usr/bin/env python3
# this extension is under licence CC-by-sa @ Tiemen DUVILLARD 2020
# for all questions, comments, bugs: duvillard.tiemen@gmail.com
import inkex
from lxml import etree
# my maze module
from maze import *
def points_to_svgd(p, close=False):
""" convert list of points (x,y) pairs
into a SVG path list
"""
f = p[0]
p = p[1:]
svgd = 'M%.4f,%.4f' % f
for x in p:
svgd += 'L%.4f,%.4f' % x
if close:
svgd += 'z'
return svgd
class Recursive(inkex.Effect):
def __init__(self):
" define how the options are mapped from the inx file "
inkex.Effect.__init__(self) # initialize the super class
self.arg_parser.add_argument("--verti", type=int, default=20, help="Height")
self.arg_parser.add_argument("--horiz", type=int, default=20, help="Length")
self.arg_parser.add_argument("--size", type=float, default=10.0, help="Cell Size")
self.arg_parser.add_argument("--algo", default=1, help="Algorithm")
self.arg_parser.add_argument("--width", type=float, default=10.0, help="Line width")
def effect(self):
# basic style
style = { 'stroke': "black", "fill":"none", 'stroke-width': self.options.width }
# my group of paths
topgroup = etree.SubElement(self.svg.get_current_layer(), 'g' )
lc = self.options.size
X = self.options.verti
Y = self.options.horiz
L = Maze(X,Y,self.options.algo)
for i,j,door in L.verticalDoors():
if door:
path = points_to_svgd([(lc*(j+1), lc*(i)), (lc*(j+1), lc*(i+1))])
mypath_attribs = { 'style': str(inkex.Style(style)), 'd': path }
squiggle = etree.SubElement(topgroup, inkex.addNS('path','svg'), mypath_attribs )
for i,j,door in L.horizontalDoors():
if door:
path = points_to_svgd([(lc*(j), lc*(i+1)), (lc*(j+1), lc*(i+1))])
mypath_attribs = { 'style': str(inkex.Style(style)), 'd': path }
squiggle = etree.SubElement(topgroup, inkex.addNS('path','svg'), mypath_attribs )
path = points_to_svgd([(0,0),(0,lc*Y),(lc*X,lc*Y),(lc*X,0)], True)
mypath_attribs = { 'style': str(inkex.Style(style)), 'd': path }
squiggle = etree.SubElement(topgroup, inkex.addNS('path','svg'), mypath_attribs )
if __name__ == '__main__':
Recursive().run()

View File

@ -0,0 +1,537 @@
#! /usr/bin/env python
# -*- coding: utf-8 -*-
# this module is under licence CC-by-sa @ Tiemen DUVILLARD 2020
# for all questions, comments, bugs: duvillard.tiemen@gmail.com
from random import choice, randrange
# Representation of maze :
# A labyrinth is a set of 2 door panels.
# For a maze 5*5 :
# Theory : ## Example :
# L = [[[a, b, c, d], # vertical doors ## L = [[[1, 1, 1, 1], # vertical doors
# [e, f, g, h], ## [1, 0, 1, 0],
# [i, j, k, l], ## [0, 1, 0, 1],
# [m, n, o, p], ## [0, 0, 0, 0],
# [q, r, s, t]], ## [0, 1, 0, 0]],
# [[A, B, C, D, E], # horizontal doors ## [[0, 0, 0, 0, 0], # horizontal doors
# [F, G, H, I, J], ## [0, 0, 0, 0, 1],
# [K, L, M, N, O], ## [0, 1, 1, 1, 0],
# [P, Q, R, S, T]]] ## [0, 1, 0, 1, 1]]]
# ##
# ==>> ## ==>>
# X 0 1 2 3 4 ## X 0 1 2 3 4
# Y ┌───┬───┬───┬───┬───┐ ## Y ┌───┬───┬───┬───┬───┐
# 0 │ ∙ a ∙ b ∙ c ∙ d ∙ │ ## 0 │ ∙ │ ∙ │ ∙ │ ∙ │ ∙ │
# ├─A─┼─B─┼─C─┼─D─┼─E─┤ ## │ │ │ │ │ │
# 1 │ ∙ e ∙ f ∙ g ∙ h ∙ │ ## 1 │ ∙ │ ∙ ∙ │ ∙ ∙ │
# ├─F─┼─G─┼─H─┼─I─┼─J─┤ ## │ │ │ │ ┌───┤
# 2 │ ∙ i ∙ j ∙ k ∙ l ∙ │ ## 2 │ ∙ ∙ │ ∙ ∙ │ ∙ │
# ├─K─┼─L─┼─M─┼─N─┼─O─┤ ## │ ╶───┴───────┘ │
# 3 │ ∙ m ∙ n ∙ o ∙ p ∙ │ ## 3 │ ∙ ∙ ∙ ∙ ∙ │
# ├─P─┼─Q─┼─R─┼─S─┼─T─┤ ## │ ╶───┐ ╶───────│
# 4 │ ∙ q ∙ r ∙ s ∙ t ∙ │ ## 4 │ ∙ ∙ │ ∙ ∙ ∙ │
# └───┴───┴───┴───┴───┘ ## └───────┴───────────┘
def kruskal(x, y):
global ID
ID = 0
class case:
"""Little class for union-find"""
def __init__(self,x,y):
self.x = x
self.y = y
self.is_repr = True
self.repr = None
global ID
self.ID = ID
ID += 1
def represent(self):
if self.is_repr:
return self
else:
L = [self.repr]
while not L[0].is_repr:
L[0] = L[0].repr
return L[0]
def __eq__(self,other):
return self.represent().ID == other.represent().ID
def union(self,other):
a = self.represent()
b = other.represent()
if not (a == b):
b.is_repr = False
b.repr = a
# set of doors
doors = []
# verticals doors result
verti = []
# horizontals doors result
horiz = []
# set of cases
cases = []
## initialize vertical doors
for j in range(y):
l = []
for i in range(x-1):
l.append(1)
doors.append([i,j,'v'])
verti.append(l)
## initialize horizontal doors
for j in range(y-1):
l = []
for i in range(x):
l.append(1)
doors.append([i,j,'h'])
horiz.append(l)
## initialize cases
for i in range(y):
l = []
for j in range(x):
l.append(case(x,y))
cases.append(l)
cpt = x*y -1 # nb of openings in perfect maze
while cpt > 0:
# I choose a door
idoor = randrange(len(doors))
dx, dy, dd = doors[idoor]
cx1, cy1 = dx, dy
if dd == 'h' : cx2, cy2 = cx1, cy1+1
else : cx2, cy2 = cx1+1, cy1
C1 = cases[cy1][cx1]
C2 = cases[cy2][cx2]
# if the 2 cases separate by my door are not in same set
if not C1 == C2:
C1.union(C2)
cpt -= 1
# i modify my result
if dd == "v": verti[dy][dx] = 0
else: horiz[dy][dx] = 0
# i delete the door
del doors[idoor]
return verti,horiz
def recursive_backtrack(x, y):
# Initialisation of my variables
labyrinthe = []
for i in range(y): labyrinthe.append([0] * x)
horiz = []
for i in range(y-1): horiz.append([1] * x)
verti = []
for i in range(y): verti.append([1] * (x-1))
# I choose a random start
X_pos = randrange(x)
Y_pos = randrange(y)
labyrinthe[Y_pos][X_pos] = 1
historique = [[X_pos,Y_pos]]
# I explore a tree with deep parcours
while len(historique) != 0:
X = historique[-1][0]
Y = historique[-1][1]
possibilite = []
if (Y-1 >= 0) and (labyrinthe[Y-1][X] == 0): possibilite.append(0)
if (X+1 < x) and (labyrinthe[Y][X+1] == 0): possibilite.append(1)
if (Y+1 < y) and (labyrinthe[Y+1][X] == 0): possibilite.append(2)
if (X-1 >= 0) and (labyrinthe[Y][X-1] == 0): possibilite.append(3)
if len(possibilite) == 0:
del historique[-1]
else:
d = choice(possibilite)
if d == 0:
X1,Y1 = X, Y-1
horiz[Y-1][X] = 0
if d == 1:
X1,Y1 = X+1, Y
verti[Y][X] = 0
if d == 2:
X1,Y1 = X, Y+1
horiz[Y][X] = 0
if d == 3:
X1,Y1 = X-1, Y
verti[Y][X-1] = 0
labyrinthe[Y1][X1] = 1
historique.append([X1,Y1])
return verti, horiz
# a empty maze
def empty(x, y):
verti = []
for i in range(y):
verti.append([0] * (x-1))
horiz = []
for i in range(y-1):
horiz.append([0] * x)
return verti, horiz
# a full maze
def full(x, y):
verti = []
for i in range(y):
verti.append([1] * (x-1))
horiz = []
for i in range(y-1):
horiz.append([1] * x)
return verti, horiz
class Maze:
"""This Class define a Maze object"""
def __init__(self, X=5, Y=5, algorithm="empty"):
"""Use : m = Maze(X:int,Y:int, algorithm:string)"""
self.X = X # width
self.Y = Y # height
self.verti = [] # table of vertical doors
self.horiz = [] # table of horizontal doors
if algorithm in ("kruskal","K") :
self.verti, self.horiz = kruskal(self.X,self.Y)
self.doors = self.nbDoors()
self.algorithm = "kruskal"
self.perfect = True
elif algorithm in ("recursive_backtrack","RB") :
self.verti, self.horiz = recursive_backtrack(self.X,self.Y)
self.doors = self.nbDoors()
self.algorithm = "recursive_backtrack"
self.perfect = True
elif algorithm in ("empty","E") :
self.verti, self.horiz = empty(self.X,self.Y)
self.doors = self.nbDoors()
self.algorithm = "empty"
self.perfect = False
elif algorithm in ("full","F") :
self.verti, self.horiz = full(self.X,self.Y)
self.doors = self.nbDoors()
self.algorithm = "full"
self.perfect = False
else :
raise("Algorithm not recognized. Algorithm must be in ['kruskal','recursive_backtrack','empty','full']")
def __str__(self):
"""Return information about maze"""
s = "Size of maze : {}*{}\nGenerate by : {}\nNb of doors : {}\nPerfect : {}"
s = s.format(self.X,self.Y,self.algorithm,self.doors,self.perfect)
return s
def nbDoors(self):
"""Return number of doors in my maze. If maze is perfect, it equals to (self.X-1)*(self.Y-1)"""
S = 0
for k in self.verti: S += sum(k)
for k in self.horiz: S += sum(k)
return S
def verticalDoors(self):
"""Iterate on vertical doors"""
for i in range(len(self.verti)):
for j in range(len(self.verti[i])):
yield i,j, bool(self.verti[i][j])
def horizontalDoors(self):
"""Iterate on horizontal doors"""
for i in range(len(self.horiz)):
for j in range(len(self.horiz[i])):
yield i,j, bool(self.horiz[i][j])
def canMove(self, x, y, direction) :
"""return if i can move in a direction since (x,y) """
if direction in ["down","d",0] : # down
if y == self.Y-1 :return False
return not self.horiz[y][x]
if direction in ["up","u",2] : # up
if y == 0 : return False
return not self.horiz[y-1][x]
if direction in ["left","l",3] : # left
if x == 0 : return False
return not self.verti[y][x-1]
if direction in ["right","r",1] : # right
if x == self.X-1 : return False
return not self.verti[y][x]
return False
def move(self, x, y, direction) :
"""return new cord after one move in direction"""
if direction in ["down","d",0] : # down
return x,y+1
if direction in ["up","u",2] : # up
return x,y-1
if direction in ["left","l",3] : # left
return x-1,y
if direction in ["right","r",1] : # right
return x+1,y
return x,y
def toSquare(self):
"""return another representation of maze, a List of List of case in {0,1} """
RET = []
for i in range(self.Y*2+1):
l = []
for j in range(self.X*2+1):
if i == 0 or i == self.Y*2 or j == 0 or j == self.X*2 :
l.append(1)
else :
if (i%2==0 and j%2==0) :
l.append(1)
elif (i%2==1 and j%2==1):
l.append(0)
elif (i%2==0 and j%2==1):
l.append(self.horiz[(i-1)//2][(j-1)//2])
elif (i%2==1 and j%2==0):
l.append(self.verti[(i-1)//2][(j-1)//2])
RET.append(l)
return RET
def solve(self, xa, ya, xb, yb):
"""return a list of direction for link (xa,ya) -> (xb,yb). Null"""
Sol = []
Pile = [(xa,ya)]
laby = []
for i in range(self.Y):
laby.append(['new'] * self.X)
while Pile[-1] != (xb,yb) and len(Pile) != 0:
pos = Pile[-1]
x,y = pos[0],pos[1]
if laby[y][x] == 'new':
possibilite = []
for d in ['down',"up","left","right"]:
if self.canMove(pos[0],pos[1],d) :
nx,ny = self.move(pos[0],pos[1],d)
if laby[ny][nx] == 'new' :
possibilite.append(d)
if len(possibilite) == 0:
laby[y][x] = "old"
else :
laby[y][x] = possibilite
elif laby[y][x] == "old":
del Pile[-1]
del Sol[-1]
elif type(laby[y][x]) == list:
if len(laby[y][x]) == 0:
laby[y][x] = "old"
else:
ichoix = randrange(len(laby[y][x]))
choix = laby[y][x][ichoix]
Pile.append(self.move(x,y,choix))
Sol.append(choix)
del laby[y][x][ichoix]
return Sol
def save(self):
"""Return a string contain's all information. it can be save in file, print, etc.."""
s = "{};{};{};{};{};{};{}".format(self.X, self.Y, self.doors, self.algorithm, self.perfect, self.verti, self.horiz)
return s
def load(self, st):
"""Load a Maze since a string of format save"""
cont = st.split(";")
self.X = int(cont[0])
self.Y = int(cont[1])
self.doors = int(cont[2])
self.algorithm = cont[3]
self.perfect = bool(cont[4])
self.verti = cont[5].split("], [")
for i in range(len(self.verti)):
self.verti[i] = self.verti[i].split(',')
for j in range(len(self.verti[i])):
self.verti[i][j] = self.verti[i][j].replace("[","")
self.verti[i][j] = self.verti[i][j].replace("]","")
self.verti[i][j] = self.verti[i][j].replace(" ","")
self.verti[i][j] = int(self.verti[i][j])
self.horiz = cont[6].split("], [")
for i in range(len(self.horiz)):
self.horiz[i] = self.horiz[i].split(',')
for j in range(len(self.horiz[i])):
self.horiz[i][j] = self.horiz[i][j].replace("[","")
self.horiz[i][j] = self.horiz[i][j].replace("]","")
self.horiz[i][j] = self.horiz[i][j].replace(" ","")
self.horiz[i][j] = int(self.horiz[i][j])
def toTxt(self, centre=False, coord=False, basic= False):
"""return a txt representation of maze in ASCII art"""
if type(centre) == bool:
if centre : centre = "*"
else : centre = " "
else:
centre = centre[0]
if basic :
if coord:
R = " X"
for i in range(self.X):
R += " {} ".format(i%10)
R += "\n"
R += "Y +"
r = "0 |"
else:
R = "+"
r = "|"
for j in range(self.X-1):
if self.verti[0][j]:
R += "---+"
r += " {} |".format(centre)
else:
R += "----"
r += " {} ".format(centre)
R += "---+\n"
r += " {} |\n".format(centre)
R += r
for i in range(self.Y-1):
if coord:
if self.horiz[i][0]:
l1 = " +"
else:
l1 = " |"
l2 = "{} |".format((i+1)%10)
else:
if self.horiz[i][0]:
l1 = "+"
else:
l1 = "|"
l2 = "|"
for j in range(self.X-1):
inter = "+"
if self.horiz[i][j] and self.horiz[i][j+1] and not self.verti[i][j] and not self.verti[i+1][j] : inter = "-"
if not self.horiz[i][j] and not self.horiz[i][j+1] and self.verti[i][j] and self.verti[i+1][j] : inter = "|"
if not self.horiz[i][j] and not self.horiz[i][j+1] and not self.verti[i][j] and not self.verti[i+1][j] : inter = " "
if self.horiz[i][j]:
l1 += "---"+inter
else:
l1 += " "+inter
if self.verti[i+1][j]:
l2 += " {} |".format(centre)
else:
l2 += " {} ".format(centre)
if self.horiz[i][-1]:
l1 += "---+"
else:
l1 += " |"
l2 += " {} |".format(centre)
R += l1 + "\n"
R += l2 + "\n"
if coord:
r = " +"
else:
r = "+"
for j in range(self.X-1):
if self.verti[-1][j]:
r += "---+"
else:
r += "----"
r += "---+"
R += r
return R
else :
if coord:
R = " X"
for i in range(self.X):
R += " {} ".format(i%10)
R += "\n"
R += "Y ┌"
r = "0 │"
else:
R = ""
r = ""
for j in range(self.X-1):
if self.verti[0][j]:
R += "───┬"
r += " {}".format(centre)
else:
R += "────"
r += " {} ".format(centre)
R += "───┐\n"
r += " {}\n".format(centre)
R += r
for i in range(self.Y-1):
if coord:
if self.horiz[i][0]:
l1 = ""
else:
l1 = ""
l2 = "{}".format((i+1)%10)
else:
if self.horiz[i][0]:
l1 = ""
else:
l1 = ""
l2 = ""
for j in range(self.X-1):
# 4 door
if self.horiz[i][j] and self.horiz[i][j+1] and self.verti[i][j] and self.verti[i+1][j] : inter = ""
# 3 door
if self.horiz[i][j] and self.horiz[i][j+1] and self.verti[i][j] and not self.verti[i+1][j] : inter = ""
if self.horiz[i][j] and self.horiz[i][j+1] and not self.verti[i][j] and self.verti[i+1][j] : inter = ""
if self.horiz[i][j] and not self.horiz[i][j+1] and self.verti[i][j] and self.verti[i+1][j] : inter = ""
if not self.horiz[i][j] and self.horiz[i][j+1] and self.verti[i][j] and self.verti[i+1][j] : inter = ""
# 2 door
if self.horiz[i][j] and self.horiz[i][j+1] and not self.verti[i][j] and not self.verti[i+1][j] : inter = ""
if self.horiz[i][j] and not self.horiz[i][j+1] and self.verti[i][j] and not self.verti[i+1][j] : inter = ""
if not self.horiz[i][j] and self.horiz[i][j+1] and self.verti[i][j] and not self.verti[i+1][j] : inter = ""
if self.horiz[i][j] and not self.horiz[i][j+1] and not self.verti[i][j] and self.verti[i+1][j] : inter = ""
if not self.horiz[i][j] and self.horiz[i][j+1] and not self.verti[i][j] and self.verti[i+1][j] : inter = ""
if not self.horiz[i][j] and not self.horiz[i][j+1] and self.verti[i][j] and self.verti[i+1][j] : inter = ""
# 1 door
if not self.horiz[i][j] and not self.horiz[i][j+1] and not self.verti[i][j] and self.verti[i+1][j] : inter = ""
if not self.horiz[i][j] and not self.horiz[i][j+1] and self.verti[i][j] and not self.verti[i+1][j] : inter = ""
if not self.horiz[i][j] and self.horiz[i][j+1] and not self.verti[i][j] and not self.verti[i+1][j] : inter = ""
if self.horiz[i][j] and not self.horiz[i][j+1] and not self.verti[i][j] and not self.verti[i+1][j] : inter = ""
# 0 door
if not self.horiz[i][j] and not self.horiz[i][j+1] and not self.verti[i][j] and not self.verti[i+1][j] : inter = " "
if self.horiz[i][j]:
l1 += "───"+inter
else:
l1 += " "+inter
if self.verti[i+1][j]:
l2 += " {}".format(centre)
else:
l2 += " {} ".format(centre)
if self.horiz[i][-1]:
l1 += "───┤"
else:
l1 += ""
l2 += " {}".format(centre)
R += l1 + "\n"
R += l2 + "\n"
if coord:
r = ""
else:
r = ""
for j in range(self.X-1):
if self.verti[-1][j]:
r += "───┴"
else:
r += "────"
r += "───┘"
R += r
return R