2020-07-31 01:46:27 +02:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
|
|
# Draw a cylindrical maze suitable for plotting with the Eggbot
|
|
|
|
# The maze itself is generated using a depth first search (DFS)
|
|
|
|
|
|
|
|
# Written by Daniel C. Newman for the Eggbot Project
|
|
|
|
# Improvements and suggestions by W. Craig Trader
|
|
|
|
# 20 September 2010
|
|
|
|
|
|
|
|
# Update 26 April 2011 by Daniel C. Newman
|
|
|
|
#
|
|
|
|
# 1. Address Issue #40
|
|
|
|
# The extension now draws the maze by columns, going down
|
|
|
|
# one column of cells and then up the next column. By using
|
|
|
|
# this technique, the impact of slippage is largely limited
|
|
|
|
# the the West and East ends of the maze not meeting. Otherwise,
|
|
|
|
# the maze will still look quite well aligned both locally and
|
|
|
|
# globally. Only very gross slippage will impact the local
|
|
|
|
# appearance of the maze.
|
|
|
|
#
|
|
|
|
# Note that this new drawing technique is nearly as fast as
|
|
|
|
# the prior method. The prior method has been preserved and
|
|
|
|
# can be selected by setting self.hpp = True. ("hpp" intended
|
|
|
|
# to mean "high plotting precision".)
|
|
|
|
#
|
|
|
|
# 2. Changed the page dimensions to use a height of 800 rather
|
|
|
|
# than 1000 pixels.
|
|
|
|
#
|
|
|
|
# 3. When drawing the solution layer, draw the ending cell last.
|
|
|
|
# Previously, the starting and ending cells were first drawn,
|
|
|
|
# and then the solution path itself. That caused the pen to
|
|
|
|
# move to the beginning, the end, and then back to the beginning
|
|
|
|
# again to start the solution path. Alternatively, the solution
|
|
|
|
# path might have been drawn from the end to the start. However,
|
|
|
|
# just drawing the ending cell last was easier code-wise.
|
|
|
|
#
|
|
|
|
# This program is free software; you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with this program; if not, write to the Free Software
|
|
|
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
|
|
|
|
import sys
|
|
|
|
import array
|
|
|
|
import math
|
|
|
|
import random
|
|
|
|
import inkex
|
|
|
|
from lxml import etree
|
|
|
|
|
|
|
|
# Initialize the pseudo random number generator
|
|
|
|
random.seed()
|
|
|
|
|
|
|
|
PLOT_WIDTH = 3200 # Eggbot plot width in pixels
|
|
|
|
PLOT_HEIGHT = 800 # Eggbot plot height in pixels
|
|
|
|
|
|
|
|
TARGET_WIDTH = 3200 # Desired plot width in pixels
|
|
|
|
TARGET_HEIGHT = 600 # Desired plot height in pixels
|
|
|
|
|
|
|
|
|
|
|
|
def draw_SVG_path(pts, c, t, parent):
|
|
|
|
"""
|
|
|
|
Add a SVG path element to the document
|
|
|
|
We could do this just as easily as a polyline
|
|
|
|
"""
|
|
|
|
if not pts: # Nothing to draw
|
|
|
|
return
|
|
|
|
if isinstance(pts, list):
|
|
|
|
assert len(pts) % 3 == 0, "len(pts) must be a multiple of three"
|
|
|
|
d = "{0} {1:d},{2:d}".format(pts[0], pts[1], pts[2])
|
|
|
|
for i in range(3, len(pts), 3):
|
|
|
|
d += " {0} {1:d},{2:d}".format(pts[i], pts[i + 1], pts[i + 2])
|
|
|
|
elif isinstance(pts, str):
|
|
|
|
d = pts
|
|
|
|
else:
|
|
|
|
return
|
|
|
|
style = {'stroke': c, 'stroke-width': str(t), 'fill': 'none'}
|
|
|
|
line_attribs = {'style': str(inkex.Style(style)), 'd': d}
|
|
|
|
etree.SubElement(parent, inkex.addNS('path', 'svg'), line_attribs)
|
|
|
|
|
|
|
|
|
|
|
|
def draw_SVG_rect(x, y, w, h, c, t, fill, parent):
|
|
|
|
"""
|
|
|
|
Add a SVG rect element to the document
|
|
|
|
"""
|
|
|
|
style = {'stroke': c, 'stroke-width': str(t), 'fill': fill}
|
|
|
|
rect_attribs = {'style': str(inkex.Style(style)),
|
|
|
|
'x': str(x), 'y': str(y),
|
|
|
|
'width': str(w), 'height': str(h)}
|
|
|
|
etree.SubElement(parent, inkex.addNS('rect', 'svg'), rect_attribs)
|
|
|
|
|
|
|
|
|
2021-06-02 23:30:37 +02:00
|
|
|
class Eggmazing(inkex.EffectExtension):
|
2020-07-31 01:46:27 +02:00
|
|
|
"""
|
|
|
|
Each cell in the maze is represented using 9 bits:
|
|
|
|
|
|
|
|
Visited -- When set, indicates that this cell has been visited during
|
|
|
|
construction of the maze
|
|
|
|
|
|
|
|
Border -- Four bits indicating which if any of this cell's walls are
|
|
|
|
part of the maze's boundary (i.e., are unremovable walls)
|
|
|
|
|
|
|
|
Walls -- Four bits indicating which if any of this cell's walls are
|
|
|
|
still standing
|
|
|
|
|
|
|
|
Visited Border Walls
|
|
|
|
x x x x x x x x x
|
|
|
|
W S E N W S E N
|
|
|
|
"""
|
|
|
|
|
|
|
|
_VISITED = 0x0100
|
|
|
|
_NORTH = 0x0001
|
|
|
|
_EAST = 0x0002
|
|
|
|
_SOUTH = 0x0004
|
|
|
|
_WEST = 0x0008
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
|
|
|
inkex.Effect.__init__(self)
|
|
|
|
self.arg_parser.add_argument("--tab", default="controls", help="The active tab when Apply was pressed")
|
|
|
|
self.arg_parser.add_argument("--mazeSize", default="MEDIUM", help="Difficulty of maze to build")
|
|
|
|
|
|
|
|
self.hpp = False
|
|
|
|
|
|
|
|
self.w = 0
|
|
|
|
self.h = 0
|
|
|
|
self.solved = 0
|
|
|
|
self.start_x = 0
|
|
|
|
self.start_y = 0
|
|
|
|
self.finish_x = 0
|
|
|
|
self.finish_y = 0
|
|
|
|
self.solution_x = None
|
|
|
|
self.solution_y = None
|
|
|
|
self.cells = None
|
|
|
|
|
|
|
|
# Drawing information
|
|
|
|
self.scale = 25.0
|
|
|
|
self.last_point = None
|
|
|
|
self.path = ''
|
|
|
|
|
|
|
|
def effect(self):
|
|
|
|
|
|
|
|
# These dimensions are chosen so as to maintain integral dimensions
|
|
|
|
# with a ratio of width to height of TARGET_WIDTH to TARGET_HEIGHT.
|
|
|
|
# Presently that's 3200 to 600 which leads to a ratio of 5 and 1/3.
|
|
|
|
|
|
|
|
if self.options.mazeSize == 'SMALL':
|
|
|
|
self.w = 32
|
|
|
|
self.h = 6
|
|
|
|
elif self.options.mazeSize == 'MEDIUM':
|
|
|
|
self.w = 64
|
|
|
|
self.h = 12
|
|
|
|
elif self.options.mazeSize == 'LARGE':
|
|
|
|
self.w = 96
|
|
|
|
self.h = 18
|
|
|
|
else:
|
|
|
|
self.w = 128
|
|
|
|
self.h = 24
|
|
|
|
|
|
|
|
# The large mazes tend to hit the recursion limit
|
|
|
|
limit = sys.getrecursionlimit()
|
|
|
|
if limit < (4 + self.w * self.h):
|
|
|
|
sys.setrecursionlimit(4 + self.w * self.h)
|
|
|
|
|
|
|
|
maze_size = self.w * self.h
|
|
|
|
self.finish_x = self.w - 1
|
|
|
|
self.finish_y = self.h - 1
|
|
|
|
self.solution_x = array.array('i', range(maze_size))
|
|
|
|
self.solution_y = array.array('i', range(maze_size))
|
|
|
|
self.cells = array.array('H', range(maze_size))
|
|
|
|
|
|
|
|
# Remove any old maze
|
|
|
|
for node in self.document.xpath('//svg:g[@inkscape:label="1 - Maze"]', namespaces=inkex.NSS):
|
|
|
|
parent = node.getparent()
|
|
|
|
parent.remove(node)
|
|
|
|
|
|
|
|
# Remove any old solution
|
|
|
|
for node in self.document.xpath('//svg:g[@inkscape:label="2 - Solution"]', namespaces=inkex.NSS):
|
|
|
|
parent = node.getparent()
|
|
|
|
parent.remove(node)
|
|
|
|
|
|
|
|
# Remove any empty, default "Layer 1"
|
|
|
|
for node in self.document.xpath('//svg:g[@id="layer1"]', namespaces=inkex.NSS):
|
|
|
|
if not node.getchildren():
|
|
|
|
parent = node.getparent()
|
|
|
|
parent.remove(node)
|
|
|
|
|
|
|
|
# Start a new maze
|
|
|
|
self.solved = 0
|
|
|
|
self.start_x = random.randint(0, self.w - 1)
|
|
|
|
self.finish_x = random.randint(0, self.w - 1)
|
|
|
|
|
|
|
|
# Initialize every cell with all four walls up
|
|
|
|
|
|
|
|
for i in range(maze_size):
|
2021-06-02 23:30:37 +02:00
|
|
|
self.cells[i] = Eggmazing._NORTH | Eggmazing._EAST | Eggmazing._SOUTH | Eggmazing._WEST
|
2020-07-31 01:46:27 +02:00
|
|
|
|
|
|
|
# Now set our borders -- borders being walls which cannot be removed.
|
|
|
|
# Since we are a maze on the surface of a cylinder we only have two
|
|
|
|
# edges and hence only two borders. We consider our two edges to run
|
|
|
|
# from WEST to EAST and to be at the NORTH and SOUTH.
|
|
|
|
|
|
|
|
z = (self.h - 1) * self.w
|
|
|
|
for x in range(self.w):
|
2021-06-02 23:30:37 +02:00
|
|
|
self.cells[x] |= Eggmazing._NORTH << 4
|
|
|
|
self.cells[x + z] |= Eggmazing._SOUTH << 4
|
2020-07-31 01:46:27 +02:00
|
|
|
|
|
|
|
# Build the maze
|
|
|
|
self.handle_cell(0, self.start_x, self.start_y)
|
|
|
|
|
|
|
|
# Now that the maze has been built, remove the appropriate walls
|
|
|
|
# associated with the start and finish points of the maze
|
|
|
|
|
|
|
|
# Note: we have to remove these after building the maze. If we
|
|
|
|
# remove them first, then the lack of a border at the start (or
|
|
|
|
# finish) cell will allow the handle_cell() routine to wander
|
|
|
|
# outside of the maze. I.e., handle_cell() doesn't do boundary
|
|
|
|
# checking on the cell cell coordinates it generates. Instead, it
|
|
|
|
# relies upon the presence of borders to prevent it wandering
|
|
|
|
# outside the confines of the maze.
|
|
|
|
|
2021-06-02 23:30:37 +02:00
|
|
|
self.remove_border(self.start_x, self.start_y, Eggmazing._NORTH)
|
|
|
|
self.remove_wall(self.start_x, self.start_y, Eggmazing._NORTH)
|
2020-07-31 01:46:27 +02:00
|
|
|
|
2021-06-02 23:30:37 +02:00
|
|
|
self.remove_border(self.finish_x, self.finish_y, Eggmazing._SOUTH)
|
|
|
|
self.remove_wall(self.finish_x, self.finish_y, Eggmazing._SOUTH)
|
2020-07-31 01:46:27 +02:00
|
|
|
|
|
|
|
# Now draw the maze
|
|
|
|
|
|
|
|
# The following scaling and translations scale the maze's
|
|
|
|
# (width, height) to (TARGET_WIDTH, TARGET_HEIGHT), and translates
|
|
|
|
# the maze so that it centered within a document of dimensions
|
|
|
|
# (width, height) = (PLOT_WIDTH, PLOT_HEIGHT)
|
|
|
|
|
|
|
|
# Note that each cell in the maze is drawn 2 x units wide by
|
|
|
|
# 2 y units high. A width and height of 2 was chosen for
|
|
|
|
# convenience and for allowing easy identification (as the integer 1)
|
|
|
|
# of the centerline along which to draw solution paths. It is the
|
|
|
|
# abstract units which are then mapped to the TARGET_WIDTH eggbot x
|
|
|
|
# pixels by TARGET_HEIGHT eggbot y pixels rectangle.
|
|
|
|
|
|
|
|
scale_x = float(TARGET_WIDTH) / float(2 * self.w)
|
|
|
|
scale_y = float(TARGET_HEIGHT) / float(2 * self.h)
|
|
|
|
translate_x = float(PLOT_WIDTH - TARGET_WIDTH) / 2.0
|
|
|
|
translate_y = float(PLOT_HEIGHT - TARGET_HEIGHT) / 2.0
|
|
|
|
|
|
|
|
# And the SVG transform is thus
|
|
|
|
t = 'translate({0:f},{1:f}) scale({2:f},{3:f})'.format(translate_x, translate_y, scale_x, scale_y)
|
|
|
|
|
|
|
|
# For scaling line thicknesses. We'll typically draw a line of
|
|
|
|
# thickness 1 but will need to make the SVG path have a thickness
|
|
|
|
# of 1 / scale so that after our transforms are applied, the
|
|
|
|
# resulting thickness is the 1 we wanted in the first place.
|
|
|
|
|
|
|
|
if scale_x > scale_y:
|
|
|
|
self.scale = scale_x
|
|
|
|
else:
|
|
|
|
self.scale = scale_y
|
|
|
|
|
|
|
|
self.last_point = None
|
|
|
|
self.path = ''
|
|
|
|
|
|
|
|
if not self.hpp:
|
|
|
|
|
|
|
|
# To draw the walls, we start at the left-most column of cells, draw down drawing
|
|
|
|
# the WEST and NORTH walls and then draw up drawing the EAST and SOUTH walls.
|
|
|
|
# By drawing in this back and forth fashion, we minimize the effect of slippage.
|
|
|
|
|
|
|
|
for x in range(0, self.w, 2):
|
|
|
|
self.draw_vertical(x)
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
# The drawing style of the "high plotting precision" / "faster plotting" mode
|
|
|
|
# is such that it minimizes the number of pen up / pen down operations
|
|
|
|
# but at the expense of requiring higher drawing precision. It's style
|
|
|
|
# of drawing works best when there is very minimal slippage of the egg
|
|
|
|
|
|
|
|
# Draw the horizontal walls
|
|
|
|
|
2021-06-02 23:30:37 +02:00
|
|
|
self.draw_horizontal_hpp(0, Eggmazing._NORTH)
|
2020-07-31 01:46:27 +02:00
|
|
|
for y in range(self.h - 1):
|
2021-06-02 23:30:37 +02:00
|
|
|
self.draw_horizontal_hpp(y, Eggmazing._SOUTH)
|
|
|
|
self.draw_horizontal_hpp(self.h - 1, Eggmazing._SOUTH)
|
2020-07-31 01:46:27 +02:00
|
|
|
|
|
|
|
# Draw the vertical walls
|
|
|
|
|
|
|
|
# Since this is a maze on the surface of a cylinder, we don't need
|
|
|
|
# to draw the vertical walls at the outer edges (x = 0 & x = w - 1)
|
|
|
|
|
|
|
|
for x in range(self.w):
|
2021-06-02 23:30:37 +02:00
|
|
|
self.draw_vertical_hpp(x, Eggmazing._EAST)
|
2020-07-31 01:46:27 +02:00
|
|
|
|
|
|
|
# Maze in layer "1 - Maze"
|
|
|
|
attribs = {
|
|
|
|
inkex.addNS('label', 'inkscape'): '1 - Maze',
|
|
|
|
inkex.addNS('groupmode', 'inkscape'): 'layer',
|
|
|
|
'transform': t}
|
|
|
|
maze_layer = etree.SubElement(self.document.getroot(), 'g', attribs)
|
|
|
|
draw_SVG_path(self.path, "#000000", float(1 / self.scale), maze_layer)
|
|
|
|
|
|
|
|
# Now draw the solution in red in layer "2 - Solution"
|
|
|
|
|
|
|
|
attribs = {
|
|
|
|
inkex.addNS('label', 'inkscape'): '2 - Solution',
|
|
|
|
inkex.addNS('groupmode', 'inkscape'): 'layer',
|
|
|
|
'transform': t}
|
|
|
|
maze_layer = etree.SubElement(self.document.getroot(), 'g', attribs)
|
|
|
|
|
|
|
|
# Mark the starting cell
|
|
|
|
|
|
|
|
draw_SVG_rect(0.25 + 2 * self.start_x, 0.25 + 2 * self.start_y,
|
|
|
|
1.5, 1.5, "#ff0000", 0, "#ff0000", maze_layer)
|
|
|
|
|
|
|
|
# And now generate the solution path itself
|
|
|
|
|
|
|
|
# To minimize the number of plotted paths (and hence pen up / pen
|
|
|
|
# down operations), we generate as few SVG paths as possible.
|
|
|
|
# However, for aesthetic reasons we stop the path and start a new
|
|
|
|
# one when it runs off the edge of the document. We could keep on
|
|
|
|
# drawing as the eggbot will handle that just fine. However, it
|
|
|
|
# doesn't look as good in Inkscape. So, we end the path and start
|
|
|
|
# a new one which is wrapped to the other edge of the document.
|
|
|
|
|
|
|
|
pts = []
|
|
|
|
end_path = False
|
|
|
|
i = 0
|
|
|
|
while i < self.solved:
|
|
|
|
|
|
|
|
x1 = self.solution_x[i]
|
|
|
|
y1 = self.solution_y[i]
|
|
|
|
|
|
|
|
i += 1
|
|
|
|
x2 = self.solution_x[i]
|
|
|
|
y2 = self.solution_y[i]
|
|
|
|
|
|
|
|
if math.fabs(x1 - x2) > 1:
|
|
|
|
|
|
|
|
# We wrapped horizontally...
|
|
|
|
if x1 > x2:
|
|
|
|
x2 = x1 + 1
|
|
|
|
else:
|
|
|
|
x2 = x1 - 1
|
|
|
|
end_path = True
|
|
|
|
|
|
|
|
if i == 1:
|
|
|
|
pts.extend(['M', 2 * x1 + 1, 2 * y1 + 1])
|
|
|
|
pts.extend(['L', 2 * x2 + 1, 2 * y2 + 1])
|
|
|
|
|
|
|
|
if not end_path:
|
|
|
|
continue
|
|
|
|
|
|
|
|
x2 = self.solution_x[i]
|
|
|
|
y2 = self.solution_y[i]
|
|
|
|
pts.extend(['M', 2 * x2 + 1, 2 * y2 + 1])
|
|
|
|
end_path = False
|
|
|
|
|
|
|
|
# Put the solution path into the drawing
|
|
|
|
draw_SVG_path(pts, '#ff0000', float(8 / self.scale), maze_layer)
|
|
|
|
|
|
|
|
# Now mark the ending cell
|
|
|
|
draw_SVG_rect(0.25 + 2 * self.finish_x, 0.25 + 2 * self.finish_y,
|
|
|
|
1.5, 1.5, "#ff0000", 0, "#ff0000", maze_layer)
|
|
|
|
|
|
|
|
# Restore the recursion limit
|
|
|
|
sys.setrecursionlimit(limit)
|
|
|
|
|
|
|
|
# Set some document properties
|
|
|
|
node = self.document.getroot()
|
|
|
|
node.set('width', '3200')
|
|
|
|
node.set('height', '800')
|
|
|
|
|
|
|
|
# The following end up being ignored by Inkscape....
|
|
|
|
node = self.svg.namedview
|
|
|
|
node.set('showborder', 'false')
|
|
|
|
node.set(inkex.addNS('cx', u'inkscape'), '1600')
|
|
|
|
node.set(inkex.addNS('cy', u'inkscape'), '500')
|
|
|
|
node.set(inkex.addNS('showpageshadow', u'inkscape'), 'false')
|
|
|
|
|
|
|
|
# Mark the cell at (x, y) as "visited"
|
|
|
|
def visit(self, x, y):
|
2021-06-02 23:30:37 +02:00
|
|
|
self.cells[y * self.w + x] |= Eggmazing._VISITED
|
2020-07-31 01:46:27 +02:00
|
|
|
|
|
|
|
# Return a non-zero value if the cell at (x, y) has been visited
|
|
|
|
def is_visited(self, x, y):
|
2021-06-02 23:30:37 +02:00
|
|
|
if self.cells[y * self.w + x] & Eggmazing._VISITED:
|
2020-07-31 01:46:27 +02:00
|
|
|
return -1
|
|
|
|
else:
|
|
|
|
return 0
|
|
|
|
|
|
|
|
# Return a non-zero value if the cell at (x, y) has a wall
|
|
|
|
# in the direction d
|
|
|
|
def is_wall(self, x, y, d):
|
|
|
|
if self.cells[y * self.w + x] & d:
|
|
|
|
return -1
|
|
|
|
else:
|
|
|
|
return 0
|
|
|
|
|
|
|
|
# Remove the wall in the direction d from the cell at (x, y)
|
|
|
|
def remove_wall(self, x, y, d):
|
|
|
|
self.cells[y * self.w + x] &= ~d
|
|
|
|
|
|
|
|
# Return a non-zero value if the cell at (x, y) has a border wall
|
|
|
|
# in the direction d
|
|
|
|
def is_border(self, x, y, d):
|
|
|
|
if self.cells[y * self.w + x] & (d << 4):
|
|
|
|
return -1
|
|
|
|
else:
|
|
|
|
return 0
|
|
|
|
|
|
|
|
# Remove the border in the direction d from the cell at (x, y)
|
|
|
|
def remove_border(self, x, y, d):
|
|
|
|
self.cells[y * self.w + x] &= ~(d << 4)
|
|
|
|
|
|
|
|
# This is the DFS algorithm which builds the maze. We start at depth 0
|
|
|
|
# at the starting cell (self.start_x, self.start_y). We then walk to a
|
|
|
|
# randomly selected neighboring cell which has not yet been visited (i.e.,
|
|
|
|
# previously walked into). Each step of the walk is a recursive descent
|
|
|
|
# in depth. The solution to the maze comes about when we walk into the
|
|
|
|
# finish cell at (self.finish_x, self.finish_y).
|
|
|
|
#
|
|
|
|
# Each recursive descent finishes when the currently visited cell has no
|
|
|
|
# unvisited neighboring cells.
|
|
|
|
#
|
|
|
|
# Since we don't revisit previously visited cells, each cell is visited
|
|
|
|
# no more than once. As it turns out, each cell is visited, but that's a
|
|
|
|
# little harder to show. Net, net, each cell is visited exactly once.
|
|
|
|
|
|
|
|
def handle_cell(self, depth, x, y):
|
|
|
|
|
|
|
|
# Mark the current cell as visited
|
|
|
|
self.visit(x, y)
|
|
|
|
|
|
|
|
# Save this cell's location in our solution trail / backtrace
|
|
|
|
if not self.solved:
|
|
|
|
|
|
|
|
self.solution_x[depth] = x
|
|
|
|
self.solution_y[depth] = y
|
|
|
|
|
|
|
|
if (x == self.finish_x) and (y == self.finish_y):
|
|
|
|
# Maze has been solved
|
|
|
|
self.solved = depth
|
|
|
|
|
|
|
|
# Shuffle the four compass directions: this is the primary source
|
|
|
|
# of "randomness" in the generated maze. We need to visit each
|
|
|
|
# neighboring cell which has not yet been visited. If we always
|
|
|
|
# did that in the same order, then our mazes would look very regular.
|
|
|
|
# So, we shuffle the list of directions we try in order to find an
|
|
|
|
# unvisited neighbor.
|
|
|
|
|
|
|
|
# HINT: TRY COMMENTING OUT THE shuffle() BELOW AND SEE FOR YOURSELF
|
|
|
|
|
2021-06-02 23:30:37 +02:00
|
|
|
directions = [Eggmazing._NORTH, Eggmazing._SOUTH, Eggmazing._EAST, Eggmazing._WEST]
|
2020-07-31 01:46:27 +02:00
|
|
|
random.shuffle(directions)
|
|
|
|
|
|
|
|
# Now from the cell at (x, y), look to each of the four
|
|
|
|
# directions for unvisited neighboring cells
|
|
|
|
|
|
|
|
for each_direction in directions:
|
|
|
|
|
|
|
|
# If there is a border in direction[i], then don't try
|
|
|
|
# looking for a neighboring cell in that direction. We
|
|
|
|
# Use this check and borders to prevent generating invalid
|
|
|
|
# cell coordinates.
|
|
|
|
|
|
|
|
if self.is_border(x, y, each_direction):
|
|
|
|
continue
|
|
|
|
|
|
|
|
# Determine the cell coordinates of a neighboring cell
|
|
|
|
# NOTE: we trust the use of maze borders to prevent us
|
|
|
|
# from generating invalid cell coordinates
|
|
|
|
|
2021-06-02 23:30:37 +02:00
|
|
|
if each_direction == Eggmazing._NORTH:
|
2020-07-31 01:46:27 +02:00
|
|
|
nx = x
|
|
|
|
ny = y - 1
|
2021-06-02 23:30:37 +02:00
|
|
|
opposite_direction = Eggmazing._SOUTH
|
2020-07-31 01:46:27 +02:00
|
|
|
|
2021-06-02 23:30:37 +02:00
|
|
|
elif each_direction == Eggmazing._SOUTH:
|
2020-07-31 01:46:27 +02:00
|
|
|
nx = x
|
|
|
|
ny = y + 1
|
2021-06-02 23:30:37 +02:00
|
|
|
opposite_direction = Eggmazing._NORTH
|
2020-07-31 01:46:27 +02:00
|
|
|
|
2021-06-02 23:30:37 +02:00
|
|
|
elif each_direction == Eggmazing._EAST:
|
2020-07-31 01:46:27 +02:00
|
|
|
nx = x + 1
|
|
|
|
ny = y
|
2021-06-02 23:30:37 +02:00
|
|
|
opposite_direction = Eggmazing._WEST
|
2020-07-31 01:46:27 +02:00
|
|
|
|
|
|
|
else:
|
|
|
|
nx = x - 1
|
|
|
|
ny = y
|
2021-06-02 23:30:37 +02:00
|
|
|
opposite_direction = Eggmazing._EAST
|
2020-07-31 01:46:27 +02:00
|
|
|
|
|
|
|
# Wrap in the horizontal dimension
|
|
|
|
if nx < 0:
|
|
|
|
nx += self.w
|
|
|
|
elif nx >= self.w:
|
|
|
|
nx -= self.w
|
|
|
|
|
|
|
|
# See if this neighboring cell has been visited
|
|
|
|
if self.is_visited(nx, ny):
|
|
|
|
# Neighbor has been visited already
|
|
|
|
continue
|
|
|
|
|
|
|
|
# The neighboring cell has not been visited: remove the wall in
|
|
|
|
# the current cell leading to the neighbor. And, from the
|
|
|
|
# neighbor remove its wall leading to the current cell.
|
|
|
|
|
|
|
|
self.remove_wall(x, y, each_direction)
|
|
|
|
self.remove_wall(nx, ny, opposite_direction)
|
|
|
|
|
|
|
|
# Now recur by "moving" to this unvisited neighboring cell
|
|
|
|
|
|
|
|
self.handle_cell(depth + 1, nx, ny)
|
|
|
|
|
|
|
|
def draw_line(self, x1, y1, x2, y2):
|
|
|
|
|
|
|
|
if self.last_point is not None:
|
|
|
|
if (self.last_point[0] == x1) and (self.last_point[1] == y1):
|
|
|
|
self.path += ' L {0:d},{1:d}'.format(x2, y2)
|
|
|
|
self.last_point = [x2, y2]
|
|
|
|
elif (self.last_point[0] == x2) and (self.last_point[1] == y2):
|
|
|
|
self.path += ' L {0:d},{1:d} L {2:d},{3:d}'.format(x1, y1, x2, y2)
|
|
|
|
# self.last_point unchanged
|
|
|
|
else:
|
|
|
|
self.path += ' M {0:d},{1:d} L {2:d},{3:d}'.format(x1, y1, x2, y2)
|
|
|
|
self.last_point = [x2, y2]
|
|
|
|
else:
|
|
|
|
self.path = 'M {0:d},{1:d} L {2:d},{3:d}'.format(x1, y1, x2, y2)
|
|
|
|
self.last_point = [x2, y2]
|
|
|
|
|
|
|
|
def draw_wall(self, x, y, d, dir_):
|
|
|
|
|
|
|
|
if dir_ > 0:
|
2021-06-02 23:30:37 +02:00
|
|
|
if d == Eggmazing._NORTH:
|
2020-07-31 01:46:27 +02:00
|
|
|
self.draw_line(2 * (x + 1), 2 * y, 2 * x, 2 * y)
|
2021-06-02 23:30:37 +02:00
|
|
|
elif d == Eggmazing._WEST:
|
2020-07-31 01:46:27 +02:00
|
|
|
self.draw_line(2 * x, 2 * y, 2 * x, 2 * (y + 1))
|
2021-06-02 23:30:37 +02:00
|
|
|
elif d == Eggmazing._SOUTH:
|
2020-07-31 01:46:27 +02:00
|
|
|
self.draw_line(2 * (x + 1), 2 * (y + 1), 2 * x, 2 * (y + 1))
|
2021-06-02 23:30:37 +02:00
|
|
|
else: # Eggmazing._EAST
|
2020-07-31 01:46:27 +02:00
|
|
|
self.draw_line(2 * (x + 1), 2 * y, 2 * (x + 1), 2 * (y + 1))
|
|
|
|
else:
|
2021-06-02 23:30:37 +02:00
|
|
|
if d == Eggmazing._NORTH:
|
2020-07-31 01:46:27 +02:00
|
|
|
self.draw_line(2 * x, 2 * y, 2 * (x + 1), 2 * y)
|
2021-06-02 23:30:37 +02:00
|
|
|
elif d == Eggmazing._WEST:
|
2020-07-31 01:46:27 +02:00
|
|
|
self.draw_line(2 * x, 2 * (y + 1), 2 * x, 2 * y)
|
2021-06-02 23:30:37 +02:00
|
|
|
elif d == Eggmazing._SOUTH:
|
2020-07-31 01:46:27 +02:00
|
|
|
self.draw_line(2 * x, 2 * (y + 1), 2 * (x + 1), 2 * (y + 1))
|
2021-06-02 23:30:37 +02:00
|
|
|
else: # Eggmazing._EAST
|
2020-07-31 01:46:27 +02:00
|
|
|
self.draw_line(2 * (x + 1), 2 * (y + 1), 2 * (x + 1), 2 * y)
|
|
|
|
|
|
|
|
# Draw the vertical walls of the maze along the column of cells at
|
|
|
|
# horizontal positions
|
|
|
|
|
|
|
|
def draw_vertical(self, x):
|
|
|
|
|
|
|
|
# Drawing moving downwards from north to south
|
|
|
|
|
2021-06-02 23:30:37 +02:00
|
|
|
if self.is_wall(x, 0, Eggmazing._NORTH):
|
|
|
|
self.draw_wall(x, 0, Eggmazing._NORTH, +1)
|
2020-07-31 01:46:27 +02:00
|
|
|
|
|
|
|
for y in range(self.h):
|
2021-06-02 23:30:37 +02:00
|
|
|
if self.is_wall(x, y, Eggmazing._WEST):
|
|
|
|
self.draw_wall(x, y, Eggmazing._WEST, +1)
|
|
|
|
if self.is_wall(x, y, Eggmazing._SOUTH):
|
|
|
|
self.draw_wall(x, y, Eggmazing._SOUTH, +1)
|
2020-07-31 01:46:27 +02:00
|
|
|
|
|
|
|
# Now, return drawing upwards moving from south to north
|
|
|
|
|
|
|
|
x += 1
|
|
|
|
if x >= self.w:
|
|
|
|
return
|
|
|
|
|
|
|
|
for y in range(self.h - 1, -1, -1):
|
2021-06-02 23:30:37 +02:00
|
|
|
if self.is_wall(x, y, Eggmazing._SOUTH):
|
|
|
|
self.draw_wall(x, y, Eggmazing._SOUTH, -1)
|
|
|
|
if self.is_wall(x, y, Eggmazing._WEST):
|
|
|
|
self.draw_wall(x, y, Eggmazing._WEST, -1)
|
|
|
|
if self.is_wall(x, 0, Eggmazing._NORTH):
|
|
|
|
self.draw_wall(x, 0, Eggmazing._NORTH, -1)
|
2020-07-31 01:46:27 +02:00
|
|
|
|
|
|
|
# Draw the horizontal walls of the maze along the row of
|
|
|
|
# cells at "height" y: "high plotting precision" version
|
|
|
|
|
|
|
|
def draw_horizontal_hpp(self, y, wall):
|
|
|
|
|
|
|
|
# Cater to Python 2.4 and earlier
|
2021-06-02 23:30:37 +02:00
|
|
|
# dy = 0 if wall == Eggmazing._NORTH else 1
|
|
|
|
if wall == Eggmazing._NORTH:
|
2020-07-31 01:46:27 +02:00
|
|
|
dy = 0
|
|
|
|
else:
|
|
|
|
dy = 1
|
|
|
|
|
|
|
|
tracing = False
|
|
|
|
segment = 0
|
|
|
|
for x in range(self.w):
|
|
|
|
|
|
|
|
if self.is_wall(x, y, wall):
|
|
|
|
if not tracing:
|
|
|
|
# Starting a new segment
|
|
|
|
segment = x
|
|
|
|
tracing = True
|
|
|
|
else:
|
|
|
|
if tracing:
|
|
|
|
# Reached the end of a segment
|
|
|
|
self.draw_line(2 * segment, 2 * (y + dy),
|
|
|
|
2 * x, 2 * (y + dy))
|
|
|
|
tracing = False
|
|
|
|
|
|
|
|
if tracing:
|
|
|
|
# Draw the last wall segment
|
|
|
|
self.draw_line(2 * segment, 2 * (y + dy),
|
|
|
|
2 * self.w, 2 * (y + dy))
|
|
|
|
|
|
|
|
# Draw the vertical walls of the maze along the column of cells at
|
|
|
|
# horizontal position x: "high plotting precision" version
|
|
|
|
|
|
|
|
def draw_vertical_hpp(self, x, wall):
|
|
|
|
|
2021-06-02 23:30:37 +02:00
|
|
|
dx = 0 if wall == Eggmazing._WEST else 1
|
2020-07-31 01:46:27 +02:00
|
|
|
|
|
|
|
# We alternate the direction in which we draw each vertical wall.
|
|
|
|
# First, from North to South and then from South to North. This
|
|
|
|
# reduces pen travel on the Eggbot
|
|
|
|
|
|
|
|
if x % 2 == 0: # North-South
|
|
|
|
y_start, y_finis, dy, offset = 0, self.h, 1, 0
|
|
|
|
else: # South-North
|
|
|
|
y_start, y_finis, dy, offset = self.h - 1, -1, -1, 2
|
|
|
|
|
|
|
|
tracing = False
|
|
|
|
segment = y_start
|
|
|
|
for y in range(y_start, y_finis, dy):
|
|
|
|
assert 0 <= y < self.h, "y ({0:d}) is out of range".format(y)
|
|
|
|
if self.is_wall(x, y, wall):
|
|
|
|
if not tracing:
|
|
|
|
# Starting a new segment
|
|
|
|
segment = y
|
|
|
|
tracing = True
|
|
|
|
else:
|
|
|
|
if tracing:
|
|
|
|
# Hit the end of a segment
|
|
|
|
self.draw_line(2 * (x + dx), 2 * segment + offset,
|
|
|
|
2 * (x + dx), 2 * y + offset)
|
|
|
|
tracing = False
|
|
|
|
|
|
|
|
if tracing:
|
|
|
|
# complete the last wall segment
|
|
|
|
self.draw_line(2 * (x + dx), 2 * segment + offset,
|
|
|
|
2 * (x + dx), 2 * y_finis + offset)
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
2021-06-02 23:30:37 +02:00
|
|
|
Eggmazing().run()
|