Models / Tilt maze
Tilt maze
Tilt maze: roll a ball bearing from the dot to the star. Every seed draws a new maze, and there is always exactly one way through. The maze layer is stacked as many times as the ball needs, under a clear cover.
5 parts on 2 sheets, shown together. Each sheet has its own cut file.
Source
// Tilt maze: roll a ball bearing from the dot to the star. Every seed draws
// a new maze, and there is always exactly one way through. The maze layer
// is stacked as many times as the ball needs, under a clear cover.
/* [Maze] */
// Cells across
cols = 12; // [4:1:20]
// Cells down
rows = 12; // [4:1:20]
// Different seeds draw different mazes
seed = 7; // [1:1:999]
/* [Ball and walls] */
// Ball bearing diameter
ball = 6; // [4:0.5:12]
// Width of the walls between channels
wall = 3; // [2:0.5:6]
// Add a clear cover to keep the ball in
cover = true;
/* [Material] */
// Sheet thickness of the wood layers
$thickness = 3; // [2:0.5:6]
// Thickness of the clear cover
cover_thickness = 2; // [1.5:0.5:3]
// Beam width
$kerf = 0.15; // [0:0.01:0.3]
/* [Hidden] */
$material = "plywood";
$fn = 32;
channel = ball + 1.5;
cell = channel + wall;
border = 9;
w = cols * cell + 2 * border;
h = rows * cell + 2 * border;
// Enough maze layers to sink the ball below the cover
layers = ceil((ball + 0.5) / $thickness);
rnd = rands(0, 0.999, cols * rows * 2, seed);
// The centre of cell [x, y]
function at(c) = [border + (c.x + 0.5) * cell, border + (c.y + 0.5) * cell];
// Depth-first search: walk to a random unvisited neighbour, back up when
// there is none. Returns the passages as pairs of cells.
function carve(stack, seen, passages, k) =
len(stack) == 0 ? passages :
let (
here = stack[len(stack) - 1],
next = [for (d = [[1, 0], [-1, 0], [0, 1], [0, -1]]) let (n = here + d)
if (n.x >= 0 && n.x < cols && n.y >= 0 && n.y < rows && !seen[n.y * cols + n.x]) n]
)
len(next) == 0 ? carve(slice(stack, 0, len(stack) - 1), seen, passages, k) :
let (n = next[floor(rnd[k % len(rnd)] * len(next))], id = n.y * cols + n.x)
carve(concat(stack, [n]), [for (i = [0:len(seen) - 1]) i == id || seen[i]],
concat(passages, [[here, n]]), k + 1);
passages = carve([[0, 0]], [for (i = [0:cols * rows - 1]) i == 0], [], 0);
start = [0, 0];
goal = [cols - 1, rows - 1];
module plate() rect([w, h], r = 5);
module screw_holes() for (x = [border / 2, w - border / 2], y = [border / 2, h - border / 2])
translate([x, y]) circle(d = 3.4);
module channels() for (p = passages) hull() {
translate(at(p[0])) rect(channel, center = true);
translate(at(p[1])) rect(channel, center = true);
}
// Assembled: the base, the maze layers and the cover, stacked; the cover's
// own material block changes $thickness, so keep the plywood's
ply = $thickness;
step("Glue the maze layers onto the base, lining up the screw holes") {
part("Base", at = [0, 0, 0]) {
difference() {
plate();
screw_holes();
}
engrave() {
translate(at(start)) circle(d = channel * 0.55);
translate(at(goal)) star(5, channel * 0.42, channel * 0.18);
}
}
part("Maze layer", qty = layers, at = [for (k = [1:layers]) [0, 0, k * ply]]) difference() {
plate();
screw_holes();
channels();
}
}
if (cover) step("Drop the ball in and screw the clear cover on") material(thickness = cover_thickness, name = "clear acrylic") part("Cover", at = [0, 0, (layers + 1) * ply]) difference() {
plate();
screw_holes();
}