Maze generation: Difference between revisions
Line 2,751: | Line 2,751: | ||
=={{header|EasyLang}}== |
=={{header|EasyLang}}== |
||
[https://easylang.online/show/#cod=fZPLbqswEIb3fopP6iYXQe20WWThJ0HoiGJzghrsCHJ6e/rKxBhQq7MB/P8ee+abYWi/LBp1FA7NgR1DUPYo0QRdSh7ZODJkftyKi3V0RYljhxMPiJeqfv3b+3/OIKUU197XDGf//qerviw5uQDqi6368NH4njYcy80Tzwo60DZ0RVuikVEBPtBsWjLUls4b3Gx9LizTvi2t2l98z+l0mqXOv1k+2NGQ0fDIgc/lat7Z2/pGww6VH6d3dMdaxsdwsfaKzKUSufBNMxQh8QKFI1NsMrelDHhGHt0dxtUPE5CuuPoh1Zp4hYVJRx144plyye0Z49/dzaNiSgZNXznjO9pJans0Y1KmMOXEN3yjMUW7Ih6S2oeYcmxL5UwSwzBMxoLkOmTRrEWZKXhNLhfWmeDr+wCF7kVE1atdjcx/JiWOiUrdWO11612HtAw3/iaR4cK8/7RctOao8bpz/FEid3WM20LIShczlPO96/fyE7dIY69xIheJgZgHQnwD Run it] |
|||
[https://easylang.dev/show/#cod=fZPLbqswEIb3fopP6iYXQe20WWTBkyB0RLE5sRrsCHJ6e/rKxBhQq7MB/P8z9vibYbBfhgJ1FI6CAzuGoOxRog26lDyycWTI/LgVF+PoygrHDiceEC918/q39/+cRkoprr1vGM7+/U9XfxlycgE0F1P34aP1PTZsy80T9wo6YFu60lYUyKgAHxRsLBlqS+c1brY+F5a2b0ur8RffczqdZqnzb4YPdrRktDxy4HO5miN709xo2aHy4/SO7niX8TFcjLkic6lELnzbDmUovEThyBSbzG2pAp6RR3eHcfXDBKQrr35Id028wkKnrQ488Uy15PaM9u/u5lGxJE1BXztt3Q07abanYKxKl7qaAIdvCnRpV8hDVfuQU419qZ1OYpiGyVigXKcsurW4Z0peo8uFcTr4xX2CQvsio/rVrGbmP6MS50Sldqxi3TrqkJbhxN8kMlwY+J+Wi9acNR53jn/KBF4dY1zIWRtixnK+N/4OIJGLPPYFTuQiURDzTHwD Run it] |
|||
<syntaxhighlight> |
<syntaxhighlight> |
||
Line 2,780: | Line 2,780: | ||
d[] = [ 1 2 3 4 ] |
d[] = [ 1 2 3 4 ] |
||
for i = 4 downto 1 |
for i = 4 downto 1 |
||
d = |
d = random i |
||
dir = offs[d[d]] |
dir = offs[d[d]] |
||
d[d] = d[i] |
d[d] = d[i] |
||
Line 2,800: | Line 2,800: | ||
m[n * n - n + i] = 2 |
m[n * n - n + i] = 2 |
||
. |
. |
||
h = 2 * |
h = 2 * random 15 - n + n * 2 * random 15 |
||
m_maze h |
m_maze h |
||
m[endpos] = 0 |
m[endpos] = 0 |
Revision as of 18:11, 17 July 2024
![Task](http://static.miraheze.org/rosettacodewiki/thumb/b/ba/Rcode-button-task-crushed.png/64px-Rcode-button-task-crushed.png)
You are encouraged to solve this task according to the task description, using any language you may know.
This page uses content from Wikipedia. The original article was at Maze generation algorithm. The list of authors can be seen in the page history. As with Rosetta Code, the text of Wikipedia is available under the GNU FDL. (See links for details on variance) |
![a maze](http://static.miraheze.org/rosettacodewiki/a/ab/A_maze.png)
- Task
Generate and show a maze, using the simple Depth-first search algorithm.
- Start at a random cell.
- Mark the current cell as visited, and get a list of its neighbors. For each neighbor, starting with a randomly selected neighbor:
- If that neighbor hasn't been visited, remove the wall between this cell and that neighbor, and then recurse with that neighbor as the current cell.
- Related tasks
11l
F make_maze(w = 16, h = 8)
V vis = [[0] * w [+] [1]] * h [+] [[1] * (w + 1)]
V ver = [[‘| ’] * w [+] [String(‘|’)]] * h [+] [[String]()]
V hor = [[‘+--’] * w [+] [String(‘+’)]] * (h + 1)
F walk(Int x, Int y) -> Void
@vis[y][x] = 1
V d = [(x - 1, y), (x, y + 1), (x + 1, y), (x, y - 1)]
random:shuffle(&d)
L(=xx, =yy) d
I yy == -1
yy = @vis.len - 1
I xx == -1
xx = @vis[0].len - 1
I @vis[yy][xx]
L.continue
I xx == x
@hor[max(y, yy)][x] = ‘+ ’
I yy == y
@ver[y][max(x, xx)] = ‘ ’
@walk(xx, yy)
walk(random:(w), random:(h))
V s = ‘’
L(a, b) zip(hor, ver)
s ‘’= (a [+] [String("\n")] + b [+] [String("\n")]).join(‘’)
R s
print(make_maze())
- Output:
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | | | | + +--+--+--+--+--+--+--+--+--+ + +--+ + +--+ | | | | | | | +--+--+--+--+--+--+ + +--+ +--+ + + + + + | | | | | | | | | | + + +--+--+ + +--+ + +--+ +--+ + +--+ + | | | | | | | | | + +--+--+--+--+--+ + + + +--+ + +--+--+--+ | | | | | | | | | | | + +--+--+ + + + +--+ + + +--+--+ +--+ + | | | | | | | | | | +--+ + +--+ +--+ + +--+ + + +--+--+ + + | | | | | | | | | | | + + +--+ +--+ +--+--+ +--+--+--+--+--+ + + | | | | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
Action!
Action! language does not support recursion. Therefore an iterative approach with a stack has been proposed.
DEFINE TOP="0"
DEFINE RIGHT="1"
DEFINE BOTTOM="2"
DEFINE LEFT="3"
DEFINE WIDTH="160"
DEFINE HEIGHT="96"
DEFINE STACK_SIZE="5000"
BYTE ARRAY stack(STACK_SIZE)
INT stackSize
PROC InitStack()
stackSize=0
RETURN
BYTE FUNC IsEmpty()
IF stackSize=0 THEN
RETURN (1)
FI
RETURN (0)
BYTE FUNC IsFull()
IF stackSize>=STACK_SIZE THEN
RETURN (1)
FI
RETURN (0)
PROC Push(BYTE x,y)
IF IsFull() THEN Break() RETURN FI
stack(stackSize)=x stackSize==+1
stack(stackSize)=y stackSize==+1
RETURN
PROC Pop(BYTE POINTER x,y)
IF IsEmpty() THEN Break() RETURN FI
stackSize==-1 y^=stack(stackSize)
stackSize==-1 x^=stack(stackSize)
RETURN
PROC FillScreen()
BYTE POINTER ptr ;pointer to the screen memory
INT screenSize=[3840]
ptr=PeekC(88)
SetBlock(ptr,screenSize,$55)
Color=0
Plot(0,HEIGHT-1) DrawTo(WIDTH-1,HEIGHT-1) DrawTo(WIDTH-1,0)
RETURN
PROC GetNeighbors(BYTE x,y BYTE ARRAY n BYTE POINTER count)
DEFINE WALL="1"
count^=0
IF y>2 AND Locate(x,y-2)=WALL THEN
n(count^)=TOP count^==+1
FI
IF x<WIDTH-3 AND Locate(x+2,y)=WALL THEN
n(count^)=RIGHT count^==+1
FI
IF y<HEIGHT-3 AND Locate(x,y+2)=WALL THEN
n(count^)=BOTTOM count^==+1
FI
IF x>2 AND Locate(x-2,y)=WALL THEN
n(count^)=LEFT count^==+1
FI
RETURN
PROC Maze(BYTE x,y)
BYTE ARRAY stack,neighbors
BYTE dir,nCount
FillScreen()
Color=2
InitStack()
Push(x,y)
WHILE IsEmpty()=0
DO
Pop(@x,@y)
GetNeighbors(x,y,neighbors,@nCount)
IF nCount>0 THEN
Push(x,y)
Plot(x,y)
dir=neighbors(Rand(nCount))
IF dir=TOP THEN
y==-2
ELSEIF dir=RIGHT THEN
x==+2
ELSEIF dir=BOTTOM THEN
y==+2
ELSE
x==-2
FI
DrawTo(x,y)
Push(x,y)
FI
OD
RETURN
PROC Main()
BYTE CH=$02FC,COLOR0=$02C4,COLOR1=$02C5
BYTE x,y
Graphics(7+16)
COLOR0=$0A
COLOR1=$04
x=Rand((WIDTH RSH 1)-1) LSH 1+1
y=Rand((HEIGHT RSH 1)-1) LSH 1+1
Maze(x,y)
DO UNTIL CH#$FF OD
CH=$FF
RETURN
- Output:
Screenshot from Atari 8-bit computer
Ada
mazes.ads:
generic
Height : Positive;
Width : Positive;
package Mazes is
type Maze_Grid is private;
procedure Initialize (Maze : in out Maze_Grid);
procedure Put (Item : Maze_Grid);
private
type Directions is (North, South, West, East);
type Cell_Walls is array (Directions) of Boolean;
type Cells is record
Walls : Cell_Walls := (others => True);
Visited : Boolean := False;
end record;
subtype Height_Type is Positive range 1 .. Height;
subtype Width_Type is Positive range 1 .. Width;
type Maze_Grid is array (Height_Type, Width_Type) of Cells;
end Mazes;
mazes.adb:
with Ada.Numerics.Discrete_Random;
with Ada.Text_IO;
package body Mazes is
package RNG is new Ada.Numerics.Discrete_Random (Positive);
package Random_Direction is new Ada.Numerics.Discrete_Random (Directions);
Generator : RNG.Generator;
Dir_Generator : Random_Direction.Generator;
function "-" (Dir : Directions) return Directions is
begin
case Dir is
when North =>
return South;
when South =>
return North;
when East =>
return West;
when West =>
return East;
end case;
end "-";
procedure Move
(Row : in out Height_Type;
Column : in out Width_Type;
Direction : Directions;
Valid_Move : out Boolean)
is
begin
Valid_Move := False;
case Direction is
when North =>
if Row > Height_Type'First then
Valid_Move := True;
Row := Row - 1;
end if;
when East =>
if Column < Width_Type'Last then
Valid_Move := True;
Column := Column + 1;
end if;
when West =>
if Column > Width_Type'First then
Valid_Move := True;
Column := Column - 1;
end if;
when South =>
if Row < Height_Type'Last then
Valid_Move := True;
Row := Row + 1;
end if;
end case;
end Move;
procedure Depth_First_Algorithm
(Maze : in out Maze_Grid;
Row : Height_Type;
Column : Width_Type)
is
Next_Row : Height_Type;
Next_Column : Width_Type;
Next_Direction : Directions;
Valid_Direction : Boolean;
Tested_Wall : array (Directions) of Boolean := (others => False);
All_Tested : Boolean;
begin
-- mark as visited
Maze (Row, Column).Visited := True;
loop
-- use random direction
loop
Next_Direction := Random_Direction.Random (Dir_Generator);
exit when not Tested_Wall (Next_Direction);
end loop;
Next_Row := Row;
Next_Column := Column;
Move (Next_Row, Next_Column, Next_Direction, Valid_Direction);
if Valid_Direction then
if not Maze (Next_Row, Next_Column).Visited then
-- connect the two cells
Maze (Row, Column).Walls (Next_Direction) :=
False;
Maze (Next_Row, Next_Column).Walls (-Next_Direction) :=
False;
Depth_First_Algorithm (Maze, Next_Row, Next_Column);
end if;
end if;
Tested_Wall (Next_Direction) := True;
-- continue as long as there are unvisited neighbours left
All_Tested := True;
for D in Directions loop
All_Tested := All_Tested and Tested_Wall (D);
end loop;
-- all directions are either visited (from here,
-- or previously visited), or invalid.
exit when All_Tested;
end loop;
end Depth_First_Algorithm;
procedure Initialize (Maze : in out Maze_Grid) is
Row, Column : Positive;
begin
-- initialize random generators
RNG.Reset (Generator);
Random_Direction.Reset (Dir_Generator);
-- choose starting cell
Row := RNG.Random (Generator) mod Height + 1;
Column := RNG.Random (Generator) mod Width + 1;
Ada.Text_IO.Put_Line
("Starting generation at " &
Positive'Image (Row) &
" x" &
Positive'Image (Column));
Depth_First_Algorithm (Maze, Row, Column);
end Initialize;
procedure Put (Item : Maze_Grid) is
begin
for Row in Item'Range (1) loop
if Row = Item'First (1) then
Ada.Text_IO.Put ('+');
for Col in Item'Range (2) loop
if Item (Row, Col).Walls (North) then
Ada.Text_IO.Put ("---+");
else
Ada.Text_IO.Put (" +");
end if;
end loop;
Ada.Text_IO.New_Line;
end if;
for Col in Item'Range (2) loop
if Col = Item'First (2) then
if Item (Row, Col).Walls (West) then
Ada.Text_IO.Put ('|');
else
Ada.Text_IO.Put (' ');
end if;
elsif Item (Row, Col).Walls (West)
and then Item (Row, Col - 1).Walls (East)
then
Ada.Text_IO.Put ('|');
elsif Item (Row, Col).Walls (West)
or else Item (Row, Col - 1).Walls (East)
then
Ada.Text_IO.Put ('>');
else
Ada.Text_IO.Put (' ');
end if;
if Item (Row, Col).Visited then
Ada.Text_IO.Put (" ");
else
Ada.Text_IO.Put ("???");
end if;
end loop;
if Item (Row, Item'Last (2)).Walls (East) then
Ada.Text_IO.Put_Line ("|");
else
Ada.Text_IO.Put_Line (" ");
end if;
Ada.Text_IO.Put ('+');
for Col in Item'Range (2) loop
if Item (Row, Col).Walls (South) then
Ada.Text_IO.Put ("---+");
else
Ada.Text_IO.Put (" +");
end if;
end loop;
Ada.Text_IO.New_Line;
end loop;
end Put;
end Mazes;
Example main.adb:
with Mazes;
procedure Main is
package Small_Mazes is new Mazes (Height => 8, Width => 11);
My_Maze : Small_Mazes.Maze_Grid;
begin
Small_Mazes.Initialize (My_Maze);
Small_Mazes.Put (My_Maze);
end Main;
- Output:
Starting generation at 3 x 7 +---+---+---+---+---+---+---+---+---+---+---+ | | | | | + + + +---+ + + +---+---+---+ + | | | | | | | + +---+---+ +---+---+ + + + +---+ | | | | | | | | + +---+---+---+---+ +---+ + + + + | | | | | | | + + +---+ + + + +---+---+---+ + | | | | | | | + + +---+---+---+---+---+ +---+ + + | | | | | | +---+ + +---+---+---+ +---+---+---+ + | | | | | + +---+ +---+---+ +---+---+---+---+---+ | | +---+---+---+---+---+---+---+---+---+---+---+
Aime
grid_maze(data b, integer N)
{
data d;
N.times(bb_cast, d, "+---");
bb_cast(d, "+\n");
N.times(bb_cast, d, "| * ");
bb_cast(d, "|\n");
N.times(bb_copy, b, d);
b_size(d, N * 4 + 2);
bb_copy(b, d);
}
void
walk_cell(data b, integer N, line_size, x, y, list x_offsets, y_offsets)
{
integer i, p, q, r;
b_replace(b, y + x, ' ');
r = drand(3);
i = 0;
while (i < 4) {
p = x + x_offsets[q = (r + i) & 3];
q = y + y_offsets[q];
if (-1 < p && p < line_size
&& -1 < q && q < line_size * (N * 2 + 1)) {
if (b[q + p] == '*') {
walk_cell(b, N, line_size, p, q, x_offsets, y_offsets);
b[(q + y) / 2 + (p + x) / 2] = ' ';
if (p == x) {
b[(q + y) / 2 + p - 1] = ' ';
b[(q + y) / 2 + p + 1] = ' ';
}
}
}
i += 1;
}
}
walk_maze(data b, integer N)
{
integer line_size, x, y;
list x_offsets, y_offsets;
line_size = N * 4 + 1 + 1;
l_bill(x_offsets, 0, 4, 0, -4, 0);
l_bill(y_offsets, 0, 0, line_size * 2, 0, line_size * -2);
x = drand(N - 1) * 4 + 2;
y = line_size * (drand(N - 1) * 2 + 1);
walk_cell(b, N, line_size, x, y, x_offsets, y_offsets);
}
main(void)
{
data b;
grid_maze(b, 10);
walk_maze(b, 10);
o_(b);
0;
}
- Output:
+---+---+---+---+---+---+---+---+---+---+ | | | + +---+---+---+---+ + +---+---+ + | | | | | | | + + + + + + + + +---+ + | | | | | | | | | + +---+---+ +---+---+---+ + + + | | | | | | +---+---+---+---+---+---+ + + + + | | | | | + +---+---+---+---+ + + +---+---+ | | | | | | + +---+---+---+ + + +---+ + + | | | | | | | + + + + + +---+---+ +---+ + | | | | | | | +---+---+---+ +---+---+---+---+ + + | | | | | + +---+---+---+ + + + +---+ + | | | | +---+---+---+---+---+---+---+---+---+---+
APL
This example shows how to use GNU APL scripting.
#!/usr/local/bin/apl --script --
⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝
⍝ ⍝
⍝ mazeGen.apl 2022-01-07 19:47:35 (GMT-8) ⍝
⍝ ⍝
⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝
∇initPRNG
⍝⍝ Seed the internal PRNG used by APL ? operator
⎕RL ← +/ ⎕TS ⍝⍝ Not great... but good enough
∇
∇offs ← cellTo dir
⍝⍝ Return the offset (row col) to cell which lies in compass (dir)
offs ← ∊((¯1 0)(0 1)(1 0)(0 ¯1))[('nesw'⍳dir)]
∇
∇doMaze rc
⍝⍝ Main function
0 0 mazeGen rc ⍝⍝ Do the maze gen
m ⍝⍝ output result
∇
∇b ← m isVisited coord;mr;mc
→( ∨/ (coord[1] < 1) (coord[2] < 1) )/yes
→( ∨/ (coord > ⌊(⍴m)÷2) )/yes
b ← ' ' ∊ m[2×coord[1];2×coord[2]]
→0
yes:
b←1
∇
∇c mazeGen sz ;dirs;c;dir;cell;next
→(c≠(0 0))/gen
init:
c ← ?sz[1],?sz[2]
m ← mazeInit sz
gen:
cell ← c
dirs ← 'nesw'[4?4]
m[2×c[1];2×c[2]] ← ' ' ⍝ mark cell as visited
dir1:
dir ← dirs[1]
next ← cell + cellTo dir
→(m isVisited next)/dir2
m ← m openWall cell dir
next mazeGen sz
dir2:
dir ← dirs[2]
next ← cell + cellTo dir
→(m isVisited next)/dir3
m ← m openWall cell dir
next mazeGen sz
dir3:
dir ← dirs[3]
next ← cell + cellTo dir
→(m isVisited next)/dir4
m ← m openWall cell dir
next mazeGen sz
dir4:
dir ← dirs[4]
next ← cell + cellTo dir
→(m isVisited next)/done
m ← m openWall cell dir
next mazeGen sz
done:
∇
∇m ← mazeInit sz;rows;cols;r
⍝⍝ Init an ASCII grid which
⍝⍝ has all closed and unvisited cells:
⍝⍝
⍝⍝ +-+
⍝⍝ |.|
⍝⍝ +-+
⍝⍝
⍝⍝ @param sz - tuple (rows cols)
⍝⍝ @return m - ASCII representation of (rows × cols) closed maze cells
⍝⍝⍝⍝
initPRNG
(rows cols) ← sz
r ← ∊ (cols ⍴ ⊂"+-" ),"+"
r ← r,∊ (cols ⍴ ⊂"|." ),"|"
r ← (rows,(⍴r))⍴r
r ← ((2×rows),(1+2×cols))⍴r
r ← r⍪ (∊ (cols ⍴ ⊂"+-" ),"+")
m ← r
∇
∇r ← m openWall cellAndDir ;ri;ci;rw;cw;row;col;dir
(row col dir) ← ∊cellAndDir
ri ← 2×row
ci ← 2×col
(rw cw) ← (ri ci) + cellTo dir
m[rw;cw] ← ' ' ⍝ open wall in (dir)
r ← m
∇
⎕IO←1
doMaze 9 9
)OFF
- Output:
~/GNUAPL$ workspaces/mazeGen.apl +-+-+-+-+-+-+-+-+-+ | | | + + + +-+ +-+-+-+ + | | | | | | + +-+ +-+-+ + +-+-+ | | | | | + + + + +-+-+-+-+ + | | | | | | + + + +-+ +-+-+-+-+ | | | | | | | + + +-+ + + + + + + | | | | | | | | + +-+ + + +-+-+ + + | | | | | | | + + + +-+-+-+-+-+ + | | | | +-+ +-+ +-+ +-+-+-+ | | | +-+-+-+-+-+-+-+-+-+
AutoHotkey
For a challenge, this maze generation is entirely string based. That is to say, all operations including the wall removal and retrieval of cell states are done on the output string.
; Initially build the board
Width := 11
Height := 8
Loop % height*2+1
{
Outer := A_Index
Loop % Width
maze .= Outer & 1 ? "+-" : "|0"
maze .= (Outer & 1 ? "+" : "|") "`n"
}
StringTrimRight, maze, maze, 1 ; removes trailing newline
Clipboard := Walk(maze)
Walk(S, x=0, y=0){
If !x{ ; --Start at a random cell...
StringReplace, junk, S, `n,,UseErrorLevel ; Calculate rows
Random, y, 1, ErrorLevel//2
Random, x, 1, InStr(S, "`n")//2-1 ; Calculate height
}
; --Obtain a list of its neighbors...
neighbors := x "," y+1 "`n" x "," y-1 "`n" x+1 "," y "`n" x-1 "," y
; --Randomize the list...
Sort neighbors, random
; --Then for each neighbor...
Loop Parse, neighbors, `n
{
pC := InStr(A_LoopField, ","), x2 := SubStr(A_LoopField, 1, pC-1), y2 := SubStr(A_LoopField, pC+1)
; If it has not been visited...
If GetChar(S, 2*x2, 2*y2) = "0"{
; Mark it as visited...
S := ChangeChar(s, 2*x2, 2*y2, " ")
; Remove the wall between this cell and the neighbor...
S := ChangeChar(S, x+x2, y+y2, " ")
; Then recurse with the neighbor as the current cell
S := Walk(S, x2, y2)
}
}
return S
}
; Change a character in a string using x and y coordinates
ChangeChar(s, x, y, c){
Loop Parse, s, `n
{
If (A_Index = Y)
Loop Parse, A_LoopField
If (A_Index = x)
out .= c
Else out .= A_LoopField
Else out .= A_LoopField
out .= "`n"
}
StringTrimRight, out, out, 1
return out
}
; retrieve a character in a string using x and y coordinates
GetChar(s, x, y, n=1){
x*=n, y*=n
Loop Parse, s, `n
If (A_Index = Y)
return SubStr(A_LoopField, x, 1)
}
- Sample output:
+-+-+-+-+-+-+-+-+-+-+-+ | | | | +-+ +-+-+ +-+ + + +-+-+ | | | | | + +-+ +-+ +-+-+ +-+ + + | | | | | | | | + + +-+-+ + + +-+ +-+ + | | | | | | | + +-+ + +-+-+-+ +-+ + + | | | | | | + +-+-+-+-+-+ +-+-+-+ + | | | | | | + + + + +-+-+-+ + + +-+ | | | | | | | +-+-+-+-+ +-+ + +-+-+ + | | | +-+-+-+-+-+-+-+-+-+-+-+
Alternative Version
http://rosettacode.org/wiki/Maze_solving#AutoHotkey
Generator and solver combined.
AWK
#!/usr/bin/awk -f
# Remember: AWK is 1-based, for better or worse.
BEGIN {
# The maze dimensions.
width = 20; # Global
height = 20; # Global
resetMaze();
# Some constants.
top = 1;
bottom = 2;
left = 3;
right = 4;
# Randomize the PRNG.
randomize();
# Visit all the cells starting at a random point.
visitCell(getRandX(), getRandY());
# Show the result.
printMaze();
}
# Wander through the maze removing walls as we go.
function visitCell(x, y, dirList, dir, nx, ny, ndir, pi) {
setVisited(x, y); # This cell has been visited.
# Visit neighbors in a random order.
dirList = getRandDirList();
for (dir = 1; dir <= 4; dir++) {
# Get coordinates of a random neighbor (next in random direction list).
ndir = substr(dirList, dir, 1);
nx = getNextX(x, ndir);
ny = getNextY(y, ndir);
# Visit an unvisited neighbor, removing the separating walls.
if (wasVisited(nx, ny) == 0) {
rmWall(x, y, ndir);
rmWall(nx, ny, getOppositeDir(ndir));
visitCell(nx, ny)
}
}
}
# Display the text-mode maze.
function printMaze( x, y, r, w) {
for (y = 1; y <= height; y++) {
for (pass = 1; pass <= 2; pass++) { # Go over each row twice: top, middle
for (x = 1; x <= width; x++) {
if (pass == 1) { # top
printf("+");
printf(hasWall(x, y, top) == 1 ? "---" : " ");
if (x == width) printf("+");
}
else if (pass == 2) { # left, right
printf(hasWall(x, y, left) == 1 ? "|" : " ");
printf(" ");
if (x == width) printf(hasWall(x, y, right) == 1 ? "|" : " ");
}
}
print;
}
}
for (x = 1; x <= width; x++) printf("+---"); # bottom row
print("+"); # bottom right corner
}
# Given a direction, get its opposite.
function getOppositeDir(d) {
if (d == top) return bottom;
if (d == bottom) return top;
if (d == left) return right;
if (d == right) return left;
}
# Build a list (string) of the four directions in random order.
function getRandDirList( dirList, randDir, nx, ny, idx) {
dirList = "";
while (length(dirList) < 4) {
randDir = getRandDir();
if (!index(dirList, randDir)) {
dirList = dirList randDir;
}
}
return dirList;
}
# Get x coordinate of the neighbor in a given a direction.
function getNextX(x, dir) {
if (dir == left) x = x - 1;
if (dir == right) x = x + 1;
if (!isGoodXY(x, 1)) return -1; # Off the edge.
return x;
}
# Get y coordinate of the neighbor in a given a direction.
function getNextY(y, dir) {
if (dir == top) y = y - 1;
if (dir == bottom) y = y + 1;
if (!isGoodXY(1, y)) return -1; # Off the edge.
return y;
}
# Mark a cell as visited.
function setVisited(x, y, cell) {
cell = getCell(x, y);
if (cell == -1) return;
cell = substr(cell, 1, 4) "1"; # walls plus visited
setCell(x, y, cell);
}
# Get the visited state of a cell.
function wasVisited(x, y, cell) {
cell = getCell(x, y);
if (cell == -1) return 1; # Off edges already visited.
return substr(getCell(x,y), 5, 1);
}
# Remove a cell's wall in a given direction.
function rmWall(x, y, d, i, oldCell, newCell) {
oldCell = getCell(x, y);
if (oldCell == -1) return;
newCell = "";
for (i = 1; i <= 4; i++) { # Ugly as concat of two substrings and a constant?.
newCell = newCell (i == d ? "0" : substr(oldCell, i, 1));
}
newCell = newCell wasVisited(x, y);
setCell(x, y, newCell);
}
# Determine if a cell has a wall in a given direction.
function hasWall(x, y, d, cell) {
cell = getCell(x, y);
if (cell == -1) return 1; # Cells off edge always have all walls.
return substr(getCell(x, y), d, 1);
}
# Plunk a cell into the maze.
function setCell(x, y, cell, idx) {
if (!isGoodXY(x, y)) return;
maze[x, y] = cell
}
# Get a cell from the maze.
function getCell(x, y, idx) {
if (!isGoodXY(x, y)) return -1; # Bad cell marker.
return maze[x, y];
}
# Are the given coordinates in the maze?
function isGoodXY(x, y) {
if (x < 1 || x > width) return 0;
if (y < 1 || y > height) return 0;
return 1;
}
# Build the empty maze.
function resetMaze( x, y) {
delete maze;
for (y = 1; y <= height; y++) {
for (x = 1; x <= width; x++) {
maze[x, y] = "11110"; # walls (up, down, left, right) and visited state.
}
}
}
# Random things properly scaled.
function getRandX() {
return 1 + int(rand() * width);
}
function getRandY() {
return 1 +int(rand() * height);
}
function getRandDir() {
return 1 + int(rand() * 4);
}
function randomize() {
"echo $RANDOM" | getline t;
srand(t);
}
Example output:
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | | | | | +---+ +---+ +---+---+ +---+ +---+---+ +---+ +---+---+ + +---+ + | | | | | | | | | | | | | + +---+ + + +---+---+ +---+---+ + +---+ + + +---+---+---+ + | | | | | | | | | + + + +---+ +---+ + +---+ +---+---+ +---+---+ +---+ +---+ + | | | | | | | | | | | | | | +---+ + + +---+ +---+ + +---+---+ + + + +---+---+---+ +---+ | | | | | | | | | | | + + +---+---+ +---+ +---+---+---+---+ +---+---+ +---+ + +---+ + | | | | | | | | | | | | | + +---+ + + +---+---+ +---+ + + + + +---+ + + + + + | | | | | | | | | | | | | | | + + +---+---+---+ + +---+ + + + +---+---+ + + +---+---+ + | | | | | | | | | | + + +---+---+---+ +---+---+---+ + +---+---+---+ + +---+---+ +---+ | | | | | | | | | | + +---+ +---+---+---+ + +---+---+---+---+ + +---+ + + +---+ + | | | | | | | | | | | | | | + + + + +---+ + + + + + + + + + +---+---+ + +---+ | | | | | | | | | | | | | | + +---+---+---+ + +---+---+---+ + + + + + +---+ + +---+ + | | | | | | | | | | | | +---+ +---+---+---+---+---+ + +---+ +---+---+---+---+ + +---+ + + | | | | | | | | | | | | | + + + +---+ +---+ + +---+ + + +---+ +---+ +---+ + + + | | | | | | | | | | | | + + + +---+---+ +---+---+ + +---+ +---+---+ +---+---+ + +---+ | | | | | | | | | | | | | + + + + + +---+ +---+---+---+---+---+ + +---+---+ + +---+ + | | | | | | | | | +---+---+ + +---+---+---+---+ + +---+---+---+ +---+---+---+---+ + + | | | | | | | | | | + + +---+ +---+---+ + + +---+ + + +---+---+ +---+---+---+ + | | | | | | | | | | | | | + + +---+---+ + +---+ + +---+ +---+ + + +---+---+ + +---+ | | | | | | | | | | | | | + +---+ +---+ + + +---+---+ +---+ +---+ +---+---+ + +---+ + | | | | | | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
BASIC
QB64
This implementation was written using QB64. It should also be compatible with Qbasic, as it uses no QB64-exclusive features.
OPTION BASE 0
RANDOMIZE TIMER
REM must be even
width% = 40
height% = 20
REM make array and fill
DIM maze$(width%, height%)
FOR x% = 0 TO width%
FOR y% = 0 TO height%
maze$(x%, y%) = "#"
NEXT y%
NEXT x%
REM initial start location
currentx% = INT(RND * (width% - 1))
currenty% = INT(RND * (height% - 1))
REM value must be odd
IF currentx% MOD 2 = 0 THEN currentx% = currentx% + 1
IF currenty% MOD 2 = 0 THEN currenty% = currenty% + 1
maze$(currentx%, currenty%) = " "
REM generate maze
done% = 0
DO WHILE done% = 0
FOR i% = 0 TO 99
oldx% = currentx%
oldy% = currenty%
REM move in random direction
SELECT CASE INT(RND * 4)
CASE 0
IF currentx% + 2 < width% THEN currentx% = currentx% + 2
CASE 1
IF currenty% + 2 < height% THEN currenty% = currenty% + 2
CASE 2
IF currentx% - 2 > 0 THEN currentx% = currentx% - 2
CASE 3
IF currenty% - 2 > 0 THEN currenty% = currenty% - 2
END SELECT
REM if cell is unvisited then connect it
IF maze$(currentx%, currenty%) = "#" THEN
maze$(currentx%, currenty%) = " "
maze$(INT((currentx% + oldx%) / 2), ((currenty% + oldy%) / 2)) = " "
END IF
NEXT i%
REM check if all cells are visited
done% = 1
FOR x% = 1 TO width% - 1 STEP 2
FOR y% = 1 TO height% - 1 STEP 2
IF maze$(x%, y%) = "#" THEN done% = 0
NEXT y%
NEXT x%
LOOP
REM draw maze
FOR y% = 0 TO height%
FOR x% = 0 TO width%
PRINT maze$(x%, y%);
NEXT x%
PRINT
NEXT y%
REM wait
DO: LOOP WHILE INKEY$ = ""
- Output:
This used a slightly modified version that outputs to a text file. (You can't copy from a QB64 window.)
######################################### # # # # # # # # # # # ### # # # # ##### # # ### # # # # # ### # # # # # # # # # # # # # # # # # ####### # ####### ####### ##### # # # # # # # # # # # ##### ### ### # # ### # # ##### ### ### # # # # # # # # # # # # # ### ##### # ##### ### ### # ### # # ##### # # # # # # # # # # # # # # ### # # ##### ### # # # # ##### ### # # # # # # # # # # # # ### # # ######### ### ####### # ##### # # # # # # # # # # # # # # # # # ### ####### # ### # ##### # # ### # # # # # # # # # # # # # ##### # ### ### ### ### # # # # # # # # # # # # # # # # # # # # # # # # # ### # ### ### ### # ### ### ### # # # # # # # # # # # # # # #########################################
BASIC256
global size_x, size_y
size_x = 25
size_y = 15
global char_wall, char_room
char_wall = "#"
char_room = " "
global directions_permutations
directions_permutations = {{0, 1, 2, 3}, {0, 1, 3, 2}, {0, 2, 1, 3}, {0, 2, 3, 1}, {0, 3, 1, 2}, {0, 3, 2, 1}, {1, 0, 2, 3}, {1, 0, 3, 2}, {1, 2, 0, 3}, {1, 2, 3, 0}, {1, 3, 0, 2}, {1, 3, 2, 0}, {2, 0, 1, 3}, {2, 0, 3, 1}, {2, 1, 0, 3}, {2, 1, 3, 0}, {2, 3, 0, 1}, {2, 3, 1, 0}, {3, 0, 1, 2}, {3, 0, 2, 1}, {3, 1, 0, 2}, {3, 1, 2, 0}, {3, 2, 0, 1}, {3, 2, 1, 0}}
global maze
dim maze[size_x * 2 + 1][size_y * 2 + 1]
for i = 0 to size_x * 2
for j = 0 to size_y * 2
maze[i][j] = char_wall
next j
next i
call make_room(int(rand * size_x), int(rand * size_y))
call draw_maze()
end
subroutine make_room(room_x, room_y)
maze[1 + room_x * 2][1 + room_y * 2] = char_room
random_directions_index = rand * 24
for i = 0 to 3
random_direction = directions_permutations[random_directions_index][i]
if ((random_direction / 2) mod 2) < 1 then
dx = (random_direction mod 2) * 2 - 1
dy = 0
else
dx = 0
dy = (random_direction mod 2) * 2 - 1
end if
if can_dig(room_x + dx, room_y + dy) then
call make_door(room_x, room_y, dx, dy)
call make_room(room_x + dx, room_y + dy)
end if
next i
end subroutine
function can_dig(room_x, room_y)
if (room_x < 0) or (room_x >= size_x) or (room_y < 0) or (room_y >= size_y) then
can_dig = false
else
can_dig = (maze[1 + room_x * 2][1 + room_y * 2] = char_wall)
end if
end function
subroutine make_door(room_x, room_y, dx, dy)
maze[1 + room_x * 2 + dx][1 + room_y * 2 + dy] = char_room
end subroutine
subroutine draw_maze()
for i = 0 to size_y * 2
for j = 0 to size_x * 2
print maze[j][i];
next j
print
next i
end subroutine
- Output:
################################################### # # # # # # # # ####### ####### # ##### # ### ##### ##### # # ### # # # # # # # # # # # # # # ### # ####### ### # ####### # # # # # ##### ### # # # # # # # # # # # # # # # # ##### # ##### ############### # # ### ##### # # # # # # # # # # # # # # # ##### ####### ### # # # ##### # ##### ### ####### # # # # # # # # # # # # # # # # ### # ### # ##### ### # # ### # ### # ### # # ### # # # # # # # # # # # # # # # ######### ######### ##### ########### # ### # # # # # # # # # # # # # # # # # # # # ############# ### # # ### ####### # # ### # # # # # # # # # # # # # # # # # # # ####### ### # # # # ### ### ##### # # # ### # # # # # # # # # # # # # # # # ####### # # ######### ####### # ### # # # ####### # # # # # # # # # # # # # # ### # ##### ######### ### ##### # ########### # # # # # # # # # # # # # # # ####### ##### ##### # # ######### # ### ### # # # # # # # # # # # # # # # # # # ### # # # # ##### # ##### ##### # ### # # # ### # # # # # # # # # # # # # # # # # # # ### # ##### # ##### ##### ### ### # ##### # ### # # # # # # # # # # # # # # # # ####### # ####### # # ####### ### ### ####### # # # # # # ###################################################
Batch File
:amaze Rows Cols [wall char]
:: A stack-less, iterative, depth-first maze generator in native WinNT batch.
:: Rows and Cols must each be >1 and Rows*Cols cannot exceed 2096.
:: Default wall character is #, [wall char] is used if provided.
@ECHO OFF
SETLOCAL EnableDelayedExpansion
:: check for valid input, else GOTO :help
IF /I "%~2" EQU "" GOTO :amaze_help
FOR /F "tokens=* delims=0123456789" %%A IN ("%~1%~2") DO IF "%%~A" NEQ "" GOTO :amaze_help
SET /A "rows=%~1, cols=%~2, mTmp=rows*cols"
IF !rows! LSS 2 GOTO :amaze_help
IF !cols! LSS 2 GOTO :amaze_help
IF !mTmp! GTR 2096 GOTO :amaze_help
:: set map characters and use 1st character of %3 for wall, if defined
SET "wall=#"
SET "hall= "
SET "crumb=."
IF "%~3" NEQ "" SET "wall=%~3"
SET "wall=!wall:~0,1!"
:: assign width, height, cursor position, loop count, and offsets for NSEW
SET /A "cnt=0, wide=cols*2-1, high=rows*2-1, size=wide*high, N=wide*-2, S=wide*2, E=2, W=-2"
:: different random entrance points
:: ...on top
:: SET /A "start=(!RANDOM! %% cols)*2"
:: ...on bottom
:: SET /A "start=size-(!RANDOM! %% cols)*2-1"
:: ...on top or bottom
:: SET /A ch=cols*2, ch=!RANDOM! %% ch
:: IF !ch! GEQ !cols! ( SET /A "start=size-(ch-cols)*2-1"
:: ) ELSE SET /A start=ch*2
:: random entrance inside maze
SET /A "start=(!RANDOM! %% cols*2)+(!RANDOM! %% rows*2)*wide"
SET /A "curPos=start, cTmp=curPos+1, loops=cols*rows*2+1"
:: fill the maze with 8186 wall characters, clip to size, and open 1st cell
SET "mz=!wall!"
FOR /L %%A IN (1,1,6) DO SET mz=!mz!!mz!!mz!!mz!
SET bdr=!mz:~-%wide%!
SET mz=!mz:~3!!mz:~3!
SET mz=!mz:~-%size%!
SET mz=!mz:~0,%curPos%!!hall!!mz:~%cTmp%!
:: iterate #cells*2+1 steps of random depth-first search
FOR /L %%@ IN (1,1,%loops%) DO (
SET "rand=" & SET "crmPos="
REM set values for NSEW cell and wall positions
SET /A "rCnt=rTmp=0, cTmp=curPos+1, np=curPos+N, sp=curPos+S, ep=curPos+E, wp=curPos+W, wChk=curPos/wide*wide, eChk=wChk+wide, nw=curPos-wide, sw=curPos+wide, ew=curPos+1, ww=curPos-1"
REM examine adjacent cells, build direction list, and find last crumb position
FOR /F "tokens=1-8" %%A IN ("!np! !sp! !ep! !wp! !nw! !sw! !ew! !ww!") DO (
IF !np! GEQ 0 IF "!mz:~%%A,1!" EQU "!wall!" ( SET /A rCnt+=1 & SET "rand=n !rand!"
) ELSE IF "!mz:~%%E,1!" EQU "!crumb!" SET /A crmPos=np, cw=nw
IF !sp! LEQ !size! IF "!mz:~%%B,1!" EQU "!wall!" ( SET /A rCnt+=1 & SET "rand=s !rand!"
) ELSE IF "!mz:~%%F,1!" EQU "!crumb!" SET /A crmPos=sp, cw=sw
IF !ep! LEQ !eChk! IF "!mz:~%%C,1!" EQU "!wall!" ( SET /A rCnt+=1 & SET "rand=e !rand!"
) ELSE IF "!mz:~%%G,1!" EQU "!crumb!" SET /A crmPos=ep, cw=ew
IF !wp! GEQ !wChk! IF "!mz:~%%D,1!" EQU "!wall!" ( SET /A rCnt+=1 & SET "rand=w !rand!"
) ELSE IF "!mz:~%%H,1!" EQU "!crumb!" SET /A crmPos=wp, cw=ww
)
IF DEFINED rand ( REM adjacent unvisited cell is available
SET /A rCnt=!RANDOM! %% rCnt
FOR %%A IN (!rand!) DO ( REM pick random cell + wall
IF !rTmp! EQU !rCnt! SET /A "curPos=!%%Ap!, cTmp=curPos+1, mw=!%%Aw!, mTmp=mw+1"
SET /A rTmp+=1
)
REM write the 2 new characters into the maze
FOR /F "tokens=1-4" %%A IN ("!mw! !mTmp! !curPos! !cTmp!") DO (
SET "mz=!mz:~0,%%A!!crumb!!mz:~%%B!"
SET "mz=!mz:~0,%%C!!hall!!mz:~%%D!"
)
) ELSE IF DEFINED crmPos ( REM follow the crumbs backward
SET /A mTmp=cw+1
REM erase the crumb character and set new cursor position
FOR /F "tokens=1-2" %%A IN ("!cw! !mTmp!") DO SET "mz=!mz:~0,%%A!!hall!!mz:~%%B!"
SET "curPos=!crmPos!"
)
)
SET /A open=cols/2*2, mTmp=open+1
ECHO !wall!!bdr:~0,%open%!!hall!!bdr:~%mTmp%!!wall!
FOR /L %%A IN (0,!wide!,!size!) DO IF %%A LSS !size! ECHO !wall!!mz:~%%A,%wide%!!wall!
ECHO !wall!!bdr:~0,%open%!!hall!!bdr:~%mTmp%!!wall!
ENDLOCAL
EXIT /B 0
:amaze_help
ECHO Usage: %~0 Rows Cols [wall char]
ECHO Rows^>1, Cols^>1, and Rows*Cols^<=2096
ECHO Example: %~0 11 39 @
ENDLOCAL
EXIT /B 0
Example output:
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ @ @ @ @ @ @ @ @ @ @ @ @@@@@@@@@@@ @@@@@@@ @ @ @ @ @ @@@@@ @ @ @ @ @@@ @ @@@ @@@ @ @@@ @ @@@@@ @@@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @@@@@@@ @@@ @@@@@@@ @ @ @@@@@ @@@@@ @@@ @ @ @@@@@@@@@@@@@@@ @@@ @@@@@@@@@@@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @@@ @ @ @@@@@ @ @ @@@ @ @ @@@ @@@@@@@ @@@@@ @ @ @@@@@@@ @ @ @@@@@ @ @@@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @@@ @@@ @ @ @@@@@ @ @ @ @ @@@ @ @ @@@ @ @ @@@ @@@@@ @ @ @ @@@ @@@ @ @@@@@@@@@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @@@@@ @ @@@ @ @@@ @ @@@@@ @ @ @@@@@@@@@@@@@@@@@ @@@@@ @ @@@@@ @@@ @@@@@ @ @@@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @@@@@ @@@ @ @ @@@@@ @ @ @@@@@ @ @ @ @ @@@@@ @ @ @@@ @ @@@ @ @@@@@ @ @ @ @ @@@@@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @@@@@@@@@@@ @ @@@@@ @@@@@ @ @@@@@@@ @@@ @ @ @@@@@ @ @ @@@@@@@@@ @@@ @@@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @@@@@@@ @@@@@@@ @@@ @@@ @@@ @ @ @@@ @@@ @@@@@ @@@ @@@@@@@ @ @ @ @ @ @@@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @@@@@ @ @@@ @@@@@ @ @ @ @@@@@@@ @ @ @@@ @@@ @ @@@ @@@ @@@ @ @ @@@@@@@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @@@@@@@ @ @@@@@@@@@ @@@@@ @ @ @ @@@@@ @ @ @ @ @ @@@ @@@ @@@ @ @ @@@@@@@ @@@ @ @ @ @ @ @ @ @ @ @ @ @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
BBC BASIC
MazeWidth% = 11
MazeHeight% = 9
MazeCell% = 50
VDU 23,22,MazeWidth%*MazeCell%/2+3;MazeHeight%*MazeCell%/2+3;8,16,16,128
VDU 23,23,3;0;0;0; : REM Line thickness
PROCgeneratemaze(Maze&(), MazeWidth%, MazeHeight%, MazeCell%)
END
DEF PROCgeneratemaze(RETURN m&(), w%, h%, s%)
LOCAL x%, y%
DIM m&(w%, h%)
FOR y% = 0 TO h%
LINE 0,y%*s%,w%*s%,y%*s%
NEXT
FOR x% = 0 TO w%
LINE x%*s%,0,x%*s%,h%*s%
NEXT
GCOL 15
PROCcell(m&(), RND(w%)-1, y% = RND(h%)-1, w%, h%, s%)
ENDPROC
DEF PROCcell(m&(), x%, y%, w%, h%, s%)
LOCAL i%, p%, q%, r%
m&(x%,y%) OR= &40 : REM Mark visited
r% = RND(4)
FOR i% = r% TO r%+3
CASE i% MOD 4 OF
WHEN 0: p% = x%-1 : q% = y%
WHEN 1: p% = x%+1 : q% = y%
WHEN 2: p% = x% : q% = y%-1
WHEN 3: p% = x% : q% = y%+1
ENDCASE
IF p% >= 0 IF p% < w% IF q% >= 0 IF q% < h% IF m&(p%,q%) < &40 THEN
IF p% > x% m&(p%,q%) OR= 1 : LINE p%*s%,y%*s%+4,p%*s%,(y%+1)*s%-4
IF q% > y% m&(p%,q%) OR= 2 : LINE x%*s%+4,q%*s%,(x%+1)*s%-4,q%*s%
IF x% > p% m&(x%,y%) OR= 1 : LINE x%*s%,y%*s%+4,x%*s%,(y%+1)*s%-4
IF y% > q% m&(x%,y%) OR= 2 : LINE x%*s%+4,y%*s%,(x%+1)*s%-4,y%*s%
PROCcell(m&(), p%, q%, w%, h%, s%)
ENDIF
NEXT
ENDPROC
Befunge
Dimensions are specified by the first two values pushed onto the stack - currently 20 (45*) by 16 (28*). Note, however, that the upper limit in a standard Befunge-93 implementation will be around 38 by 40 (1520 cells) due to the constrained page size.
Also note that this requires an interpreter with working read-write memory support, which is suprisingly rare in online implementations. Padding the code page with extra blank lines or spaces can sometimes help. Using smaller dimensions might also be preferable, especially on slower implementations.
45*28*10p00p020p030p006p0>20g30g00g*+::"P"%\"P"/6+gv>$\1v@v1::\+g02+*g00+g03-\<
0_ 1!%4+1\-\0!::\-\2%2:p<pv0<< v0p+6/"P"\%"P":\+4%4<^<v-<$>+2%\1-*20g+\1+4%::v^
#| +2%\1-*30g+\1\40g1-:v0+v2?1#<v>+:00g%!55+*>:#0>#,_^>:!|>\#%"P"v#:*+*g00g0<>1
02!:++`\0\`-1g01:\+`\< !46v3<^$$<^1,g2+1%2/2,g1+1<v%g00:\<*g01,<>:30p\:20p:v^3g
0#$g#<1#<-#<`#<\#<0#<^#_^/>#1+#4<>"P"%\"P"/6+g:2%^!>,1-:#v_$55+^|$$ "JH" $$>#<0
::"P"%\"P"/6+g40p\40g+\:#^"P"%#\<^ ::$_,#!0#:<*"|"<^," _"<:g000 <> /6+g4/2%+#^_
- Output:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |_ | _ _ |_ _ _| _ _ _ _ |_ | | |_ _| _|_ |_|_ | _| | _|_ |_ | | | |_ _ | | |_ _ _| | _ _ _|_ | | |_ |_| _|_ _|_ |_ _| _|_ | _| | | |_ _ _ | | _ _| | _ | | | | _| | _ _ |_| | | | _ _| |_| _| _|_ _| | | | | _| _|_ _|_ |_ _ | | | | _ _ | |_ |_ _ _| _ _ |_ _ |_ _| | |_ | | | | |_ _ _ _ _| |_ _ _ _| | |_ | | | | |_ | _ _ _|_ _ _ _ _ | |_ |_ _| | | | _|_ _|_ _ _ | _ _|_ |_ _ _| | | | _ _ _ | | _|_|_ _ _ |_ |_ _ | | | _| _ | | |_ _ _ _ _ _ _| | | | | |_ |_ | _|_| | _ |_| _ _ _| | | | | | | | |_| _ _| | | _| _| | _|_| | |_ _ _|_ _ _ _ _|_ _|_ _ _ _ _|_|_ _ _ _|
C
Generation/solver in one. Requires UTF8 locale and unicode capable console. If your console font line-drawing chars are single width, define DOUBLE_SPACE to 0.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <locale.h>
#define DOUBLE_SPACE 1
#if DOUBLE_SPACE
# define SPC " "
#else
# define SPC " "
#endif
wchar_t glyph[] = L""SPC"│││─┘┐┤─└┌├─┴┬┼"SPC"┆┆┆┄╯╮ ┄╰╭ ┄";
typedef unsigned char byte;
enum { N = 1, S = 2, W = 4, E = 8, V = 16 };
byte **cell;
int w, h, avail;
#define each(i, x, y) for (i = x; i <= y; i++)
int irand(int n)
{
int r, rmax = n * (RAND_MAX / n);
while ((r = rand()) >= rmax);
return r / (RAND_MAX/n);
}
void show()
{
int i, j, c;
each(i, 0, 2 * h) {
each(j, 0, 2 * w) {
c = cell[i][j];
if (c > V) printf("\033[31m");
printf("%lc", glyph[c]);
if (c > V) printf("\033[m");
}
putchar('\n');
}
}
inline int max(int a, int b) { return a >= b ? a : b; }
inline int min(int a, int b) { return b >= a ? a : b; }
static int dirs[4][2] = {{-2, 0}, {0, 2}, {2, 0}, {0, -2}};
void walk(int x, int y)
{
int i, t, x1, y1, d[4] = { 0, 1, 2, 3 };
cell[y][x] |= V;
avail--;
for (x1 = 3; x1; x1--)
if (x1 != (y1 = irand(x1 + 1)))
i = d[x1], d[x1] = d[y1], d[y1] = i;
for (i = 0; avail && i < 4; i++) {
x1 = x + dirs[ d[i] ][0], y1 = y + dirs[ d[i] ][1];
if (cell[y1][x1] & V) continue;
/* break walls */
if (x1 == x) {
t = (y + y1) / 2;
cell[t][x+1] &= ~W, cell[t][x] &= ~(E|W), cell[t][x-1] &= ~E;
} else if (y1 == y) {
t = (x + x1)/2;
cell[y-1][t] &= ~S, cell[y][t] &= ~(N|S), cell[y+1][t] &= ~N;
}
walk(x1, y1);
}
}
int solve(int x, int y, int tox, int toy)
{
int i, t, x1, y1;
cell[y][x] |= V;
if (x == tox && y == toy) return 1;
each(i, 0, 3) {
x1 = x + dirs[i][0], y1 = y + dirs[i][1];
if (cell[y1][x1]) continue;
/* mark path */
if (x1 == x) {
t = (y + y1)/2;
if (cell[t][x] || !solve(x1, y1, tox, toy)) continue;
cell[t-1][x] |= S, cell[t][x] |= V|N|S, cell[t+1][x] |= N;
} else if (y1 == y) {
t = (x + x1)/2;
if (cell[y][t] || !solve(x1, y1, tox, toy)) continue;
cell[y][t-1] |= E, cell[y][t] |= V|E|W, cell[y][t+1] |= W;
}
return 1;
}
/* backtrack */
cell[y][x] &= ~V;
return 0;
}
void make_maze()
{
int i, j;
int h2 = 2 * h + 2, w2 = 2 * w + 2;
byte **p;
p = calloc(sizeof(byte*) * (h2 + 2) + w2 * h2 + 1, 1);
p[1] = (byte*)(p + h2 + 2) + 1;
each(i, 2, h2) p[i] = p[i-1] + w2;
p[0] = p[h2];
cell = &p[1];
each(i, -1, 2 * h + 1) cell[i][-1] = cell[i][w2 - 1] = V;
each(j, 0, 2 * w) cell[-1][j] = cell[h2 - 1][j] = V;
each(i, 0, h) each(j, 0, 2 * w) cell[2*i][j] |= E|W;
each(i, 0, 2 * h) each(j, 0, w) cell[i][2*j] |= N|S;
each(j, 0, 2 * w) cell[0][j] &= ~N, cell[2*h][j] &= ~S;
each(i, 0, 2 * h) cell[i][0] &= ~W, cell[i][2*w] &= ~E;
avail = w * h;
walk(irand(2) * 2 + 1, irand(h) * 2 + 1);
/* reset visited marker (it's also used by path finder) */
each(i, 0, 2 * h) each(j, 0, 2 * w) cell[i][j] &= ~V;
solve(1, 1, 2 * w - 1, 2 * h - 1);
show();
}
int main(int c, char **v)
{
setlocale(LC_ALL, "");
if (c < 2 || (w = atoi(v[1])) <= 0) w = 16;
if (c < 3 || (h = atoi(v[2])) <= 0) h = 8;
make_maze();
return 0;
}
- Sample output:
┌───┬─────┬─────────┬───────┬───┐ │┄┄╮│╭┄┄┄╮│ ╭┄┄┄┄┄╮│ ╭┄┄┄╮│╭┄╮│ │ │┆│┆──┐┆│ │┆──┬─┐┆└──┆┌─┐┆│┆│┆│ │ │┆│╰┄╮│┆│ │╰┄╮│ │╰┄┄┄╯│ │╰┄╯│┆│ │ │┆└──┆│┆└─┼──┆│ └─────┤ └─┬─┘┆│ │ │╰┄┄┄╯│╰┄╮│╭┄╯│ │ │╭┄╯│ │ └─────┴─┐┆│┆┌─┴───┐ │ │ │ │┆──┤ │ │┆│┆│╭┄┄┄╮│ │ │ │╰┄╮│ │ ──────┐ │┆│┆│┆──┐┆└─┤ ┌─┘ └─┐┆│ │ │ │┆│╰┄╯ │╰┄╮│ │ │┆│ │ ┌─────┘ │┆├─────┴─┐┆│ │ ──┬─┘┆│ │ │ │┆│╭┄┄┄┄┄╮│┆│ │ │╭┄╯│ ├─┤ ──┬─┬─┘┆│┆┌─┬──┆│┆└─┴─┐ │┆┌─┤ │ │ │ │╭┄╯│┆│ │╭┄╯│╰┄┄┄╮│ │┆│ │ │ └── │ │┆──┘┆│ │┆──┴────┆│ │┆│ │ │ │ ╰┄┄┄╯│ ╰┄┄┄┄┄┄┄╯│ ╰┄┄│ └─────┴───────┴───────────┴─────┘
Alternative approach
/* --------------------------------------------------- */ /* I include this alternative method for consideration */ /* --------------------------------------------------- */ /* in file define’s.h */ /* I came to ansi-C after Algol-60 etc and these five pretend to be language tokens */ /* I do like ansi-C but the syntax is rather 'scruffy' */ #define then #define endif #define endwhile #define endfor #define endswitch #ifndef BOOL #define BOOL int #define TRUE 1 #define FALSE 0 #endif #define MAZE_SIZE_X 16 #define MAZE_SIZE_Y 16 /* in file GlobalDefs.h */ unsigned char box[MAZE_SIZE_X+1][MAZE_SIZE_Y+1]; int allowed[4]; int x_[4] = { 0, 1, 0, -1 }; int y_[4] = { 1, 0, -1, 0 }; /* in file DesignMaze.c */ /* This produces an enclosed rectangular maze. There will only be one path between any (x1,y1) and (x2,y2). The code to add doors is not included here */ /* When I write code for me I put an explanation before a function to remind me what I was thinking at the time – I have retained those explanations to self here */ /* Also note at the end of the path_check() function the return relies on the weak type checking of ansi-C and (int)TRUE == 1 */ /* By making the recursive functions static, this is a hint to the compiler to simplify the stack code instructions as the compiler knows everything that it needs from the current source file and does not need to involve the linker. Implementation specific #pragma could also be used */ /**************************************************************************/ /* */ /* The maze is made up of a set of boxes (it is a rectangular maze). */ /* Each box has a description of the four sides, each side can be :- */ /* (a) a solid wall */ /* (b) a gap */ /* (c) a door */ /* */ /* A side has an opening bit set for gaps and doors, this makes the */ /* movement checking easier. */ /* */ /* For any opening there are four bits corresponding to the four sides:- */ /* Bit 0 is set if Northwards movement is allowed */ /* Bit 1 is set if Eastwards movement is allowed */ /* Bit 2 is set if Southwards movement is allowed */ /* Bit 3 is set if Westwards movement is allowed */ /* */ /* For a door there are four bits corresponding to the four sides:- */ /* Bit 4 is set if North has a door, unset if North has a gap */ /* Bit 5 is set if East has a door, unset if East has a gap */ /* Bit 6 is set if South has a door, unset if South has a gap */ /* Bit 7 is set if West has a door, unset if West has a gap */ /* */ /**************************************************************************/ /**************************************************************************/ /********************************** path_check ****************************/ /**************************************************************************/ /* */ /* This sets: */ /* allowed[0] to TRUE if a path could be extended to the 'North' */ /* allowed[1] to TRUE if a path could be extended to the 'East' */ /* allowed[2] to TRUE if a path could be extended to the 'South' */ /* allowed[3] to TRUE if a path could be extended to the 'West' */ /* */ /* A path could be extended in a given direction if it does not go */ /* beyond the edge of the maze (there are no gaps in the surrounding */ /* walls), or the adjacent box has not already been visited */ /* (i.e. it contains 0). */ /* */ /* It also returns non-zero if there is at least one potential path */ /* which can be extended. */ /* */ /**************************************************************************/ static int path_check(int x, int y) { if ( y > (MAZE_SIZE_Y-1) ) then { allowed[0] = FALSE; } else { allowed[0] = (box[x ][y+1] == 0) ? TRUE : FALSE; } endif if ( x > (MAZE_SIZE_X-1) ) then { allowed[1] = FALSE; } else { allowed[1] = (box[x+1][y ] == 0) ? TRUE : FALSE; } endif if ( y < 2 ) then { allowed[2] = FALSE; } else { allowed[2] = (box[x ][y-1] == 0) ? TRUE : FALSE; } endif if ( x < 2 ) then { allowed[3] = FALSE; } else { allowed[3] = (box[x-1][y ] == 0) ? TRUE : FALSE; } endif return (allowed[0]+allowed[1]+allowed[2]+allowed[3]); } /* end of 'path_check' */ /**************************************************************************/ /********************************** design_maze ***************************/ /**************************************************************************/ /* */ /* This is a recursive routine to produce a random rectangular maze */ /* with only one route between any two points. */ /* For each box reached, a 'wall' is knocked through to an adjacent */ /* box if that box has not previously been reached (i.e. no walls */ /* knocked out). */ /* For the adjacent box the adjacent wall is also knocked out. */ /* Then the routine is called again from the adjacent box. */ /* If there are no adjacent boxes which have not already been reached */ /* then the routine returns to the previous call. */ /* */ /**************************************************************************/ static void design_maze(int x, int y) { int direction; while ( path_check(x,y) > 0) { do { direction = rand()%4; } while ( allowed[direction]==FALSE ); box[x ][y ] |= (1 << direction ); box[x+x_[direction]][y+y_[direction]] |= (1 << ((direction+2) % 4) ); design_maze( x+x_[direction] , y+y_[direction] ); } endwhile } /* end of 'design_maze()' */
C#
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Drawing;
namespace MazeGeneration
{
public static class Extensions
{
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source, Random rng)
{
var e = source.ToArray();
for (var i = e.Length - 1; i >= 0; i--)
{
var swapIndex = rng.Next(i + 1);
yield return e[swapIndex];
e[swapIndex] = e[i];
}
}
public static CellState OppositeWall(this CellState orig)
{
return (CellState)(((int) orig >> 2) | ((int) orig << 2)) & CellState.Initial;
}
public static bool HasFlag(this CellState cs,CellState flag)
{
return ((int)cs & (int)flag) != 0;
}
}
[Flags]
public enum CellState
{
Top = 1,
Right = 2,
Bottom = 4,
Left = 8,
Visited = 128,
Initial = Top | Right | Bottom | Left,
}
public struct RemoveWallAction
{
public Point Neighbour;
public CellState Wall;
}
public class Maze
{
private readonly CellState[,] _cells;
private readonly int _width;
private readonly int _height;
private readonly Random _rng;
public Maze(int width, int height)
{
_width = width;
_height = height;
_cells = new CellState[width, height];
for(var x=0; x<width; x++)
for(var y=0; y<height; y++)
_cells[x, y] = CellState.Initial;
_rng = new Random();
VisitCell(_rng.Next(width), _rng.Next(height));
}
public CellState this[int x, int y]
{
get { return _cells[x,y]; }
set { _cells[x,y] = value; }
}
public IEnumerable<RemoveWallAction> GetNeighbours(Point p)
{
if (p.X > 0) yield return new RemoveWallAction {Neighbour = new Point(p.X - 1, p.Y), Wall = CellState.Left};
if (p.Y > 0) yield return new RemoveWallAction {Neighbour = new Point(p.X, p.Y - 1), Wall = CellState.Top};
if (p.X < _width-1) yield return new RemoveWallAction {Neighbour = new Point(p.X + 1, p.Y), Wall = CellState.Right};
if (p.Y < _height-1) yield return new RemoveWallAction {Neighbour = new Point(p.X, p.Y + 1), Wall = CellState.Bottom};
}
public void VisitCell(int x, int y)
{
this[x,y] |= CellState.Visited;
foreach (var p in GetNeighbours(new Point(x, y)).Shuffle(_rng).Where(z => !(this[z.Neighbour.X, z.Neighbour.Y].HasFlag(CellState.Visited))))
{
this[x, y] -= p.Wall;
this[p.Neighbour.X, p.Neighbour.Y] -= p.Wall.OppositeWall();
VisitCell(p.Neighbour.X, p.Neighbour.Y);
}
}
public void Display()
{
var firstLine = string.Empty;
for (var y = 0; y < _height; y++)
{
var sbTop = new StringBuilder();
var sbMid = new StringBuilder();
for (var x = 0; x < _width; x++)
{
sbTop.Append(this[x, y].HasFlag(CellState.Top) ? "+---" : "+ ");
sbMid.Append(this[x, y].HasFlag(CellState.Left) ? "| " : " ");
}
if (firstLine == string.Empty)
firstLine = " " + sbTop.ToString();
Debug.WriteLine(" " + sbTop + "+");
Debug.WriteLine(" " + sbMid + "|");
}
Debug.WriteLine(firstLine);
}
}
class Program
{
static void Main(string[] args)
{
var maze = new Maze(20, 20);
maze.Display();
}
}
}
Sample output:
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | | | | | + +---+ +---+---+ +---+ + +---+---+ +---+ +---+---+ +---+---+ + | | | | | | | | | | | | + + +---+ +---+---+ + +---+---+ +---+---+ + +---+---+ + + + | | | | | | | | | | | | + + +---+---+ + + + + +---+---+ + +---+---+ +---+---+ + + | | | | | | | | | | | | + +---+ + + + +---+---+ +---+---+---+---+---+ + + +---+---+ + | | | | | | | | | | | +---+---+---+ + +---+---+ +---+---+---+---+ + +---+---+ + + +---+ | | | | | | | | | + +---+---+---+ + +---+---+---+---+ + +---+---+ +---+---+ +---+ + | | | | | | | | | | | + + + +---+---+---+---+ + + + + + +---+---+ + + +---+ + | | | | | | | | | | + +---+---+ +---+ +---+---+ +---+---+ +---+ +---+---+ +---+---+---+ | | | | | | | | | | | | | +---+---+ +---+ + + +---+---+ +---+ + + + + +---+---+ + + | | | | | | | | | | + +---+---+ +---+---+---+ + + +---+---+ +---+---+---+---+---+---+ + | | | | | | | | | + + + +---+ +---+ + + +---+ +---+---+ +---+---+---+---+---+ + | | | | | | | | | | | + + +---+ +---+ +---+---+ + + + +---+---+ +---+---+---+---+ + | | | | | | | | | | + +---+---+---+---+---+ +---+---+ + +---+ + + + + +---+---+ + | | | | | | | | | | | | | | + + + +---+ + + + +---+ + + +---+---+---+ +---+ + + + | | | | | | | | | | | | +---+---+---+ + +---+---+ + +---+ +---+---+ + + +---+---+---+---+ | | | | | | | | | | | + +---+ + + + + +---+---+ +---+ + +---+ +---+ +---+ + + | | | | | | | | | | | | | | | + + +---+---+---+ +---+---+---+ + +---+ + + + + + +---+ + | | | | | | | | | | | +---+ +---+ + +---+---+---+ +---+ + +---+ + +---+---+---+---+ + | | | | | | | | | | + +---+---+---+ + +---+ +---+---+---+ + + +---+ +---+---+---+ + | | | | | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---
C++
#include <windows.h>
#include <iostream>
#include <string>
//--------------------------------------------------------------------------------------------------
using namespace std;
//--------------------------------------------------------------------------------------------------
const int BMP_SIZE = 512, CELL_SIZE = 8;
//--------------------------------------------------------------------------------------------------
enum directions { NONE, NOR = 1, EAS = 2, SOU = 4, WES = 8 };
//--------------------------------------------------------------------------------------------------
class myBitmap
{
public:
myBitmap() : pen( NULL ) {}
~myBitmap()
{
DeleteObject( pen );
DeleteDC( hdc );
DeleteObject( bmp );
}
bool create( int w, int h )
{
BITMAPINFO bi;
ZeroMemory( &bi, sizeof( bi ) );
bi.bmiHeader.biSize = sizeof( bi.bmiHeader );
bi.bmiHeader.biBitCount = sizeof( DWORD ) * 8;
bi.bmiHeader.biCompression = BI_RGB;
bi.bmiHeader.biPlanes = 1;
bi.bmiHeader.biWidth = w;
bi.bmiHeader.biHeight = -h;
HDC dc = GetDC( GetConsoleWindow() );
bmp = CreateDIBSection( dc, &bi, DIB_RGB_COLORS, &pBits, NULL, 0 );
if( !bmp ) return false;
hdc = CreateCompatibleDC( dc );
SelectObject( hdc, bmp );
ReleaseDC( GetConsoleWindow(), dc );
width = w; height = h;
return true;
}
void clear()
{
ZeroMemory( pBits, width * height * sizeof( DWORD ) );
}
void setPenColor( DWORD clr )
{
if( pen ) DeleteObject( pen );
pen = CreatePen( PS_SOLID, 1, clr );
SelectObject( hdc, pen );
}
void saveBitmap( string path )
{
BITMAPFILEHEADER fileheader;
BITMAPINFO infoheader;
BITMAP bitmap;
DWORD wb;
GetObject( bmp, sizeof( bitmap ), &bitmap );
DWORD* dwpBits = new DWORD[bitmap.bmWidth * bitmap.bmHeight];
ZeroMemory( dwpBits, bitmap.bmWidth * bitmap.bmHeight * sizeof( DWORD ) );
ZeroMemory( &infoheader, sizeof( BITMAPINFO ) );
ZeroMemory( &fileheader, sizeof( BITMAPFILEHEADER ) );
infoheader.bmiHeader.biBitCount = sizeof( DWORD ) * 8;
infoheader.bmiHeader.biCompression = BI_RGB;
infoheader.bmiHeader.biPlanes = 1;
infoheader.bmiHeader.biSize = sizeof( infoheader.bmiHeader );
infoheader.bmiHeader.biHeight = bitmap.bmHeight;
infoheader.bmiHeader.biWidth = bitmap.bmWidth;
infoheader.bmiHeader.biSizeImage = bitmap.bmWidth * bitmap.bmHeight * sizeof( DWORD );
fileheader.bfType = 0x4D42;
fileheader.bfOffBits = sizeof( infoheader.bmiHeader ) + sizeof( BITMAPFILEHEADER );
fileheader.bfSize = fileheader.bfOffBits + infoheader.bmiHeader.biSizeImage;
GetDIBits( hdc, bmp, 0, height, ( LPVOID )dwpBits, &infoheader, DIB_RGB_COLORS );
HANDLE file = CreateFile( path.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
WriteFile( file, &fileheader, sizeof( BITMAPFILEHEADER ), &wb, NULL );
WriteFile( file, &infoheader.bmiHeader, sizeof( infoheader.bmiHeader ), &wb, NULL );
WriteFile( file, dwpBits, bitmap.bmWidth * bitmap.bmHeight * 4, &wb, NULL );
CloseHandle( file );
delete [] dwpBits;
}
HDC getDC() const { return hdc; }
int getWidth() const { return width; }
int getHeight() const { return height; }
private:
HBITMAP bmp;
HDC hdc;
HPEN pen;
void *pBits;
int width, height;
};
//--------------------------------------------------------------------------------------------------
class mazeGenerator
{
public:
mazeGenerator()
{
_world = 0;
_bmp.create( BMP_SIZE, BMP_SIZE );
_bmp.setPenColor( RGB( 0, 255, 0 ) );
}
~mazeGenerator() { killArray(); }
void create( int side )
{
_s = side;
generate();
display();
}
private:
void generate()
{
killArray();
_world = new BYTE[_s * _s];
ZeroMemory( _world, _s * _s );
_ptX = rand() % _s; _ptY = rand() % _s;
carve();
}
void carve()
{
while( true )
{
int d = getDirection();
if( d < NOR ) return;
switch( d )
{
case NOR:
_world[_ptX + _s * _ptY] |= NOR; _ptY--;
_world[_ptX + _s * _ptY] = SOU | SOU << 4;
break;
case EAS:
_world[_ptX + _s * _ptY] |= EAS; _ptX++;
_world[_ptX + _s * _ptY] = WES | WES << 4;
break;
case SOU:
_world[_ptX + _s * _ptY] |= SOU; _ptY++;
_world[_ptX + _s * _ptY] = NOR | NOR << 4;
break;
case WES:
_world[_ptX + _s * _ptY] |= WES; _ptX--;
_world[_ptX + _s * _ptY] = EAS | EAS << 4;
}
}
}
void display()
{
_bmp.clear();
HDC dc = _bmp.getDC();
for( int y = 0; y < _s; y++ )
{
int yy = y * _s;
for( int x = 0; x < _s; x++ )
{
BYTE b = _world[x + yy];
int nx = x * CELL_SIZE,
ny = y * CELL_SIZE;
if( !( b & NOR ) )
{
MoveToEx( dc, nx, ny, NULL );
LineTo( dc, nx + CELL_SIZE + 1, ny );
}
if( !( b & EAS ) )
{
MoveToEx( dc, nx + CELL_SIZE, ny, NULL );
LineTo( dc, nx + CELL_SIZE, ny + CELL_SIZE + 1 );
}
if( !( b & SOU ) )
{
MoveToEx( dc, nx, ny + CELL_SIZE, NULL );
LineTo( dc, nx + CELL_SIZE + 1, ny + CELL_SIZE );
}
if( !( b & WES ) )
{
MoveToEx( dc, nx, ny, NULL );
LineTo( dc, nx, ny + CELL_SIZE + 1 );
}
}
}
//_bmp.saveBitmap( "f:\\rc\\maze.bmp" );
BitBlt( GetDC( GetConsoleWindow() ), 10, 60, BMP_SIZE, BMP_SIZE, _bmp.getDC(), 0, 0, SRCCOPY );
}
int getDirection()
{
int d = 1 << rand() % 4;
while( true )
{
for( int x = 0; x < 4; x++ )
{
if( testDir( d ) ) return d;
d <<= 1;
if( d > 8 ) d = 1;
}
d = ( _world[_ptX + _s * _ptY] & 0xf0 ) >> 4;
if( !d ) return -1;
switch( d )
{
case NOR: _ptY--; break;
case EAS: _ptX++; break;
case SOU: _ptY++; break;
case WES: _ptX--; break;
}
d = 1 << rand() % 4;
}
}
bool testDir( int d )
{
switch( d )
{
case NOR: return ( _ptY - 1 > -1 && !_world[_ptX + _s * ( _ptY - 1 )] );
case EAS: return ( _ptX + 1 < _s && !_world[_ptX + 1 + _s * _ptY] );
case SOU: return ( _ptY + 1 < _s && !_world[_ptX + _s * ( _ptY + 1 )] );
case WES: return ( _ptX - 1 > -1 && !_world[_ptX - 1 + _s * _ptY] );
}
return false;
}
void killArray() { if( _world ) delete [] _world; }
BYTE* _world;
int _s, _ptX, _ptY;
myBitmap _bmp;
};
//--------------------------------------------------------------------------------------------------
int main( int argc, char* argv[] )
{
ShowWindow( GetConsoleWindow(), SW_MAXIMIZE );
srand( GetTickCount() );
mazeGenerator mg;
int s;
while( true )
{
cout << "Enter the maze size, an odd number bigger than 2 ( 0 to QUIT ): "; cin >> s;
if( !s ) return 0;
if( !( s & 1 ) ) s++;
if( s >= 3 ) mg.create( s );
cout << endl;
system( "pause" );
system( "cls" );
}
return 0;
}
//--------------------------------------------------------------------------------------------------
Chapel
use Random;
config const rows: int = 9;
config const cols: int = 16;
if rows < 1 || cols < 1 {
writeln("Maze must be at least 1x1 in size.");
exit(1);
}
enum direction {N = 1, E = 2, S = 3, W = 4};
record Cell {
var spaces: [direction.N .. direction.W] bool;
var visited: bool;
}
const dirs = [
((-1, 0), direction.N, direction.S), // ((rowDir, colDir), myWall, neighbourWall)
((0, 1), direction.E, direction.W),
((1, 0), direction.S, direction.N),
((0, -1), direction.W, direction.E)
];
var maze: [1..rows, 1..cols] Cell;
var startingCell = (choose(maze.dim(0)), choose(maze.dim(1)));
checkCell(maze, startingCell);
displayMaze(maze);
proc checkCell(ref maze, cell) {
maze[cell].visited = true;
for dir in permute(dirs) {
var (offset, thisDir, neighbourDir) = dir;
var neighbour = cell + offset;
if maze.domain.contains(neighbour) && !maze[neighbour].visited {
maze[cell].spaces[thisDir] = true;
maze[neighbour].spaces[neighbourDir] = true;
checkCell(maze, neighbour);
}
}
}
proc displayMaze(maze) {
for row in maze.dim(0) {
for col in maze.dim(1) {
write(if maze[row, col].spaces[direction.N] then "+ " else "+---");
}
writeln("+");
for col in maze.dim(1) {
write(if maze[row, col].spaces[direction.W] then " " else "| ");
}
writeln("|");
}
write("+---" * maze.dim(1).size);
writeln("+");
}
- Output:
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | | | | | + + + +---+---+ + +---+ + +---+---+ +---+---+ + | | | | | | | | | | + +---+---+---+ +---+---+ + +---+ +---+ + + +---+ | | | | | | | +---+---+ + +---+---+---+---+---+---+ + +---+---+---+ + | | | | | | | + + +---+---+---+---+---+ + +---+---+---+---+---+ + + | | | | | | | | + +---+---+---+ + +---+---+ + +---+ +---+ +---+ + | | | | | | | | | | | + +---+ + +---+---+ + +---+---+ + + +---+ + + | | | | | | | | | +---+---+---+ + + + +---+---+ + +---+---+ +---+ + | | | | | | | | | | | | + + +---+ + + +---+ + + +---+ + +---+ + + | | | | | | | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Clojure
(ns maze.core
(:require [clojure.set :refer [intersection
select]]
[clojure.string :as str]))
;; Misc functions
(defn neighborhood
([] (neighborhood [0 0]))
([coord] (neighborhood coord 1))
([[y x] r]
(let [y-- (- y r) y++ (+ y r)
x-- (- x r) x++ (+ x r)]
#{[y++ x] [y-- x] [y x--] [y x++]})))
(defn cell-empty? [maze coords]
(= :empty (get-in maze coords)))
(defn wall? [maze coords]
(= :wall (get-in maze coords)))
(defn filter-maze
([pred maze coords]
(select (partial pred maze) (set coords)))
([pred maze]
(filter-maze
pred
maze
(for [y (range (count maze))
x (range (count (nth maze y)))]
[y x]))))
(defn create-empty-maze [width height]
(let [width (inc (* 2 width))
height (inc (* 2 height))]
(vec (take height
(interleave
(repeat (vec (take width (repeat :wall))))
(repeat (vec (take width (cycle [:wall :empty])))))))))
(defn next-step [possible-steps]
(rand-nth (vec possible-steps)))
;;Algo
(defn create-random-maze [width height]
(loop [maze (create-empty-maze width height)
stack []
nonvisited (filter-maze cell-empty? maze)
visited #{}
coords (next-step nonvisited)]
(if (empty? nonvisited)
maze
(let [nonvisited-neighbors (intersection (neighborhood coords 2) nonvisited)]
(cond
(seq nonvisited-neighbors)
(let [next-coords (next-step nonvisited-neighbors)
wall-coords (map #(+ %1 (/ (- %2 %1) 2)) coords next-coords)]
(recur (assoc-in maze wall-coords :empty)
(conj stack coords)
(disj nonvisited next-coords)
(conj visited next-coords)
next-coords))
(seq stack)
(recur maze (pop stack) nonvisited visited (last stack)))))))
;;Conversion to string
(def cell-code->str
[" " " " " " " " "· " "╵ " "╴ " "┘ "
" " " " " " " " "╶─" "└─" "──" "┴─"
" " " " " " " " "╷ " "│ " "┐ " "┤ "
" " " " " " " " "┌─" "├─" "┬─" "┼─"])
(defn cell-code [maze coord]
(transduce
(comp
(map (partial wall? maze))
(keep-indexed (fn [idx el] (when el idx)))
(map (partial bit-shift-left 1)))
(completing bit-or)
0
(sort (cons coord (neighborhood coord)))))
(defn cell->str [maze coord]
(get cell-code->str (cell-code maze coord)))
(defn maze->str [maze]
(->> (for [y (range (count maze))]
(for [x (range (count (nth maze y)))]
(cell->str maze [y x])))
(map str/join)
(str/join \newline)))
;;Task
(println (maze->str (create-random-maze 10 10)))
- Output:
┌───────────┬───────────────┬───────┬───┐ │ │ │ │ │ ├───╴ ╷ ╵ ┌───────────┤ ╷ │ │ │ │ │ │ │ │ │ │ ╷ └───┐ │ ╶───┐ ╵ │ │ │ │ │ │ │ │ │ │ │ │ └───┐ └───┴───╴ ├───────┤ │ │ │ │ │ │ │ │ │ ╷ └───────────────┼───╴ │ ╵ │ │ │ │ │ │ ├───┴───┐ ┌───────┐ ╵ ╷ ├───╴ │ │ │ │ │ │ │ │ │ ╷ ╵ │ ╷ ╵ ┌───┴───┘ ┌───┤ │ │ │ │ │ │ │ │ └───────┴───┴───────┤ ╶───────┤ │ │ │ │ │ │ ╶───────┬───────┐ └───┬───╴ │ │ │ │ │ │ │ │ ├───────╴ ╵ ╷ │ ╶───┘ ╶───┘ │ │ │ │ │ └───────────────┴───┴───────────────────┘
Commodore BASIC
Written in Commodore BASIC V2 and tested on Commodore 64 and Commodore 128 hardware. (It will also run on the unexpanded Commodore VIC-20 if you reduce the maze size to 8x8.) Due to stack size limitations in the operating systems, this solution eschews recursive subroutine calls. Recursion is accomplished by conditional branching within the maze build routine and the use of an array-based stack for data elements.
100 MS=10:REM MAZE SIZE
110 DIM S(MS+1,MS+1):REM SOUTH WALLS
120 DIM W(MS+1,MS+1):REM WEST WALLS
130 DIM V(MS+1,MS+1):REM VISITED CELLS
140 PRINT "INITIALIZING..."
150 GOSUB 260:REM INITIALIZE MAZE
160 PRINT "BUILDING..."
170 DIM PC(MS*MS+1):DIM PR(MS*MS+1):REM STACK
180 REM PICK RANDOM STARTING CELL
190 X = RND(-TI)
200 C=(INT(RND(1)*MS)+1)
210 R=(INT(RND(1)*MS)+1)
220 GOSUB 400:REM BUILD MAZE
230 GOSUB 540:REM DRAW MAZE
240 END
250 REM -----INITIALIZE MAZE-----
260 REM SET WALLS ON AND VISITED CELLS OFF
270 T=MS+1
280 FOR C=0 TO T:FOR R=0 TO T:
290 S(C,R)=1:W(C,R)=1:V(C,R)=0
300 NEXT R:NEXT C
310 REM SET BORDER CELLS TO VISITED
320 FOR C=0 TO T
330 V(C,0)=1:V(C,T)=1
340 NEXT C
350 FOR R=0 TO T
360 V(0,R)=1:V(T,R)=1
370 NEXT R
380 RETURN
390 REM -----BUILD MAZE-----
400 U=U+1:PC(U)=C:PR(U)=R:REM PUSH
410 V(C,R)=1
420 IF V(C,R+1)=1 AND V(C+1,R)=1 AND V(C,R-1)=1 AND V(C-1,R)=1 THEN GOTO 500
430 Z=INT(RND(1)*4)
440 IF Z=0 AND V(C,R+1)=0 THEN S(C,R)=0:R=R+1:GOTO 400
450 IF Z=1 AND V(C+1,R)=0 THEN W(C+1,R)=0:C=C+1:GOTO 400
460 IF Z=2 AND V(C,R-1)=0 THEN S(C,R-1)=0:R=R-1:GOTO 400
470 IF Z=3 AND V(C-1,R)=0 THEN W(C,R)=0:C=C-1:GOTO 400
480 GOTO 430
500 C=PC(U):R=PR(U):U=U-1:REM POP
510 IF U > 0 THEN GOTO 420
520 RETURN
530 REM -----DRAW MAZE-----
540 REM OPEN 4,4:CMD 4:REM SEND OUTPUT TO PRINTER
550 PRINT "+--+--+--+--+--+--+--+--+--+--+"
560 FOR R = 1 TO MS
570 FOR C = 1 TO MS+1
580 IF W(C,R)=0 THEN PRINT " ";
590 IF W(C,R)=1 THEN PRINT ": ";
600 NEXT C
610 PRINT
620 FOR C = 1 TO MS
630 IF S(C,R)=0 THEN PRINT "+ ";
640 IF S(C,R)=1 THEN PRINT "+--";
650 NEXT C
660 PRINT "+"
670 NEXT R
680 REM PRINT#4:CLOSE 4:REM CLOSE PRINTER DEVICE
690 RETURN
- Output example (for 10x10 maze):
+--+--+--+--+--+--+--+--+--+--+ : : : : + + + + +--+ +--+--+--+ + : : : : : : : + + + +--+ +--+ + + +--+ : : : : : : : : + +--+--+ + + +--+ +--+ + : : : : : : : +--+ + +--+--+ + +--+--+ + : : : : : : : + + + + +--+--+ + +--+--+ : : : : : : : + +--+ +--+--+ + + + + + : : : : : : : + +--+ + +--+--+ +--+--+ + : : : : : : : + + +--+ + +--+--+--+ + + : : : : : : : : + +--+ + + + + +--+ + + : : : : : +--+--+--+--+--+--+--+--+--+--+
Common Lisp
The remove-wall function has been written so as to be as close as possible to the specification. The walls are made from a single unicode character, specified by the block keyword, e. g. (maze 20 6 :block #\X). The BOX_DRAWINGS_LIGHT_DIAGONAL_CROSS character is used by default.
(defun shuffle (list) ;; Z not uniform
(sort list '> :key (lambda(x) (random 1.0))))
(defun neighbors (x y maze)
(remove-if-not
(lambda (x-y) (and (< -1 (first x-y) (array-dimension maze 0))
(< -1 (second x-y) (array-dimension maze 1))))
`((,x ,(+ y 2)) (,(- x 2) ,y) (,x ,(- y 2)) (,(+ x 2) ,y))))
(defun remove-wall (maze x y &optional visited)
(labels ((walk (maze x y)
(push (list x y) visited)
(loop for (u v) in (shuffle (neighbors x y maze))
unless (member (list u v) visited :test 'equal)
do (setf (aref maze u v) #\space
(aref maze (/ (+ x u) 2) (/ (+ y v) 2)) #\space)
(walk maze u v))))
(setf (aref maze x y) #\space)
(walk maze x y)))
(defun draw-maze (width height &key (block #\BOX_DRAWINGS_LIGHT_DIAGONAL_CROSS))
(let ((maze (make-array (list (1+ (* 2 height)) (1+ (* 2 width)))
:element-type 'character :initial-element block)))
(remove-wall maze (1+ (* 2 (random height))) (1+ (* 2 (random width))))
(loop for i below (array-dimension maze 0)
do (fresh-line)
(loop for j below (array-dimension maze 1)
do (princ (aref maze i j))))))
(draw-maze 20 6)
- Output:
╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳╳╳╳╳╳╳ ╳ ╳╳╳ ╳ ╳╳╳ ╳ ╳╳╳╳╳ ╳╳╳ ╳ ╳╳╳╳╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳╳╳ ╳ ╳╳╳╳╳ ╳ ╳╳╳ ╳╳╳╳╳╳╳╳╳╳╳ ╳ ╳ ╳╳╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳╳╳ ╳ ╳ ╳╳╳ ╳ ╳ ╳╳╳ ╳╳╳╳╳ ╳ ╳╳╳ ╳ ╳╳╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳╳╳╳╳╳╳ ╳╳╳ ╳╳╳ ╳ ╳╳╳╳╳ ╳╳╳╳╳ ╳╳╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳╳╳╳╳ ╳ ╳ ╳╳╳ ╳╳╳ ╳╳╳╳╳╳╳ ╳ ╳ ╳╳╳╳╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳ ╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳
Another solution using unicode line drawing chars. Assumes they are single width on console. Code pretty horribly unreadable.
(setf *random-state* (make-random-state t))
(defun 2d-array (w h)
(make-array (list h w) :initial-element 0))
(defmacro or-and (v a b c)
`(if (or ,a (and ,b (= 1 ,c))) 0 ,v))
(defun make-maze (w h)
(let ((vis (2d-array w h))
(ver (2d-array w h))
(hor (2d-array w h)))
(labels
((walk (y x)
(setf (aref vis y x) 1)
(loop
(let (x2 y2)
(loop for (dx dy) in '((-1 0) (1 0) (0 -1) (0 1))
with cnt = 0 do
(let ((xx (+ x dx))
(yy (+ y dy)))
(if (and (array-in-bounds-p vis yy xx)
(zerop (aref vis yy xx))
(zerop (random (incf cnt))))
(setf x2 xx y2 yy))))
(if (not x2) (return-from walk))
(if (= x x2)
(setf (aref hor (min y y2) x) 1)
(setf (aref ver y (min x x2)) 1))
(walk y2 x2))))
(show ()
(let ((g " │││─┘┐┤─└┌├─┴┬┼"))
(loop for i from 0 to h do
(loop for j from 0 to w do
(format t "~c~a"
(char g
(+ (or-and 1 (= i 0) (> j 0) (aref ver (1- i) (1- j)))
(or-and 2 (= i h) (> j 0) (aref ver i (1- j)))
(or-and 4 (= j 0) (> i 0) (aref hor (1- i) (1- j)))
(or-and 8 (= j w) (> i 0) (aref hor (1- i) j ))))
(if (and (< j w)
(or (= i 0)
(= 0 (aref hor (1- i) j))))
"───" " ")))
(terpri)
(when (< i h)
(loop for j from 0 below w do
(format t (if (or (= j 0)
(= 0 (aref ver i (1- j))))
"│ " " ")))
(format t "│~%"))))))
(walk (random h) (random w))
(show))))
(make-maze 20 20)
- Output:
┼───┴───┼───┴───┴───┼───┴───┴───┼ │ │ │ │ ┼──── │ │ │ │ ┌───┐ ├ │ │ │ │ │ │ │ │ ┤ ┌───┘ │ │ │ │ │ ├ │ │ │ │ │ │ │ ┤ │ ┌───┘ ├───────┤ │ ├ │ │ │ │ │ │ ┤ │ │ ────┤ │ │ ────┼ │ │ │ │ │ │ ┤ ────┼───┐ │ │ └───┐ ├ │ │ │ │ │ │ ┼───┐ │ └───────┼───┐ └───┼ │ │ │ │ │ ┤ └──────────── │ └───┐ ├ │ │ │ ┼───┬───┬───┬───┬───┬───┬───┼───┼
D
void main() @safe {
import std.stdio, std.algorithm, std.range, std.random;
enum uint w = 14, h = 10;
auto vis = new bool[][](h, w),
hor = iota(h + 1).map!(_ => ["+---"].replicate(w)).array,
ver = h.iota.map!(_ => ["| "].replicate(w) ~ "|").array;
void walk(in uint x, in uint y) /*nothrow*/ @safe /*@nogc*/ {
vis[y][x] = true;
//foreach (immutable p; [[x-1,y], [x,y+1], [x+1,y], [x,y-1]].randomCover) {
foreach (const p; [[x-1, y], [x, y+1], [x+1, y], [x, y-1]].randomCover) {
if (p[0] >= w || p[1] >= h || vis[p[1]][p[0]]) continue;
if (p[0] == x) hor[max(y, p[1])][x] = "+ ";
if (p[1] == y) ver[y][max(x, p[0])] = " ";
walk(p[0], p[1]);
}
}
walk(uniform(0, w), uniform(0, h));
foreach (const a, const b; hor.zip(ver ~ []))
join(a ~ "+\n" ~ b).writeln;
}
- Output:
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | | | | | + + +---+---+ + +---+ + +---+---+---+ + + | | | | | | | | +---+---+---+---+---+---+---+ +---+ +---+ + +---+ | | | | | | + +---+---+---+ + + +---+ +---+---+ +---+---+ | | | | | | | | | +---+ + + + + +---+---+---+ + +---+ + + | | | | | | | | | + +---+ + +---+---+ + +---+---+---+ +---+ + | | | | | | + +---+---+ + +---+---+---+---+---+ +---+---+ + | | | | | | | | +---+ + +---+---+ + +---+ + + +---+---+ + | | | | | | | | + +---+---+---+ + +---+ +---+ + + +---+---+ | | | | | | | | | + + +---+---+---+---+ +---+ + +---+---+ + + | | | | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Delphi
program MazeGen_Rosetta;
{$APPTYPE CONSOLE}
uses System.SysUtils, System.Types, System.Generics.Collections, System.IOUtils;
type
TMCell = record
Visited : Boolean;
PassTop : Boolean;
PassLeft : Boolean;
end;
TMaze = array of array of TMCell;
TRoute = TStack<TPoint>;
const
mwidth = 24;
mheight = 14;
procedure ClearVisited(var AMaze: TMaze);
var
x, y: Integer;
begin
for y := 0 to mheight - 1 do
for x := 0 to mwidth - 1 do
AMaze[x, y].Visited := False;
end;
procedure PrepareMaze(var AMaze: TMaze);
var
Route : TRoute;
Position : TPoint;
d : Integer;
Pool : array of TPoint; // Pool of directions to pick randomly from
begin
SetLength(AMaze, mwidth, mheight);
ClearVisited(AMaze);
Position := Point(Random(mwidth), Random(mheight));
Route := TStack<TPoint>.Create;
try
with Position do
while True do
begin
repeat
SetLength(Pool, 0);
if (y > 0) and not AMaze[x, y-1].Visited then Pool := Pool + [Point(0, -1)];
if (x < mwidth-1) and not AMaze[x+1, y].Visited then Pool := Pool + [Point(1, 0)];
if (y < mheight-1) and not AMaze[x, y+1].Visited then Pool := Pool + [Point(0, 1)];
if (x > 0) and not AMaze[x-1, y].Visited then Pool := Pool + [Point(-1, 0)];
if Length(Pool) = 0 then // no direction to draw from
begin
if Route.Count = 0 then Exit; // and we are back at start so this is the end
Position := Route.Pop;
end;
until Length(Pool) > 0;
d := Random(Length(Pool));
Offset(Pool[d]);
AMaze[x, y].Visited := True;
if Pool[d].y = -1 then AMaze[x, y+1].PassTop := True; // comes from down to up ( ^ )
if Pool[d].x = 1 then AMaze[x, y].PassLeft := True; // comes from left to right ( --> )
if Pool[d].y = 1 then AMaze[x, y].PassTop := True; // comes from left to right ( v )
if Pool[d].x = -1 then AMaze[x+1, y].PassLeft := True; // comes from right to left ( <-- )
Route.Push(Position);
end;
finally
Route.Free;
end;
end;
function MazeToString(const AMaze: TMaze; const S, E: TPoint): String; overload;
var
x, y: Integer;
v : Char;
begin
Result := '';
for y := 0 to mheight - 1 do
begin
for x := 0 to mwidth - 1 do
if AMaze[x, y].PassTop then Result := Result + '+'#32#32#32 else Result := Result + '+---';
Result := Result + '+' + sLineBreak;
for x := 0 to mwidth - 1 do
begin
if S = Point(x, y) then v := 'S' else
if E = Point(x, y) then v := 'E' else
v := #32'*'[Ord(AMaze[x, y].Visited) + 1];
Result := Result + '|'#32[Ord(AMaze[x, y].PassLeft) + 1] + #32 + v + #32;
end;
Result := Result + '|' + sLineBreak;
end;
for x := 0 to mwidth - 1 do Result := Result + '+---';
Result := Result + '+' + sLineBreak;
end;
procedure Main;
var
Maze: TMaze;
begin
Randomize;
PrepareMaze(Maze);
ClearVisited(Maze); // show no route
Write(MazeToString(Maze, Point(-1, -1), Point(-1, -1)));
ReadLn;
end;
begin
Main;
end.
- Output:
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | | | | | | | | | + + +---+---+ + + +---+---+---+---+---+ + + +---+---+---+ +---+ + +---+---+ + + +---+ +---+---+ + | | | | | | | | | | | | | | + +---+ +---+---+---+---+---+---+---+---+---+---+---+ + +---+---+---+ +---+---+ +---+---+ + +---+---+ +---+---+ | | | | | | | | | | | | | | + +---+---+ + + + +---+ +---+ + + + +---+---+ +---+---+ + + + +---+---+---+ +---+ + + + + | | | | | | | | | | | | | | | | | | | | +---+ +---+ + +---+ +---+---+---+---+---+ +---+---+ +---+---+ +---+---+ +---+ +---+ +---+---+ + + + + | | | | | | | | | | | | | | | | | | + +---+ +---+---+ +---+---+ + +---+ + + + +---+ +---+---+ + + + +---+ +---+ + + +---+---+ + | | | | | | | | | | | | | | | | | | +---+---+---+ + +---+---+---+---+---+ +---+---+ +---+ + +---+ +---+ +---+---+ +---+---+---+ + + + + + | | | | | | | | | | | | | | + +---+ +---+---+ + +---+---+ + + + +---+ +---+ + +---+ +---+ +---+---+ + +---+---+---+ +---+---+ | | | | | | | | | | | | | | | | + + +---+---+ + + + +---+---+---+ +---+---+---+ +---+---+ + +---+---+---+ +---+---+ +---+---+---+ +---+ | | | | | | | | | | | | | | | | +---+ + + +---+ + +---+ +---+ +---+---+ +---+---+ + + +---+---+---+---+---+ + +---+---+ + +---+ + | | | | | | | | | | | | | | | + +---+ + + +---+---+ + + +---+ +---+---+---+---+---+---+---+ +---+---+---+ +---+---+---+---+---+---+---+ + | | | | | | | | | | | | | | | | + + + + + + + + + +---+ +---+---+ + +---+ + + + +---+ + +---+---+---+ +---+---+ +---+---+ | | | | | | | | | | | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
EasyLang
size = 15
n = 2 * size + 1
f = 100 / (n - 0.5)
len m[] n * n
#
background 000
proc show_maze . .
clear
for i = 1 to len m[]
if m[i] = 0
x = (i - 1) mod n
y = (i - 1) div n
color 999
move x * f - f / 2 y * f - f / 2
rect f * 1.5 f * 1.5
.
.
sleep 0.01
.
offs[] = [ 1 n -1 (-n) ]
#
proc m_maze pos . .
m[pos] = 0
show_maze
d[] = [ 1 2 3 4 ]
for i = 4 downto 1
d = random i
dir = offs[d[d]]
d[d] = d[i]
if m[pos + dir] = 1 and m[pos + 2 * dir] = 1
m[pos + dir] = 0
m_maze pos + 2 * dir
.
.
.
endpos = n * n - 1
proc make_maze . .
for i = 1 to len m[]
m[i] = 1
.
for i = 1 to n
m[i] = 2
m[n * i] = 2
m[n * i - n + 1] = 2
m[n * n - n + i] = 2
.
h = 2 * random 15 - n + n * 2 * random 15
m_maze h
m[endpos] = 0
endpos += n
.
make_maze
show_maze
EDSAC order code
In this EDSAC solution there is no recursion or stack. As suggested in the Wikipedia article "Maze generation algorithm", backtracking information is stored in the maze itself. The code below leaves enough storage for a 24x24 maze, but the demo maze is cut down to 12x8 to save space.
[Maze generation for Rosetta Code.
EDSAC program, Initial Orders 2.
Cells, horizontal walls, and vertical walls are numbered
as shown in this example:
+---1---+---2---+---3---+
| | | |
1 1 2 2 3 3 4 N
| | | | |
+---5---+---6---+---7---+ W---+---E
| | | | |
5 5 6 6 7 7 8 S
| | | |
+---9---+--10---+--11---+
Maze data are held in a single 1-based array of 17-bit values
(equivalent to an array of records in a high-level language).
In each entry, fields for cells and walls are as shown in "Flags" below.]
[Arrange the storage]
T51K P56F [G parameter: generator for pseudo-random numbers]
T47K P100F [M parameter: main routine + dependent subroutines]
T45K P398F [H parameter: storage for maze]
[The following once-only code can be overwritten by the maze data.]
T46K P398F [N parameter: library subroutine R4 to read data]
T50K P420F [X parameter: code executed at start-up only]
[=========== Executed at start-up, then overwritten ================]
E25K TX GK
[Enter with acc = 0]
[0] A@ GN [read 35-bit maze width into 0D]
AF TM [load and store width, assuming high word = 0]
[4] A4@ GN AF T1M [same for height]
[Initialize linear congruential generator for pseudo-random numbers]
[8] A8@ GN [read seed for LCG into 0D]
AD T4D [pass seed to LCG in 4D]
[12] A12@ GG [initialize LCG]
[Choose a random cell in the maze, for use later.]
[Also update storage of width and height.]
T4D [clear the whole of 4D, including sandwich bit]
AM U4F [load 17-bit width, pass to LCG as 35-bit value]
LD A2F TM [width to address field, add 1, store]
[20] A20@ G1G [call LCG, 0D := random in 0..(width - 1)]
AF T3M [save random to temporary store]
T4D A1M U4F [pass height of maze to LCG]
LD A2F T1M [height to address field, add 1, store]
[30] A30@ G1G [call LCG, 0D := random in 0..(height - 1)]
HF VM L64F L32F [acc := random*(width + 1)]
A3M LD A2F [add first random, shift to address, add 1]
T3M [save random index for use below]
HM V1M L64F L32F T2M [store (width+1)*(height+1)]
E65M [jump to main routine with acc = 0]
[================ Main routine ====================]
E25K TM GK
[Variables]
[0] PF [initially maze width; then (width + 1) in address field]
[1] PF [initially maze height; then (height + 1) in address field]
[List of up to four E orders to move N, S, W, E.]
[The first two are also used as temporary store at program start]
[2] PF
[3] PF PF PF
[6] TF [T order to store E order in list]
[Constants]
[7] T2@ [initial value of T order]
[8] TH [order to store into array{0}]
[9] AH [order to load from array{0}]
[10] C1H [order to collate with array{1};]
[also teleprinter colon in figures mode]
[11] MF [add to T order to make A order with same address]
[12] LF [add to T order to make C order with same address]
[Flags]
[13] K4096F [horizontal wall deleted, 10000 hex]
[14] IF [vertical wall deleted, 8000 hex]
[15] RF [no north neighbour, 4000 hex]
[16] WF [no south neighbour, 2000 hex]
[17] QF [no west neighbour, 1000 hex]
[18] P1024F [no east neighbour, 0800 hex]
[19] PD [cell visited, 0001 hex]
[20] V2047F [mask to clear visited bit]
[21] P1023F [mask to select EDSAC address field, which contains
index of previous cell for backtracking (0 if none).
[Teleprinter]
[22] #F [set figures mode]
[23] !F [space]
[24] @F [carriage return]
[25] &F [line feed]
[Subroutine called to set flag "no north neighbour" in cells along north edge,
similarly for east, south and west edges (order must be N, E, S, W).
Input: 4F = negative count of cells
5F = step in array index
6F = flag to be set]
[26] A3F T42@ [plant return link as usual]
A36@
G34@ [jump into middle of loop (since A < 0)]
[30] T4F [loop: update megative count]
A36@ A5F U36@
[34] S11@ T38@
[The following order initially refers to the NW corner cell.
First call leaves it referring to NE corner; second call to SE corner, etc.
Hence the need for calls to be in the order N, E, S, W.]
[36] A1H [planted; loaded as A1H]
A6F
[38] TF
A4F A2F [add 1 to negative count]
G30@ [if not yet 0, loop back]
[42] ZF
[Subroutine to test for unvisited neighbour of current cell in a given direction.
Input: 4F = E order to be added to list if unvisited neighbour is found
5F = step in array index to reach neighbour
6F = flag to test for no neighbour in this direction]
[43] A3F T64@ [plant return link as usual]
S6F H6F CH [if no neighbour then acc = 0, else acc < 0]
E64@ [exit if no neighbour]
TF A118@
A5F T55@
S19@ H19@
[55] CF E64@
TF A6@ U63@
A2F T6@
A4F [load jump to execute move]
[63] TF [store in list of moves]
[64] ZF [(planted) jump back to caller]
[Jump to here from once-only code, with acc = 0]
[Clear maze array]
[65] S2@
[66] TF [use 0F as loop counter]
[67] TH [planted, loaded as TH]
A67@ A2F T67@
AF A2F G66@
[Set flag "no north neighbour" in cells along northern edge]
S@ A2F T4F [count = width, pass in 4F]
A2F T5F [step in array index = 1, pass in 5F]
A15@ T6F [pass flag in 6F]
[81] A81@ G26@ [call subroutine to set flag]
[Repeat for east, south, west edges (must be in that order)]
S1@ A2F T4F A@ T5F A18@ T6F
[90] A90@ G26@
S@ A2F T4F S2F T5F A16@ T6F
[99] A99@ G26@
S1@ A2F T4F S@ T5F A17@ T6F
[108] A108@ G26@
[Start with the random cell chosen at program start (X parameter)]
A3@ A8@
[Loop: here acc = T order for current cell]
[112] U121@ [plant T order]
A12@ T118@ [make and plant C order, same address]
[Initialize storing in list of moves]
A7@ T6@
H20@
[118] CF A19@ [mark cell as visited]
UH [store flags of current cell in array{0} for easy access]
[121] TF [and also in the body of the array]
[If cell has unvisited North neighbour, add North to list of possible moves]
A177@ T4F
S@ T5F
A15@ T6F
[128] A128@ G43@
[Repeat for South, West, East neighbours]
A178@ T4F
A@ T5F
A16@ T6F
[136] A136@ G43@
A179@ T4F
S2F T5F
A17@ T6F
[144] A144@ G43@
A180@ T4F
A2F T5F
A18@ T6F
[152] A152@ G43@
[List now has 0..4 possible moves. If more than one, choose randomly.]
T4D [clear whole of 4D, including sandwich bit, for randomizer]
A6@ S7@ [address field := count of moves]
S2F G225@ [jump if none]
S2F G169@ [jump if one only]
RD A2F T4F [pass count, right-justified, to randomizer]
[164] A164@ G1G [0F := random value 0..(count - 1)]
AF LD E170@
[169] TF [only one move, must be at list{0}]
[170] A7@ A11@ T173@
[173] AF T176@
A121@ [common to all moves]
[176] EF [jump to move N,S,E, or W with acc = 0]
[177] E181@
[178] E190@
[179] E199@
[180] E208@
[Move North and delete horizontal wall]
[181] U186@ A11@ T184@
[184] AF A13@
[186] TF A121@ S@ E216@
[Move South and delete horizontal wall]
[190] A@ U196@ A11@ T194@
[194] AF A13@
[196] TF A196@ E216@
[Move West and delete vertical wall]
[199] U204@ A11@ T202@
[202] AF A14@
[204] TF A121@ S2F E216@
[Move East and delete vertical wall]
[208] A2F U214@ A11@ T212@
[212] AF A14@
[214] TF A214@
[fall through]
[Set index of current cell as previous to the new cell.
Here with T order for new cell in acc.]
[216] U222@ A11@ T221@
A121@ S8@
[221] AF
[222] TF
A222@ E112@
[No unvisited neighbour, backtrack if possible]
[225] TF [clear acc, needed]
H21@ CH [get index of previous cell (in address field)]
S2F [is it 0?]
G233@ [if so, maze is complete, jump to print]
A2F [restore]
A8@ [make T order]
E112@
[Print the maze created above]
[233] O22@ [set teleprinter to figures mode]
TF [clear acc]
S1@ T5F
[Outer 'loop and a half' with 5F as negative counter.
h + 1 rows for horizontal walls plus h rows for vertical walls]
[237]
[Print row for horizontal walls]
O296@ [print leading + sign]
S@ A2F [set inner loop counter in 4F]
H13@
[241] T4F [4F := negative count of horizontal walls per row]
S13@ [has current horizontal wall been deleted?]
[243] C1H [planted; loaded as C1H]
G247@ [jump if not]
A23@ G249@
[247] T1F [clear acc]
[248] A248@ [load hyphen (since A = minus in figures mode)]
[249] TF OF OF OF [print 3 spaces or 3 minus]
O296@ [print plus sign]
A243@ A2F T243@ [inc address in C order]
A4F A2F G241@
[Here with acc = 0 after printing one row]
A243@ A2F T243@ [skip next element in array]
O24@ O25@ [print CR, LF]
A5F A2F E295@
T5F
[Print row for vertical walls]
S@
T4F
H14@
[272] S14@
[273] C1H [planted; loaded as C1H]
G277@
A23@ G279@
[277] T1F [clear acc]
A10@ [colon in figures mode]
[279] TF OF [print colon or space]
A273@ A2F T273@ [update C order]
A4F A2F [inc negative counter for inner loop]
E292@ [jump out of loop if counter = 0]
T4F [update counter]
O23@ O23@ O23@ [print 3 spaces]
E272@
[Exit from inner loop for vertical walls]
[292] O24@ O25@ [print CR, LF]
E237@
[Exit]
[295] O22@ [dummy character to flush print buffer]
[296] ZF [(1) halt program (2) plus sign in figures mode]
[==================== Generator for pseudo-random numbers ===========]
[Linear congruential generator, same algorithm as Delphi 7 LCG
38 locations]
E25K TG
GKG10@G15@T2#ZPFT2ZI514DP257FT4#ZPFT4ZPDPFT6#ZPFT6ZPFRFA6#@S4#@
T6#@E25FE8ZPFT8ZPFPFA3FT14@A4DT8#@ZFA3FT37@H2#@V8#@L512FL512F
L1024FA4#@T8#@H6#@C8#@T8#@S4DG32@TDA8#@E35@H4DTDV8#@L1FTDZF
[==================== LIBRARY SUBROUTINE ============================]
E25K TN
[R4 Input of one signed integer.
22 storage locations; working positions 4, 5, and 6.]
GKA3FT21@T4DH6@E11@P5DJFT6FVDL4FA4DTDI4FA4FS5@G7@S5@G20@SDTDT6FEF
[===================================================================]
[The following, without the comments and white space, might have
been input from a separate tape.]
E25K TX GK
EZ [define entry point]
PF [acc = 0 on entry]
[Integers supplied by user: maze width, maze height, seed for LCG.
To be read by library subroutine R4; sign comes after value.]
12+8+987654321+
- Output:
+---+---+---+---+---+---+---+---+---+---+---+---+ : : : : : + +---+ +---+---+ +---+---+ + +---+ + : : : : : : + + +---+ +---+---+ +---+---+---+---+ + : : : : : : : + + +---+---+ + +---+ +---+ + +---+ : : : : : : : : +---+---+ + +---+---+ +---+ + +---+ + : : : : : : + +---+---+---+ + +---+---+---+---+ + + : : : : : : : + + +---+ +---+---+---+---+ + +---+ + : : : : : : + +---+ +---+---+---+---+ +---+---+---+ + : : : +---+---+---+---+---+---+---+---+---+---+---+---+
EGL
program MazeGen
// First and last columns/rows are "dead" cells. Makes generating
// a maze with border walls much easier. Therefore, a visible
// 20x20 maze has a maze size of 22.
mazeSize int = 22;
south boolean[][];
west boolean[][];
visited boolean[][];
function main()
initMaze();
generateMaze();
drawMaze();
end
private function initMaze()
visited = createBooleanArray(mazeSize, mazeSize, false);
// Initialize border cells as already visited
for(col int from 1 to mazeSize)
visited[col][1] = true;
visited[col][mazeSize] = true;
end
for(row int from 1 to mazeSize)
visited[1][row] = true;
visited[mazeSize][row] = true;
end
// Initialize all walls as present
south = createBooleanArray(mazeSize, mazeSize, true);
west = createBooleanArray(mazeSize, mazeSize, true);
end
private function createBooleanArray(col int in, row int in, initialState boolean in) returns(boolean[][])
newArray boolean[][] = new boolean[0][0];
for(i int from 1 to col)
innerArray boolean[] = new boolean[0];
for(j int from 1 to row)
innerArray.appendElement(initialState);
end
newArray.appendElement(innerArray);
end
return(newArray);
end
private function createIntegerArray(col int in, row int in, initialValue int in) returns(int[][])
newArray int[][] = new int[0][0];
for(i int from 1 to col)
innerArray int[] = new int[0];
for(j int from 1 to row)
innerArray.appendElement(initialValue);
end
newArray.appendElement(innerArray);
end
return(newArray);
end
private function generate(col int in, row int in)
// Mark cell as visited
visited[col][row] = true;
// Keep going as long as there is an unvisited neighbor
while(!visited[col][row + 1] || !visited[col + 1][row] ||
!visited[col][row - 1] || !visited[col - 1][row])
while(true)
r float = MathLib.random(); // Choose a random direction
case
when(r < 0.25 && !visited[col][row + 1]) // Go south
south[col][row] = false; // South wall down
generate(col, row + 1);
exit while;
when(r >= 0.25 && r < 0.50 && !visited[col + 1][row]) // Go east
west[col + 1][row] = false; // West wall of neighbor to the east down
generate(col + 1, row);
exit while;
when(r >= 0.5 && r < 0.75 && !visited[col][row - 1]) // Go north
south[col][row - 1] = false; // South wall of neighbor to the north down
generate(col, row - 1);
exit while;
when(r >= 0.75 && r < 1.00 && !visited[col - 1][row]) // Go west
west[col][row] = false; // West wall down
generate(col - 1, row);
exit while;
end
end
end
end
private function generateMaze()
// Pick random start position (within the visible maze space)
randomStartCol int = MathLib.floor((MathLib.random() *(mazeSize - 2)) + 2);
randomStartRow int = MathLib.floor((MathLib.random() *(mazeSize - 2)) + 2);
generate(randomStartCol, randomStartRow);
end
private function drawMaze()
line string;
// Iterate over wall arrays (skipping dead border cells as required).
// Construct a line at a time and output to console.
for(row int from 1 to mazeSize - 1)
if(row > 1)
line = "";
for(col int from 2 to mazeSize)
if(west[col][row])
line ::= "| ";
else
line ::= " ";
end
end
Syslib.writeStdout(line);
end
line = "";
for(col int from 2 to mazeSize - 1)
if(south[col][row])
line ::= "+---";
else
line ::= "+ ";
end
end
line ::= "+";
SysLib.writeStdout(line);
end
end
end
- Output example (for 10x10 maze):
+---+---+---+---+---+---+---+---+---+---+ | | | | | + + +---+---+---+ +---+ + + + | | | | | | | | + +---+ + + + + + +---+ + | | | | | | | | + + +---+ + +---+ + + +---+ | | | | | | | | + +---+---+ + + +---+ +---+---+ | | | | | | | + + +---+---+ + + + + + + | | | | | | | | | + + + + +---+ + +---+---+ + | | | | | | | + +---+ +---+---+---+ + + + + | | | | | | + +---+ +---+ + +---+ +---+ + | | | | | | | +---+ +---+ +---+---+---+---+ + + | | | +---+---+---+---+---+---+---+---+---+---+
Elixir
defmodule Maze do
def generate(w, h) do
maze = (for i <- 1..w, j <- 1..h, into: Map.new, do: {{:vis, i, j}, true})
|> walk(:rand.uniform(w), :rand.uniform(h))
print(maze, w, h)
maze
end
defp walk(map, x, y) do
Enum.shuffle( [[x-1,y], [x,y+1], [x+1,y], [x,y-1]] )
|> Enum.reduce(Map.put(map, {:vis, x, y}, false), fn [i,j],acc ->
if acc[{:vis, i, j}] do
{k, v} = if i == x, do: {{:hor, x, max(y, j)}, "+ "},
else: {{:ver, max(x, i), y}, " "}
walk(Map.put(acc, k, v), i, j)
else
acc
end
end)
end
defp print(map, w, h) do
Enum.each(1..h, fn j ->
IO.puts Enum.map_join(1..w, fn i -> Map.get(map, {:hor, i, j}, "+---") end) <> "+"
IO.puts Enum.map_join(1..w, fn i -> Map.get(map, {:ver, i, j}, "| ") end) <> "|"
end)
IO.puts String.duplicate("+---", w) <> "+"
end
end
Maze.generate(20, 10)
- Output:
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | | | | | | + +---+ + +---+ +---+ +---+---+---+---+ + + +---+---+ + +---+ | | | | | | | | | | | + + +---+---+ +---+---+---+---+ +---+---+ + + + + + + + + | | | | | | | | | | | | | | | | + + + + +---+---+ + + +---+ + + +---+ + +---+ + + + | | | | | | | | | | | | + + + + +---+---+---+---+---+---+---+ +---+---+---+ +---+---+---+ + | | | | | | | | | | | + + +---+---+---+---+ + + + + +---+ + + + + +---+ + + | | | | | | | | | | | | | + +---+---+---+---+ + + + + +---+ + + +---+---+---+ +---+ + | | | | | | | | | | | | + +---+---+---+---+ + +---+---+ +---+---+ + + + + +---+ +---+ | | | | | | | | | | | | + +---+ + +---+---+---+---+ +---+ +---+---+---+ + +---+ + + + | | | | | | | | | | | | + + + +---+ + +---+---+ + +---+---+---+---+---+---+ + + + + | | | | | | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Elm
import Maybe as M
import Result as R
import Matrix
import Mouse
import Random exposing (Seed)
import Matrix.Random
import Time exposing (Time, every, second)
import Set exposing (Set, fromList)
import List exposing (..)
import String exposing (join)
import Html exposing (Html, br, input, h1, h2, text, div, button)
import Html.Events as HE
import Html.Attributes as HA
import Html.App exposing (program)
import Json.Decode as JD
import Svg
import Svg.Attributes exposing (version, viewBox, cx, cy, r, x, y, x1, y1, x2, y2, fill,points, style, width, height, preserveAspectRatio)
minSide = 10
maxSide = 40
w = 700
h = 700
dt = 0.001
type alias Direction = Int
down = 0
right = 1
type alias Door = (Matrix.Location, Direction)
type State = Initial | Generating | Generated | Solved
type alias Model =
{ rows : Int
, cols : Int
, animate : Bool
, boxes : Matrix.Matrix Bool
, doors : Set Door
, current : List Matrix.Location
, state : State
, seedStarter : Int
, seed : Seed
}
initdoors : Int -> Int -> Set Door
initdoors rows cols =
let
pairs la lb = List.concatMap (\at -> List.map ((,) at) lb) la
downs = pairs (pairs [0..rows-2] [0..cols-1]) [down]
rights = pairs (pairs [0..rows-1] [0..cols-2]) [right]
in downs ++ rights |> fromList
initModel : Int -> Int -> Bool -> State -> Int -> Model
initModel rows cols animate state starter =
let rowGenerator = Random.int 0 (rows-1)
colGenerator = Random.int 0 (cols-1)
locationGenerator = Random.pair rowGenerator colGenerator
(c, s)= Random.step locationGenerator (Random.initialSeed starter)
in { rows = rows
, cols = cols
, animate = animate
, boxes = Matrix.matrix rows cols (\location -> state == Generating && location == c)
, doors = initdoors rows cols
, current = if state == Generating then [c] else []
, state = state
, seedStarter = starter -- updated every Tick until maze generated.
, seed = s
}
view model =
let
borderLineStyle = style "stroke:green;stroke-width:0.3"
wallLineStyle = style "stroke:green;stroke-width:0.1"
x1Min = x1 <| toString 0
y1Min = y1 <| toString 0
x1Max = x1 <| toString model.cols
y1Max = y1 <| toString model.rows
x2Min = x2 <| toString 0
y2Min = y2 <| toString 0
x2Max = x2 <| toString model.cols
y2Max = y2 <| toString model.rows
borders = [ Svg.line [ x1Min, y1Min, x2Max, y2Min, borderLineStyle ] []
, Svg.line [ x1Max, y1Min, x2Max, y2Max, borderLineStyle ] []
, Svg.line [ x1Max, y1Max, x2Min, y2Max, borderLineStyle ] []
, Svg.line [ x1Min, y1Max, x2Min, y2Min, borderLineStyle ] []
]
doorToLine door =
let (deltaX1, deltaY1) = if (snd door == right) then (1,0) else (0,1)
(row, column) = fst door
in Svg.line [ x1 <| toString (column + deltaX1)
, y1 <| toString (row + deltaY1)
, x2 <| toString (column + 1)
, y2 <| toString (row + 1)
, wallLineStyle ] []
doors = (List.map doorToLine <| Set.toList model.doors )
circleInBox (row,col) color =
Svg.circle [ r "0.25"
, fill (color)
, cx (toString (toFloat col + 0.5))
, cy (toString (toFloat row + 0.5))
] []
showUnvisited location box =
if box then [] else [ circleInBox location "yellow" ]
unvisited = model.boxes
|> Matrix.mapWithLocation showUnvisited
|> Matrix.flatten
|> concat
current =
case head model.current of
Nothing -> []
Just c -> [circleInBox c "black"]
maze =
if model.animate || model.state /= Generating
then [ Svg.g [] <| doors ++ borders ++ unvisited ++ current ]
else [ Svg.g [] <| borders ]
in
div
[]
[ h2 [centerTitle] [text "Maze Generator"]
, div
[floatLeft]
( slider "rows" minSide maxSide model.rows SetRows
++ [ br [] [] ]
++ slider "cols" minSide maxSide model.cols SetCols
++ [ br [] [] ]
++ checkbox "Animate" model.animate SetAnimate
++ [ br [] [] ]
++ [ button
[ HE.onClick Generate ]
[ text "Generate"]
] )
, div
[floatLeft]
[ Svg.svg
[ version "1.1"
, width (toString w)
, height (toString h)
, viewBox (join " "
[ 0 |> toString
, 0 |> toString
, model.cols |> toString
, model.rows |> toString ])
]
maze
]
]
checkbox label checked msg =
[ input
[ HA.type' "checkbox"
, HA.checked checked
, HE.on "change" (JD.map msg HE.targetChecked)
]
[]
, text label
]
slider name min max current msg =
[ input
[ HA.value (if current >= min then current |> toString else "")
, HE.on "input" (JD.map msg HE.targetValue )
, HA.type' "range"
, HA.min <| toString min
, HA.max <| toString max
]
[]
, text <| name ++ "=" ++ (current |> toString)
]
floatLeft = HA.style [ ("float", "left") ]
centerTitle = HA.style [ ( "text-align", "center") ]
unvisitedNeighbors : Model -> Matrix.Location -> List Matrix.Location
unvisitedNeighbors model (row,col) =
[(row, col-1), (row-1, col), (row, col+1), (row+1, col)]
|> List.filter (\l -> fst l >= 0 && snd l >= 0 && fst l < model.rows && snd l < model.cols)
|> List.filter (\l -> (Matrix.get l model.boxes) |> M.withDefault False |> not)
updateModel' : Model -> Int -> Model
updateModel' model t =
case head model.current of
Nothing -> {model | state = Generated, seedStarter = t }
Just prev ->
let neighbors = unvisitedNeighbors model prev
in if (length neighbors) > 0 then
let (neighborIndex, seed) = Random.step (Random.int 0 (length neighbors-1)) model.seed
next = head (drop neighborIndex neighbors) |> M.withDefault (0,0)
boxes = Matrix.set next True model.boxes
dir = if fst prev == fst next then right else down
doorCell = if ( (dir == down) && (fst prev < fst next))
|| (dir == right ) && (snd prev < snd next) then prev else next
doors = Set.remove (doorCell, dir) model.doors
in {model | boxes=boxes, doors=doors, current=next :: model.current, seed=seed, seedStarter = t}
else
let tailCurrent = tail model.current |> M.withDefault []
in updateModel' {model | current = tailCurrent} t
updateModel : Msg -> Model -> Model
updateModel msg model =
let stringToCellCount s =
let v' = String.toInt s |> R.withDefault minSide
in if v' < minSide then minSide else v'
in case msg of
Tick tf ->
let t = truncate tf
in
if (model.state == Generating) then updateModel' model t
else { model | seedStarter = t }
Generate ->
initModel model.rows model.cols model.animate Generating model.seedStarter
SetRows countString ->
initModel (stringToCellCount countString) model.cols model.animate Initial model.seedStarter
SetCols countString ->
initModel model.rows (stringToCellCount countString) model.animate Initial model.seedStarter
SetAnimate b ->
{ model | animate = b }
NoOp -> model
type Msg = NoOp | Tick Time | Generate | SetRows String | SetCols String | SetAnimate Bool
subscriptions model = every (dt * second) Tick
main =
let
update msg model = (updateModel msg model, Cmd.none)
init = (initModel 21 36 False Initial 0, Cmd.none)
in program
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}
Link to live demo: http://dc25.github.io/mazeGenerationElm/
Emacs Lisp
(require 'cl-lib)
(cl-defstruct maze rows cols data)
(defmacro maze-pt (w r c)
`(+ (* (mod ,r (maze-rows ,w)) (maze-cols ,w))
(mod ,c (maze-cols ,w))))
(defmacro maze-ref (w r c)
`(aref (maze-data ,w) (maze-pt ,w ,r ,c)))
(defun new-maze (rows cols)
(setq rows (1+ rows)
cols (1+ cols))
(let ((m (make-maze :rows rows :cols cols :data (make-vector (* rows cols) nil))))
(dotimes (r rows)
(dotimes (c cols)
(setf (maze-ref m r c) (copy-sequence '(wall ceiling)))))
(dotimes (r rows)
(maze-set m r (1- cols) 'visited))
(dotimes (c cols)
(maze-set m (1- rows) c 'visited))
(maze-unset m 0 0 'ceiling) ;; Maze Entrance
(maze-unset m (1- rows) (- cols 2) 'ceiling) ;; Maze Exit
m))
(defun maze-is-set (maze r c v)
(member v (maze-ref maze r c)))
(defun maze-set (maze r c v)
(let ((cell (maze-ref maze r c)))
(when (not (member v cell))
(setf (maze-ref maze r c) (cons v cell)))))
(defun maze-unset (maze r c v)
(setf (maze-ref maze r c) (delete v (maze-ref maze r c))))
(defun print-maze (maze &optional marks)
(dotimes (r (1- (maze-rows maze)))
(dotimes (c (1- (maze-cols maze)))
(princ (if (maze-is-set maze r c 'ceiling) "+---" "+ ")))
(princ "+")
(terpri)
(dotimes (c (1- (maze-cols maze)))
(princ (if (maze-is-set maze r c 'wall) "|" " "))
(princ (if (member (cons r c) marks) " * " " ")))
(princ "|")
(terpri))
(dotimes (c (1- (maze-cols maze)))
(princ (if (maze-is-set maze (1- (maze-rows maze)) c 'ceiling) "+---" "+ ")))
(princ "+")
(terpri))
(defun shuffle (lst)
(sort lst (lambda (a b) (= 1 (random 2)))))
(defun to-visit (maze row col)
(let (unvisited)
(dolist (p '((0 . +1) (0 . -1) (+1 . 0) (-1 . 0)))
(let ((r (+ row (car p)))
(c (+ col (cdr p))))
(unless (maze-is-set maze r c 'visited)
(push (cons r c) unvisited))))
unvisited))
(defun make-passage (maze r1 c1 r2 c2)
(if (= r1 r2)
(if (< c1 c2)
(maze-unset maze r2 c2 'wall) ; right
(maze-unset maze r1 c1 'wall)) ; left
(if (< r1 r2)
(maze-unset maze r2 c2 'ceiling) ; up
(maze-unset maze r1 c1 'ceiling)))) ; down
(defun dig-maze (maze row col)
(let (backup
(run 0))
(maze-set maze row col 'visited)
(push (cons row col) backup)
(while backup
(setq run (1+ run))
(when (> run (/ (+ row col) 3))
(setq run 0)
(setq backup (shuffle backup)))
(setq row (caar backup)
col (cdar backup))
(let ((p (shuffle (to-visit maze row col))))
(if p
(let ((r (caar p))
(c (cdar p)))
(make-passage maze row col r c)
(maze-set maze r c 'visited)
(push (cons r c) backup))
(pop backup)
(setq backup (shuffle backup))
(setq run 0))))))
(defun generate (rows cols)
(let* ((m (new-maze rows cols)))
(dig-maze m (random rows) (random cols))
(print-maze m)))
(defun parse-ceilings (line)
(let (rtn
(i 1))
(while (< i (length line))
(push (eq ?- (elt line i)) rtn)
(setq i (+ i 4)))
(nreverse rtn)))
(defun parse-walls (line)
(let (rtn
(i 0))
(while (< i (length line))
(push (eq ?| (elt line i)) rtn)
(setq i (+ i 4)))
(nreverse rtn)))
(defun parse-maze (file-name)
(let ((rtn)
(lines (with-temp-buffer
(insert-file-contents-literally file-name)
(split-string (buffer-string) "\n" t))))
(while lines
(push (parse-ceilings (pop lines)) rtn)
(push (parse-walls (pop lines)) rtn))
(nreverse rtn)))
(defun read-maze (file-name)
(let* ((raw (parse-maze file-name))
(rows (1- (/ (length raw) 2)))
(cols (length (car raw)))
(maze (new-maze rows cols)))
(dotimes (r rows)
(let ((ceilings (pop raw)))
(dotimes (c cols)
(unless (pop ceilings)
(maze-unset maze r c 'ceiling))))
(let ((walls (pop raw)))
(dotimes (c cols)
(unless (pop walls)
(maze-unset maze r c 'wall)))))
maze))
(defun find-exits (maze row col)
(let (exits)
(dolist (p '((0 . +1) (0 . -1) (-1 . 0) (+1 . 0)))
(let ((r (+ row (car p)))
(c (+ col (cdr p))))
(unless
(cond
((equal p '(0 . +1)) (maze-is-set maze r c 'wall))
((equal p '(0 . -1)) (maze-is-set maze row col 'wall))
((equal p '(+1 . 0)) (maze-is-set maze r c 'ceiling))
((equal p '(-1 . 0)) (maze-is-set maze row col 'ceiling)))
(push (cons r c) exits))))
exits))
(defun drop-visited (maze points)
(let (not-visited)
(while points
(unless (maze-is-set maze (caar points) (cdar points) 'visited)
(push (car points) not-visited))
(pop points))
not-visited))
(defun solve-maze (maze)
(let (solution
(exit (cons (- (maze-rows maze) 2) (- (maze-cols maze) 2)))
(pt (cons 0 0)))
(while (not (equal pt exit))
(maze-set maze (car pt) (cdr pt) 'visited)
(let ((exits (drop-visited maze (find-exits maze (car pt) (cdr pt)))))
(if (null exits)
(setq pt (pop solution))
(push pt solution)
(setq pt (pop exits)))))
(push pt solution)))
(defun solve (file-name)
(let* ((maze (read-maze file-name))
(solution (solve-maze maze)))
(print-maze maze solution)))
(generate 20 20)
- Output:
+ +---+---+---+---+---+---+---+---+---+ | | | | | +---+---+ + +---+---+ +---+---+ + | | | | | | | | + + + + +---+ + + +---+ + | | | | | +---+---+---+---+---+ +---+---+ + + | | | | | | | | | + +---+ + + +---+ + + + + | | | | | | | + + + + +---+ + + +---+ + | | | | | | | + + + +---+---+---+ +---+---+ + | | | | | | | + + +---+---+ + + + + + + | | | | | | | + + + +---+---+---+---+---+ + + | | | | | | + +---+---+ + + +---+---+---+ + | | | | +---+---+---+---+---+---+---+---+---+ +
Erlang
Erlang is single assignment. To get mutability I use processes. The code is over-enginered for this task, but the extra is used for Maze_solving. Also, Erlang starts counting at 1, not 0, so the co-ordinate of the lower left corner is 1,1.
Using multiple processes
-module( maze ).
-export( [cell_accessible_neighbours/1, cell_content/1, cell_content_set/2, cell_pid/3, cell_position/1, display/1, generation/2, stop/1, task/0] ).
-record( maze, {dict, max_x, max_y, start} ).
-record( state, {content=" ", controller, is_dug=false, max_x, max_y, neighbours=[], position, walls=[north, south, east, west], walk_done} ).
cell_accessible_neighbours( Pid ) -> read( Pid, accessible_neighbours ).
cell_content( Pid ) -> read( Pid, content ).
cell_content_set( Pid, Content ) -> Pid ! {content, Content, erlang:self()}.
cell_pid( X, Y, Maze ) -> dict:fetch( {X, Y}, Maze#maze.dict ).
cell_position( Pid ) -> read( Pid, position ).
display( #maze{dict=Dict, max_x=Max_x, max_y=Max_y} ) ->
Position_pids = dict:to_list( Dict ),
display( Max_x, Max_y, reads(Position_pids, content), reads(Position_pids, walls) ).
generation( Max_x, Max_y ) ->
Controller = erlang:self(),
Position_pids = cells_create( Controller, Max_x, Max_y ),
Pids = [Y || {_X, Y} <- Position_pids],
[X ! {position_pids, Position_pids} || X <- Pids],
{Position, Pid} = lists:nth( random:uniform(Max_x * Max_y), Position_pids ),
Pid ! {dig, Controller},
receive
{dig_done} -> ok
end,
#maze{dict=dict:from_list(Position_pids), max_x=Max_x, max_y=Max_y, start=Position}.
stop( #maze{dict=Dict} ) ->
Controller = erlang:self(),
Pids = [Y || {_X, Y} <- dict:to_list(Dict)],
[X ! {stop, Controller} || X <- Pids],
ok.
task() ->
Maze = generation( 16, 8 ),
io:fwrite( "Starting at ~p~n", [Maze#maze.start] ),
display( Maze ),
stop( Maze ).
cells_create( Controller, Max_x, Max_y ) -> [{{X, Y}, cell_create(Controller, Max_x, Max_y, {X, Y})} || X <- lists:seq(1, Max_x), Y<- lists:seq(1, Max_y)].
cell_create( Controller, Max_x, Max_y, {X, Y} ) -> erlang:spawn_link( fun() -> random:seed( X*1000, Y*1000, (X+Y)*1000 ), loop( #state{controller=Controller, max_x=Max_x, max_y=Max_y, position={X, Y}} ) end ).
display( Max_x, Max_y, Position_contents, Position_walls ) ->
All_rows = [display_row( Max_x, Y, Position_contents, Position_walls ) || Y <- lists:seq(Max_y, 1, -1)],
[io:fwrite("~s+~n~s|~n", [North, West]) || {North, West} <- All_rows],
io:fwrite("~s+~n", [lists:flatten(lists:duplicate(Max_x, display_row_north(true)))] ).
display_row( Max_x, Y, Position_contents, Position_walls ) ->
North_wests = [display_row_walls(proplists:get_value({X,Y}, Position_contents), proplists:get_value({X,Y}, Position_walls)) || X <- lists:seq(1, Max_x)],
North = lists:append( [North || {North, _West} <- North_wests] ),
West = lists:append( [West || {_X, West} <- North_wests] ),
{North, West}.
display_row_walls( Content, Walls ) -> {display_row_north( lists:member(north, Walls) ), display_row_west( lists:member(west, Walls), Content )}.
display_row_north( true ) -> "+---";
display_row_north( false ) -> "+ ".
display_row_west( true, Content ) -> "| " ++ Content ++ " ";
display_row_west( false, Content ) -> " " ++ Content ++ " ".
loop( State ) ->
receive
{accessible_neighbours, Pid} ->
Pid ! {accessible_neighbours, loop_accessible_neighbours( State#state.neighbours, State#state.walls ), erlang:self()},
loop( State );
{content, Pid} ->
Pid ! {content, State#state.content, erlang:self()},
loop( State );
{content, Content, _Pid} ->
loop( State#state{content=Content} );
{dig, Pid} ->
Not_dug_neighbours = loop_not_dug( State#state.neighbours ),
New_walls = loop_dig( Not_dug_neighbours, lists:delete( loop_wall_from_pid(Pid, State#state.neighbours), State#state.walls), Pid ),
loop( State#state{is_dug=true, walls=New_walls, walk_done=Pid} );
{dig_done} ->
Not_dug_neighbours = loop_not_dug( State#state.neighbours ),
New_walls = loop_dig( Not_dug_neighbours, State#state.walls, State#state.walk_done ),
loop( State#state{walls=New_walls} );
{is_dug, Pid} ->
Pid ! {is_dug, State#state.is_dug, erlang:self()},
loop( State );
{position, Pid} ->
Pid ! {position, State#state.position, erlang:self()},
loop( State );
{position_pids, Position_pids} ->
{_My_position, Neighbours} = lists:foldl( fun loop_neighbours/2, {State#state.position, []}, Position_pids ),
erlang:garbage_collect(), % Shrink process after using large Pid_positions. For memory starved systems.
loop( State#state{neighbours=Neighbours} );
{stop, Controller} when Controller =:= State#state.controller ->
ok;
{walls, Pid} ->
Pid ! {walls, State#state.walls, erlang:self()},
loop( State )
end.
loop_accessible_neighbours( Neighbours, Walls ) -> [Pid || {Direction, Pid} <- Neighbours, not lists:member(Direction, Walls)].
loop_dig( [], Walls, Pid ) ->
Pid ! {dig_done},
Walls;
loop_dig( Not_dug_neighbours, Walls, _Pid ) ->
{Dig_pid, Dig_direction} = lists:nth( random:uniform(erlang:length(Not_dug_neighbours)), Not_dug_neighbours ),
Dig_pid ! {dig, erlang:self()},
lists:delete( Dig_direction, Walls ).
loop_neighbours( {{X, Y}, Pid}, {{X, My_y}, Acc} ) when Y =:= My_y + 1 -> {{X, My_y}, [{north, Pid} | Acc]};
loop_neighbours( {{X, Y}, Pid}, {{X, My_y}, Acc} ) when Y =:= My_y - 1 -> {{X, My_y}, [{south, Pid} | Acc]};
loop_neighbours( {{X, Y}, Pid}, {{My_x, Y}, Acc} ) when X =:= My_x + 1 -> {{My_x, Y}, [{east, Pid} | Acc]};
loop_neighbours( {{X, Y}, Pid}, {{My_x, Y}, Acc} ) when X =:= My_x - 1 -> {{My_x, Y}, [{west, Pid} | Acc]};
loop_neighbours( _Position_pid, Acc ) -> Acc.
loop_not_dug( Neighbours ) ->
My_pid = erlang:self(),
[Pid ! {is_dug, My_pid} || {_Direction, Pid} <- Neighbours],
[{Pid, Direction} || {Direction, Pid} <- Neighbours, not read_receive(Pid, is_dug)].
loop_wall_from_pid( Pid, Neighbours ) -> loop_wall_from_pid_result( lists:keyfind(Pid, 2, Neighbours) ).
loop_wall_from_pid_result( {Direction, _Pid} ) -> Direction;
loop_wall_from_pid_result( false ) -> controller.
read( Pid, Key ) ->
Pid ! {Key, erlang:self()},
read_receive( Pid, Key ).
read_receive( Pid, Key ) ->
receive
{Key, Value, Pid} -> Value
end.
reads( Position_pids, Key ) ->
My_pid = erlang:self(),
[Pid ! {Key, My_pid} || {_Position, Pid} <- Position_pids],
[{Position, read_receive(Pid, Key)} || {Position, Pid} <- Position_pids].
- Output:
5> maze:task(). Starting at {10,5} +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | | | | | | +---+ +---+---+ + + + +---+ + + + +---+---+ + | | | | | | | | | | + +---+ + +---+ +---+---+ + +---+---+---+---+ + + | | | | | | | | | + +---+ +---+---+ + +---+---+---+---+ + +---+---+ + | | | | | | | | | | + + + + +---+---+ + +---+---+ +---+ + + +---+ | | | | | | | | | | +---+ +---+---+ +---+---+---+---+ + + +---+ +---+ + | | | | | | | | | | + + + + +---+---+ + +---+ + + +---+ + + + | | | | | | | | | | | + +---+ + +---+---+ +---+ +---+---+---+ +---+---+ + | | | | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Using 2 digraphs
Uses 2 digraph "objects": a) the 'matrix', a fully connected digraph of MxN vertices and b) the 'maze', an unconnected digraph, also MxN, that is populated while walking.
Employs a faux Visitor pattern to populate the maze while traversing the matrix in depth-first order.
Vertices: 0 .. MxN - 1
Rows: 0 .. M - 1
Cols: 0 .. N - 1
Usage: start with generate_default/0. Use generate_MxN() to test other maze sizes.
-module(maze).
-record(maze, {g, m, n}).
-export([generate_default/0, generate_MxN/2]).
make_maze(M, N) ->
Maze = #maze{g = digraph:new(), m = M, n = N},
lists:foreach(fun(X) -> digraph:add_vertex(Maze#maze.g, X) end, lists:seq(0, M * N - 1)),
Maze.
row_at(V, Maze) -> trunc(V / Maze#maze.n).
col_at(V, Maze) -> V - row_at(V, Maze) * Maze#maze.n.
vertex_at(Row, Col, Maze) -> Cell_Exists = cell_exists(Row, Col, Maze), if Cell_Exists -> Row * Maze#maze.n + Col; true -> -1 end.
cell_exists(Row, Col, Maze) -> (Row >= 0) and (Row < Maze#maze.m) and (Col >= 0) and (Col < Maze#maze.n).
adjacent_cells(V, Maze) -> % ordered: left, up, right, down
adjacent_cell(cell_left, V, Maze)++adjacent_cell(cell_up, V, Maze)++adjacent_cell(cell_right, V, Maze)++adjacent_cell(cell_down, V, Maze).
adjacent_cell(cell_left, V, Maze) -> case (col_at(V, Maze) == 0) of true -> []; _Else -> [V - 1] end;
adjacent_cell(cell_up, V, Maze) -> case (row_at(V, Maze) == 0) of true -> []; _Else -> [V - Maze#maze.n] end;
adjacent_cell(cell_right, V, Maze) -> case (col_at(V, Maze) == Maze#maze.n - 1) of true -> []; _Else -> [V + 1] end;
adjacent_cell(cell_down, V, Maze) -> case (row_at(V, Maze) == Maze#maze.m - 1) of true -> []; _Else -> [V + Maze#maze.n] end.
connect_all(V, Maze) ->
lists:foreach(fun(X) -> digraph:add_edge(Maze#maze.g, V, X) end, adjacent_cells(V, Maze)).
make_maze(M, N, all_connected) ->
Maze = make_maze(M, N),
lists:foreach(fun(X) -> connect_all(X, Maze) end, lists:seq(0, M * N - 1)),
Maze.
maze_parts(Maze) ->
SPR = Maze#maze.n + 1, % slots per row is #columns + 1
NPR = (Maze#maze.m * 2) + 1, % # part rows is #(rows * 2) + 1
[make_part(Maze, trunc(Index/SPR), Index - trunc(Index/SPR) * SPR) || Index <- lists:seq(0, (SPR * NPR) - 1)].
draw_part(Part) ->
case Part of
{pwall, pclosed} -> io:format("+---");
{pwall, popen} -> io:format("+ ");
{pwall, pend} -> io:format("+~n");
{phall, pclosed} -> io:format("| ");
{phall, popen} -> io:format(" ");
{phall, pend} -> io:format("|~n")
end.
has_neighbour(Maze, Row, Col, Direction) ->
V = vertex_at(Row, Col, Maze),
if
V >= 0 ->
Adjacent = adjacent_cell(Direction, V, Maze),
if
length(Adjacent) > 0 ->
Neighbours = digraph:out_neighbours(Maze#maze.g, lists:nth(1, Adjacent)),
lists:member(V, Neighbours);
true -> false
end;
true -> false
end.
make_part(Maze, DoubledRow, Col) ->
if
trunc(DoubledRow/2) * 2 == DoubledRow -> % --- (even row) making a wall above the cell
make_part(Maze, trunc(DoubledRow/2), Col, cell_up, pwall);
true -> % ---otherwise (odd row) making a hall through the cell
make_part(Maze, trunc(DoubledRow/2), Col, cell_left, phall)
end.
make_part(Maze, _, Col, _, Part_Type) when Col == Maze#maze.n -> {Part_Type, pend};
make_part(Maze, Row, Col, Direction, Part_Type) ->
Has_Neighbour = has_neighbour(Maze, Row, Col, Direction),
if
Has_Neighbour -> {Part_Type, popen};
true -> {Part_Type, pclosed}
end.
shuffle([], Acc) -> Acc;
shuffle(List, Acc) ->
Elem = lists:nth(random:uniform(length(List)), List),
shuffle(lists:delete(Elem, List), Acc++[Elem]).
processDepthFirst(Maze) ->
if
Maze#maze.m * Maze#maze.n == 0 -> [{pwall, pend}];
true ->
Visited = array:new([{size, Maze#maze.m * Maze#maze.n},{fixed,true},{default,false}]),
{_, Path} = processDepthFirst(Maze, -1, random:uniform(Maze#maze.m * Maze#maze.n) - 1, {Visited, []}),
Path
end.
processDepthFirst(Maze, Vfrom, V, VandP) ->
{Visited, Path} = VandP,
Was_Visited = array:get(V, Visited),
if
not Was_Visited ->
Walker = fun(X, Acc) -> processDepthFirst(Maze, V, X, Acc) end,
Random_Neighbours = shuffle(digraph:out_neighbours(Maze#maze.g, V), []),
lists:foldl(Walker, {array:set(V, true, Visited), Path++[{Vfrom, V}]}, Random_Neighbours);
true -> VandP
end.
open_wall(_, {-1, _}) -> ok;
open_wall(Maze, {V, V2}) ->
case (V2 > V) of true -> digraph:add_edge(Maze#maze.g, V, V2); _Else -> digraph:add_edge(Maze#maze.g, V2, V) end.
generate_MxN(M, N) ->
Maze = make_maze(M, N),
Matrix = make_maze(M, N, all_connected),
Trail = processDepthFirst(Matrix),
lists:foreach(fun(X) -> open_wall(Maze, X) end, Trail),
Parts = maze_parts(Maze),
lists:foreach(fun(X) -> draw_part(X) end, Parts).
generate_default() ->
generate_MxN(9, 9).
- Output:
8> maze:generate_default(). +---+---+---+---+---+---+---+---+---+ | | | + + +---+ +---+---+---+---+ + | | | | | | + +---+ +---+---+ + + + + | | | | | | | + + + + + +---+---+---+ + | | | | | | | | + + +---+ + + + + +---+ | | | | | | + +---+---+---+---+---+---+---+ + | | | | + + +---+---+ +---+---+ + + | | | | | | + +---+---+---+ +---+ +---+ + | | | | | | + + +---+ +---+ +---+ + + | | | | +---+---+---+---+---+---+---+---+---+ ok 9>
F#
Using mutable state in the form of 2D arrays:
let rnd : int -> int =
let gen = new System.Random()
fun max -> gen.Next(max)
// randomly choose an element of a list
let choose (xs:_ list) = xs.[rnd xs.Length]
type Maze(width, height) =
// (x,y) -> have we been here before?
let visited = Array2D.create width height false
// (x,y) -> is there a wall between (x,y) and (x+1,y)?
let horizWalls = Array2D.create width height true
// (x,y) -> is there a wall between (x,y) and (x,y+1)?
let vertWalls = Array2D.create width height true
let isLegalPoint (x,y) =
x >= 0 && x < width && y >= 0 && y < height
let neighbours (x,y) =
[(x-1,y);(x+1,y);(x,y-1);(x,y+1)] |> List.filter isLegalPoint
let removeWallBetween (x1,y1) (x2,y2) =
if x1 <> x2 then
horizWalls.[min x1 x2, y1] <- false
else
vertWalls.[x1, min y1 y2] <- false
let rec visit (x,y as p) =
let rec loop ns =
let (nx,ny) as n = choose ns
if not visited.[nx,ny] then
removeWallBetween p n
visit n
match List.filter ((<>) n) ns with
| [] -> ()
| others -> loop others
visited.[x,y] <- true
loop (neighbours p)
do visit (rnd width, rnd height)
member x.Print() =
("+" + (String.replicate width "-+")) ::
[for y in 0..(height-1) do
yield "\n|"
for x in 0..(width-1) do
yield if horizWalls.[x,y] then " |" else " "
yield "\n+"
for x in 0..(width-1) do
yield if vertWalls.[x,y] then "-+" else " +"
]
|> String.concat ""
|> printfn "%s"
let m = new Maze(10,10)
m.Print()
- Output example:
+-+-+-+-+-+-+-+-+-+-+ | | | | + +-+-+-+-+ +-+ + + + | | | | | | + +-+-+ + +-+-+ +-+ + | | | | | +-+ +-+ +-+-+ +-+-+ + | | | | | + +-+ +-+ +-+-+-+ +-+ | | | | | | + + +-+ +-+ +-+ +-+ + | | | | | | | | + + +-+ + +-+-+-+ + + | | | | | +-+ + +-+-+-+-+-+-+ + | | | | | + +-+-+ +-+ +-+-+ + + | | | | | | + +-+ +-+ +-+-+ +-+-+ | | | +-+-+-+-+-+-+-+-+-+-+
Forth
The solution uses the following library bits.fs, which implements bit-arrays:
\ Bit Arrays
: to-bits ( c -- f f f f f f f f )
8 0 ?do
2 /mod
swap negate swap
loop
drop ;
: from-bits ( f f f f f f f f -- )
8 0 ?do
if [char] 1 emit else [char] 0 emit then
loop ;
: byte-bin. ( c -- )
to-bits from-bits space ;
: byte. ( c -- )
dup byte-bin.
dup 2 ['] u.r 16 base-execute space
3 u.r space ;
: bytes-for-bits ( u1 -- u2 )
8 /mod swap
0> if 1+ then ;
: bits ( u -- bits )
dup bytes-for-bits cell + \ u-bits u-bytes
dup allocate throw \ u-bits u-bytes addr
2dup swap erase nip \ u-bits addr
swap over ! ; \ addr
: free-bits ( bits -- )
free throw ;
: bits. ( bits -- )
dup @ bytes-for-bits \ addr bytes
swap cell+ swap \ addr+cell bytes
bounds ?do
i cr 20 ['] u.r 16 base-execute space
i c@ byte.
loop
cr ;
: bit-position ( u -- u-bit u-byte )
8 /mod ;
: assert-bit ( bits u -- bits u )
assert( 2dup swap @ < ) ;
: find-bit ( bits u1 -- addr u2 )
assert-bit
bit-position \ addr bit byte
rot \ bit byte addr
cell+ + swap ; \ addr' bit
: set-true ( addr u -- )
1 swap lshift over \ addr mask addr
c@ or swap c! ;
: set-false ( addr u -- )
1 swap lshift invert over \ addr mask addr
c@ and swap c! ;
: set ( addr u f -- )
if set-true else set-false then ;
: set-bit ( bits u f -- )
{ f }
find-bit f set ;
: set-bits-at-addr ( addr u-start u-stop f -- )
{ f }
1+ swap u+do
dup i f set
loop
drop ;
: byte-from-flag ( f -- c )
if 255 else 0 then ;
: set-bits { bits u-start u-stop f -- }
u-start u-stop > if exit then
bits u-start find-bit { addr-start bit-start }
bits u-stop find-bit { addr-stop bit-stop }
addr-start addr-stop = if
addr-start bit-start bit-stop f set-bits-at-addr
else
addr-start bit-start 7 f set-bits-at-addr
addr-start 1+ addr-stop addr-start - 1- f byte-from-flag fill
addr-stop 0 bit-stop f set-bits-at-addr
then ;
: check-bit ( addr u -- f )
find-bit \ addr bit
1 swap lshift swap \ mask addr
c@ and 0> ;
: resize-bits ( bits u -- bits )
over @ { old-size }
tuck bytes-for-bits cell + resize throw \ u-bits bits
2dup ! swap \ bits u-bits
dup old-size > if
over swap \ bits bits u-bits
1- old-size swap false set-bits
else
drop
then ;
The solution uses three bit-arrays: one to track whether a cell has been visited, one for "East"-walls (walls to the right of a cell) and one for "South"-walls (walls to the bottom of a cell).
#! /usr/bin/gforth
\ Maze Generation
warnings off
require random.fs
require bits.fs
\ command line
: parse-number s>number? invert throw drop ;
: parse-width ." width : " next-arg parse-number dup . cr ;
: parse-height ." height: " next-arg parse-number dup . cr ;
: parse-args cr parse-width parse-height ;
parse-args constant HEIGHT constant WIDTH
2 CONSTANT AISLE-WIDTH
1 CONSTANT AISLE-HEIGHT
WIDTH HEIGHT * bits CONSTANT VISITED
WIDTH 1- HEIGHT * bits CONSTANT EAST-WALLS
HEIGHT 1- WIDTH * bits CONSTANT SOUTH-WALLS
0 CONSTANT NORTH
1 CONSTANT EAST
2 CONSTANT SOUTH
3 CONSTANT WEST
: visited-ix ( x y -- u ) WIDTH * + ;
: east-wall-ix ( x y -- u ) [ WIDTH 1- ] literal * + ;
: south-wall-ix ( x y -- u ) WIDTH * + ;
: visited! ( x y -- ) visited-ix VISITED swap TRUE set-bit ;
: visited? ( x y -- f ) visited-ix VISITED swap check-bit ;
: east-wall? ( x y -- f ) east-wall-ix EAST-WALLS swap check-bit ;
: south-wall? ( x y -- f ) south-wall-ix SOUTH-WALLS swap check-bit ;
: remove-east-wall ( x y -- ) east-wall-ix EAST-WALLS swap FALSE set-bit ;
: remove-south-wall ( x y -- ) south-wall-ix SOUTH-WALLS swap FALSE set-bit ;
: clear-visited ( -- ) VISITED 0 WIDTH 1- HEIGHT 1- visited-ix FALSE set-bits ;
: set-east-walls ( -- ) EAST-WALLS 0 WIDTH 2 - HEIGHT 1- east-wall-ix TRUE set-bits ;
: set-south-walls ( -- ) SOUTH-WALLS 0 WIDTH 1- HEIGHT 2 - south-wall-ix TRUE set-bits ;
: initial-pos ( -- x y ) WIDTH random HEIGHT random ;
: init-state ( -- -1 x y 0 ) clear-visited set-east-walls set-south-walls -1 initial-pos 2dup visited! 0 ;
: north-valid? ( x y -- f ) nip 0> ;
: east-valid? ( x y -- f ) drop [ WIDTH 1- ] literal < ;
: south-valid? ( x y -- f ) nip [ HEIGHT 1- ] literal < ;
: west-valid? ( x y -- f ) drop 0> ;
: dir-valid? ( x y d -- f ) case
NORTH of north-valid? endof
EAST of east-valid? endof
SOUTH of south-valid? endof
WEST of west-valid? endof
endcase ;
: move-north ( x y -- x' y' ) 1- ;
: move-east ( x y -- x' y' ) swap 1+ swap ;
: move-south ( x y -- x' y' ) 1+ ;
: move-west ( x y -- x' y' ) swap 1- swap ;
: move ( x y d -- x' y' ) case
NORTH of move-north endof
EAST of move-east endof
SOUTH of move-south endof
WEST of move-west endof
endcase ;
: remove-north-wall ( x y -- ) 1- remove-south-wall ;
: remove-west-wall ( x y -- ) swap 1- swap remove-east-wall ;
: remove-wall ( x y d -- ) case
NORTH of remove-north-wall endof
EAST of remove-east-wall endof
SOUTH of remove-south-wall endof
WEST of remove-west-wall endof
endcase ;
: dir? ( m d -- f ) 1 swap lshift and 0= ;
: dir! ( m d -- m' ) 1 swap lshift or ;
: pick-dir ( m -- m' d ) assert( dup $f <> ) begin 4 random 2dup dir? if tuck dir! swap exit then drop again ;
: update-state ( x y m d -- x' y' m' ) { x y m d }
x y d dir-valid? if
x y m
x y d move
2dup visited? if
2drop
else
2dup visited!
x y d remove-wall
0
then
else
x y m
then ;
: step ( x y m -- x' y' m' ) dup $f = if
drop 2drop \ backtracking!
else
pick-dir update-state
then ;
: build-maze ( -- ) init-state
begin
dup -1 <> while
step
repeat drop ;
: corner ( -- ) [char] + emit ;
: h-wall ( -- ) [char] - emit ;
: v-wall ( -- ) [char] | emit ;
: top-bottom. ( -- ) cr corner WIDTH 0 ?do AISLE-WIDTH 0 ?do h-wall loop corner loop ;
: empty ( -- ) AISLE-WIDTH 0 ?do space loop ;
: interior-cell ( x y -- ) empty east-wall? if v-wall else space then ;
: last-cell ( -- ) empty v-wall ;
: row ( y -- ) cr v-wall [ WIDTH 1- ] literal 0 ?do i over interior-cell loop drop last-cell ;
: last-row ( y -- ) cr WIDTH 0 ?do corner i over south-wall? if AISLE-WIDTH 0 ?do h-wall loop else empty then loop drop corner ;
: aisle ( y -- ) AISLE-HEIGHT 0 ?do dup row loop dup [ HEIGHT 1- ] literal < if last-row else drop then ;
: maze. ( -- ) top-bottom.
HEIGHT 0 ?do i aisle loop
top-bottom. ;
: maze ( width height -- ) build-maze maze. ;
maze cr bye
- Output:
./maze-generation.fs 20 10
width : 20 height: 10
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | | | | | | | + +--+ + +--+ +--+--+ + +--+ +--+--+ + +--+--+ + + | | | | | | | | | | | +--+--+--+--+ +--+ +--+--+--+ + + +--+--+--+ + +--+ + | | | | | | | | | + +--+--+--+--+--+--+--+ + + + +--+--+--+ +--+--+ +--+ | | | | | | | + +--+--+--+--+--+ + +--+--+--+--+ + + +--+--+ + + + | | | | | | | | | | | | | | + + + + +--+ + + + +--+ +--+ + +--+ + + +--+ + | | | | | | | | | | | | +--+--+--+--+ + + + +--+ +--+--+--+--+--+--+ +--+--+ + | | | | | | | | | + +--+ + +--+--+ +--+--+ + +--+ +--+ +--+--+--+ +--+ | | | | | | | | | | | | | | +--+ + + + + + + +--+--+ + + + + + + + +--+ + | | | | | | | | | | | | | | | | + +--+--+--+--+ + + + + + + + + +--+ +--+--+ + + | | | | | | | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
./maze-generation.fs 40 20
width : 40 height: 20
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | | | | | | | | | | | | | + +--+ + +--+ +--+--+ + +--+ +--+--+ + + + +--+--+--+ + +--+ +--+ +--+ + + + +--+ + + + + +--+ + | | | | | | | | | | | | | | | | | | | | | | +--+--+--+--+ +--+ +--+--+--+ + + +--+--+--+ + +--+ +--+--+--+ +--+--+--+--+--+ + +--+ + +--+ + +--+--+ + | | | | | | | | | | | | | | | | | | | + + + + +--+--+--+ +--+ + + +--+--+--+ +--+--+ +--+ + + +--+--+--+--+--+ +--+--+ +--+--+ +--+ + +--+ + | | | | | | | | | | | | | | | | | | | + +--+ +--+--+--+--+--+ + +--+--+ +--+ +--+--+ + + +--+ +--+ + +--+ + +--+--+ +--+--+--+--+ + +--+--+--+ | | | | | | | | | | | | | | | | | | + +--+--+ +--+--+--+--+--+ + + +--+ +--+--+ + +--+ + +--+--+--+ + +--+--+ + +--+--+ + + +--+--+--+--+ + | | | | | | | | | | | | | | | | | | | | | + +--+ +--+ +--+--+ + +--+--+--+ + + + + +--+--+--+ +--+ +--+--+--+ + + +--+ + +--+--+--+--+--+--+ + + | | | | | | | | | | | | | | | | | + + +--+--+--+--+--+ +--+ + + +--+--+--+ +--+ + + +--+ +--+--+ +--+--+ +--+--+--+ + + +--+--+--+ +--+ + | | | | | | | | | | | | | | | | | | | | | | +--+ + +--+--+--+ +--+--+--+--+--+ +--+ +--+ +--+ +--+--+ + + +--+--+ + +--+--+ + + + + +--+--+ + + + | | | | | | | | | | | | | | | | | | | | | | | + +--+ + + + +--+ +--+ + +--+--+ +--+ + + + + +--+--+--+--+ + + +--+--+ + + +--+ +--+--+ +--+--+ + | | | | | | | | | | | | | | | | | | | | | | | + +--+--+ + +--+ +--+ + + + + + +--+--+ + +--+ + +--+ + +--+ + + +--+--+ +--+--+--+--+ + + +--+--+ | | | | | | | | | | | | | | | | | | | | | | | | | | + + +--+ + + +--+ +--+ + + + + + +--+--+ + +--+ + +--+--+ + + +--+ + + + + +--+ +--+ +--+ + + | | | | | | | | | | | | | | | | | | | | | | | | | | | + +--+ +--+--+--+ + + +--+--+ + +--+ +--+ + + + +--+ + + +--+ +--+--+--+--+--+--+ + +--+ +--+ + + + | | | | | | | | | | | | | | | | | | | | | +--+ + +--+ + + + +--+--+--+--+--+--+--+--+--+ + + +--+ + +--+--+--+ + + +--+ + +--+ + + +--+ +--+ + | | | | | | | | | | | | | | | | | | | | | | | | + + +--+ +--+--+ + + +--+--+ + +--+--+--+ + + +--+ +--+ + +--+ + + +--+--+--+--+ +--+ +--+ +--+ + + | | | | | | | | | | | | | | | | | | | | | | | | + +--+ + +--+ + + +--+--+ + + + +--+ + +--+ + +--+ + + +--+--+ +--+ +--+--+ +--+ +--+ + +--+--+ + | | | | | | | | | | | | | | | | | | | | | | + +--+ +--+--+ + + + + + +--+--+--+ +--+--+--+--+ + + +--+--+--+--+--+--+--+ +--+--+ +--+ +--+--+ +--+--+ | | | | | | | | | | | | | | | | | | | + + +--+ +--+--+ +--+--+--+--+ + + +--+ +--+--+ +--+ +--+--+ + +--+ + +--+--+ +--+--+ +--+ + +--+--+ + | | | | | | | | | | | | | | | | | | | + +--+--+--+ +--+--+ +--+ +--+--+--+ + + + +--+--+ +--+--+ + + + +--+ +--+ + +--+ + + +--+--+--+--+ + | | | | | | | | | | | | | | | | | | | | | | + +--+--+ +--+--+--+--+ + + +--+ +--+--+--+--+ + + + + +--+--+--+ +--+ + +--+--+ + + +--+ +--+ + + + | | | | | | | | | | | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
FreeBASIC
' version 04-12-2016
' compile with: fbc -s console
' when generating a big maze it's possible to run out of stack space
' increase stack with the -t xxxx (xxxx is the amount you want in Kbytes)
ReDim Shared As String d() ' directions
ReDim Shared As ULong c() ' cell's
Sub cell(x As ULong, y As ULong, s As ULong)
Dim As ULong x1, y1, di_n
c(x,y) = 1 ' mark as visited
Do
Dim As String di = d(x, y)
Dim As Long l = Len(di) -1
If l < 0 Then Exit Sub ' no directions left then exit
di_n = di[l] ' get direction
If l = 0 Then
d(x,y) = ""
Else
d(x,y) = Left(di,l)
End If
Select Case di_n ' 0,0 is upper left corner
Case Asc("N")
x1 = x : y1 = y -1
Case Asc("E")
x1 = x +1 : y1 = y
Case Asc("S")
x1 = x : y1 = y +1
Case Asc("W")
x1 = x -1 : y1 = y
End Select
If c(x1,y1) <> 0 Then Continue Do
Select Case di_n ' 0,0 is upper left corner
Case Asc("N")
Line (x * s +1 , y * s) - ((x +1) * s -1, y * s),0
Case Asc("E")
Line (x1 * s, y * s +1) - (x1 * s, (y +1) * s -1),0
Case Asc("S")
Line (x * s +1, y1 * s) - ((x +1) * s -1, y1 * s),0
Case Asc("W")
Line (x * s , y * s +1) - (x * s, (y +1) * s -1),0
End Select
cell(x1, y1, s)
Loop
End Sub
Sub gen_maze(w As ULong, h As ULong, s As ULong)
ReDim d(w, h)
ReDim c(w, h)
Dim As ULong x, y, r, i
Dim As String di
d(0, 0) = "SE" ' cornes
d(0, h -1) ="NE"
d(w -1, 0) ="SW"
d(w -1, h -1) ="NW"
For x = 1 To w -2 ' sides
d(x,0) = "EWS"
d(x,h -1) = "NEW"
Next
For y = 1 To h -2
d(0, y) = "NSE"
d(w -1, y) ="NSW"
Next
For x = 0 To w -1 ' shuffle directions
For y = 0 To h -1
di = d(x,y)
If di = "" Then di = "NEWS"
i = Len(di)
Do
r = Fix(Rnd * i)
i = i - 1
Swap di[r], di[i]
Loop Until i = 0
d(x,y) = di
Next
Next
ScreenRes w * s +1, h * s +1, 8
' draw the grid
For x = 0 To w
Line (x * s, 0) - (x * s, h * s), 2 ' green color
Next
For y = 0 To h
Line(0, y * s) - (w* s, y * s),2
Next
' choice the start cell
x = Fix(Rnd * w)
y = Fix(Rnd * h)
cell(x, y, s)
End Sub
' ------=< MAIN >=------
Randomize Timer
Dim As ULong t
Do
' gen_maxe(width, height, cell size)
gen_maze(30, 30, 20)
WindowTitle " S to save, N for next maze, other key to stop"
Do
Var key = Inkey
key = UCase(key)
If key = "S" Then
t = t +1
BSave("maze" + Str(t) + ".bmp"), 0
key = ""
End If
If key = "N" Then Continue Do, Do
If key <> "" Then Exit Do, Do
Loop
Loop
End
Fōrmulæ
Fōrmulæ programs are not textual, visualization/edition of programs is done showing/manipulating structures but not text. Moreover, there can be multiple visual representations of the same program. Even though it is possible to have textual representation —i.e. XML, JSON— they are intended for storage and transfer purposes more than visualization and edition.
Programs in Fōrmulæ are created/edited online in its website.
In this page you can see and run the program(s) related to this task and their results. You can also change either the programs or the parameters they are called with, for experimentation, but remember that these programs were created with the main purpose of showing a clear solution of the task, and they generally lack any kind of validation.
Solution
Test cases
FutureBasic
_rows = 9
_cols = 11
_size = 32
_mgn = 32
_t = ( 1 << 0 )
_l = ( 1 << 1 )
_b = ( 1 << 2 )
_r = ( 1 << 3 )
_a = _t + _l + _b + _r
_window = 1
void local fn BuildWindow
window _window, @"FutureBasic - Maze generation", (0,0,_cols*_size+_mgn*2,_rows*_size+_mgn*2), NSWindowStyleMaskTitled
end fn
local fn CellAvailable( r as long, c as long ) as BOOL
if ( r < 0 || c < 0 || r >= _rows || c >= _cols ) then exit fn
if ( mda_integer(r,c) == _a ) then exit fn = YES
end fn = NO
void local fn ProcessCell( r as long, c as long )
long r1 = r, c1 = c, d(3), count, dir, opp
while ( 1 )
BlockZero( @d(0), sizeof(long) * 4 )
count = 0
if ( fn CellAvailable( r - 1, c ) ) then d(count) = _t : count++
if ( fn CellAvailable( r, c - 1 ) ) then d(count) = _l : count++
if ( fn CellAvailable( r + 1, c ) ) then d(count) = _b : count++
if ( fn CellAvailable( r, c + 1 ) ) then d(count) = _r : count++
if ( count == 0 ) then break
dir = d(rnd(count)-1)
mda(r,c) = @(mda_integer(r,c) - dir)
select ( dir )
case _t
r1 = r-1 : opp = _b
case _l
c1 = c-1 : opp = _r
case _b
r1 = r+1 : opp = _t
case _r
c1 = c+1 : opp = _l
end select
mda(r1,c1) = @(mda_integer(r1,c1) - opp)
fn ProcessCell( r1, c1 )
wend
end fn
void local fn DrawMaze
long r, c, x = _mgn, y = _mgn, value
pen 2,, NSLineCapStyleRound
for r = 0 to _rows - 1
for c = 0 to _cols - 1
value = mda(r,c)
if ( value & _t ) then line x, y to x + _size, y
if ( value & _l ) then line x, y to x, y + _size
if ( value & _b ) then line x, y + _size to x + _size, y + _size
if ( value & _r ) then line x + _size, y to x + _size, y + _size
x += _size
next
x = _mgn
y += _size
next
end fn
void local fn BuildMaze
long r, c
for r = 0 to _rows - 1
for c = 0 to _cols - 1
mda(r,c) = _a
next
next
random
r = rnd(_rows) - 1
c = rnd(_cols) - 1
fn ProcessCell( r, c )
fn DrawMaze
end fn
fn BuildWindow
fn BuildMaze
HandleEvents
File:FutureBasic - Maze generation1.png File:FutureBasic - Maze generation2.png
Go
package main
import (
"bytes"
"fmt"
"math/rand"
"time"
)
type maze struct {
c []byte // cell contents
h []byte // horizontal walls above cells
v []byte // vertical walls to the left of cells
c2 [][]byte // cells by row
h2 [][]byte // horizontal walls by row (ignore first row)
v2 [][]byte // vertical walls by row (ignore first of each column)
}
func newMaze(rows, cols int) *maze {
c := make([]byte, rows*cols) // all cells
h := bytes.Repeat([]byte{'-'}, rows*cols) // all horizontal walls
v := bytes.Repeat([]byte{'|'}, rows*cols) // all vertical walls
c2 := make([][]byte, rows) // cells by row
h2 := make([][]byte, rows) // horizontal walls by row
v2 := make([][]byte, rows) // vertical walls by row
for i := range h2 {
c2[i] = c[i*cols : (i+1)*cols]
h2[i] = h[i*cols : (i+1)*cols]
v2[i] = v[i*cols : (i+1)*cols]
}
return &maze{c, h, v, c2, h2, v2}
}
func (m *maze) String() string {
hWall := []byte("+---")
hOpen := []byte("+ ")
vWall := []byte("| ")
vOpen := []byte(" ")
rightCorner := []byte("+\n")
rightWall := []byte("|\n")
var b []byte
// for all rows
for r, hw := range m.h2 {
// draw h walls
for _, h := range hw {
if h == '-' || r == 0 {
b = append(b, hWall...)
} else {
b = append(b, hOpen...)
}
}
b = append(b, rightCorner...)
// draw v walls
for c, vw := range m.v2[r] {
if vw == '|' || c == 0 {
b = append(b, vWall...)
} else {
b = append(b, vOpen...)
}
// draw cell contents
if m.c2[r][c] != 0 {
b[len(b)-2] = m.c2[r][c]
}
}
b = append(b, rightWall...)
}
// draw bottom edge of maze
for _ = range m.h2[0] {
b = append(b, hWall...)
}
b = append(b, rightCorner...)
return string(b)
}
func (m *maze) gen() {
m.g2(rand.Intn(len(m.c2)), rand.Intn(len(m.c2[0])))
}
const (
up = iota
dn
rt
lf
)
func (m *maze) g2(r, c int) {
m.c2[r][c] = ' '
for _, dir := range rand.Perm(4) {
switch dir {
case up:
if r > 0 && m.c2[r-1][c] == 0 {
m.h2[r][c] = 0
m.g2(r-1, c)
}
case lf:
if c > 0 && m.c2[r][c-1] == 0 {
m.v2[r][c] = 0
m.g2(r, c-1)
}
case dn:
if r < len(m.c2)-1 && m.c2[r+1][c] == 0 {
m.h2[r+1][c] = 0
m.g2(r+1, c)
}
case rt:
if c < len(m.c2[0])-1 && m.c2[r][c+1] == 0 {
m.v2[r][c+1] = 0
m.g2(r, c+1)
}
}
}
}
func main() {
rand.Seed(time.Now().UnixNano())
m := newMaze(4, 6)
m.gen()
fmt.Print(m)
}
- Output:
+---+---+---+---+---+---+ | | | | + + + +---+ +---+ | | | | | + + +---+---+---+ + | | | | + + + +---+---+ + | | | +---+---+---+---+---+---+
Haskell
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeFamilies #-}
import Data.Array.ST
(STArray, freeze, newArray, readArray, writeArray)
import Data.STRef (STRef, newSTRef, readSTRef, writeSTRef)
import System.Random (Random(..), getStdGen, StdGen)
import Control.Monad (forM_, unless)
import Control.Monad.ST (ST, stToIO)
import Data.Array (Array, (!), bounds)
import Data.Bool (bool)
rand
:: Random a
=> (a, a) -> STRef s StdGen -> ST s a
rand range gen = do
(a, g) <- randomR range <$> readSTRef gen
gen `writeSTRef` g
return a
data Maze = Maze
{ rightWalls, belowWalls :: Array (Int, Int) Bool
}
maze :: Int -> Int -> StdGen -> ST s Maze
maze width height gen = do
visited <- mazeArray False
rWalls <- mazeArray True
bWalls <- mazeArray True
gen <- newSTRef gen
(,) <$> rand (0, maxX) gen <*> rand (0, maxY) gen >>=
visit gen visited rWalls bWalls
Maze <$> freeze rWalls <*> freeze bWalls
where
visit gen visited rWalls bWalls here = do
writeArray visited here True
let ns = neighbors here
i <- rand (0, length ns - 1) gen
forM_ (ns !! i : take i ns ++ drop (i + 1) ns) $
\there -> do
seen <- readArray visited there
unless seen $
do removeWall here there
visit gen visited rWalls bWalls there
where
removeWall (x1, y1) (x2, y2) =
writeArray (bool rWalls bWalls (x1 == x2)) (min x1 x2, min y1 y2) False
neighbors (x, y) =
bool [(x - 1, y)] [] (0 == x) ++
bool [(x + 1, y)] [] (maxX == x) ++
bool [(x, y - 1)] [] (0 == y) ++ bool [(x, y + 1)] [] (maxY == y)
maxX = width - 1
maxY = height - 1
mazeArray =
newArray ((0, 0), (maxX, maxY)) :: Bool -> ST s (STArray s (Int, Int) Bool)
printMaze :: Maze -> IO ()
printMaze (Maze rWalls bWalls) = do
putStrLn $ '+' : concat (replicate (maxX + 1) "---+")
forM_ [0 .. maxY] $
\y -> do
putStr "|"
forM_ [0 .. maxX] $
\x -> do
putStr " "
putStr $ bool " " "|" (rWalls ! (x, y))
putStrLn ""
forM_ [0 .. maxX] $
\x -> do
putStr "+"
putStr $ bool " " "---" (bWalls ! (x, y))
putStrLn "+"
where
maxX = fst (snd $ bounds rWalls)
maxY = snd (snd $ bounds rWalls)
main :: IO ()
main = getStdGen >>= stToIO . maze 11 8 >>= printMaze
- Sample output:
+---+---+---+---+---+---+---+---+---+---+---+ | | | + +---+---+---+ +---+---+---+---+---+ + | | | | | | + +---+---+ +---+---+ + + + + + | | | | | | | | + + + +---+---+---+---+ +---+ + + | | | | | | +---+---+ + +---+---+---+---+ +---+---+ | | | | | | + + + + +---+---+---+ +---+ + + | | | | | | | + +---+---+ + +---+---+---+ +---+ + | | | | | + +---+---+---+---+ + +---+---+ + + | | | | +---+---+---+---+---+---+---+---+---+---+---+
Huginn
import Algorithms as algo;
import Mathematics as math;
import Terminal as term;
class Maze {
_rows = none;
_cols = none;
_data = none;
constructor( rows_, cols_ ) {
_rows = ( rows_ / 2 ) * 2 - 1;
_cols = ( cols_ / 2 ) * 2 - 1;
_data = [].resize( _rows + 2, [].resize( _cols + 2, false ) );
x = 0;
y = 0;
path = [];
rng = math.Randomizer( math.Randomizer.DISTRIBUTION.DISCRETE, 0, integer( $2 ^ $63 - $1 ) );
for ( _ : algo.range( _rows * _cols / 3 ) ) {
_data[y + 1][x + 1] = true;
while ( true ) {
n = neighbours( y, x );
ns = size( n );
if ( ns == 0 ) {
if ( size( path ) == 0 ) {
break;
}
y, x = path[-1];
path.pop();
continue;
}
oy, ox = ( y, x );
y, x = n[rng.next() % ns];
_data[(y + oy) / 2 + 1][(x + ox) / 2 + 1] = true;
path.push( ( y, x ) );
break;
}
}
_data[0][1] = true;
_data[-1][-2] = true;
}
neighbours( y_, x_ ) {
n = [];
if ( ( x_ > 1 ) && ! _data[y_ + 1][x_ - 1] ) {
n.push( ( y_, x_ - 2 ) );
}
if ( ( y_ > 1 ) && ! _data[y_ - 1][x_ + 1] ) {
n.push( ( y_ - 2, x_ ) );
}
if ( ( x_ < ( _cols - 2 ) ) && ! _data[y_ + 1][x_ + 3] ) {
n.push( ( y_, x_ + 2 ) );
}
if ( ( y_ < ( _rows - 2 ) ) && ! _data[y_ + 3][x_ + 1] ) {
n.push( ( y_ + 2, x_ ) );
}
return ( n );
}
to_string() {
s = "";
for ( r : _data ) {
s += ∑( algo.map( r, @( b ) { b ? " " : "#"; } ) );
s += "\n";
}
return ( s );
}
}
main() {
rows = term.lines() - 2;
cols = term.columns() - 1;
maze = Maze( rows, cols );
print( "{}".format( maze ) );
}
Icon and Unicon
![](http://static.miraheze.org/rosettacodewiki/thumb/0/00/Mazegen-unicon-20x30-1321112170.gif/300px-Mazegen-unicon-20x30-1321112170.gif)
![](http://static.miraheze.org/rosettacodewiki/thumb/3/32/Mazegen-unicon-20x30-1321060467.gif/300px-Mazegen-unicon-20x30-1321060467.gif)
Note: The underlying maze structure (matrix) is uni-directional from the start
printf.icn provides formatting
J
This algorithm allows almost no parallelism. So, while it might be "simple", generating very large mazes this way will not be necessarily efficient to implement on future (highly parallel) systems. That said, perhaps mazes with millions of cells are not very likely to be needed to be generated quickly.
But without any relevant grid library:
maze=:4 :0
assert.0<:n=.<:x*y
horiz=. 0$~x,y-1
verti=. 0$~(x-1),y
path=.,:here=. ?x,y
unvisited=.0 (<here+1)} 0,0,~|:0,0,~1$~y,x
while.n do.
neighbors=. here+"1 (,-)=0 1
neighbors=. neighbors #~ (<"1 neighbors+1) {unvisited
if.#neighbors do.
n=.n-1
next=. ({~ ?@#) neighbors
unvisited=.0 (<next+1)} unvisited
if.{.next=here
do. horiz=.1 (<-:here+next-0 1)} horiz
else. verti=. 1 (<-:here+next-1 0)} verti end.
path=.path,here=.next
else.
here=.{:path
path=.}:path
end.
end.
horiz;verti
)
display=:3 :0
size=. >.&$&>/y
text=. (}:1 3$~2*1+{:size)#"1":size$<' '
'hdoor vdoor'=. 2 4&*&.>&.> (#&,{@;&i./@$)&.> y
' ' (a:-.~0 1;0 2; 0 3;(2 1-~$text);(1 4&+&.> hdoor),,vdoor+&.>"0/2 1;2 2;2 3)} text
)
The result of maze
is a pair of arrays: one for open "doors" in the horizontal direction and the other for open "doors" in the vertical direction. The entry and exit doors are not represented by maze
-- they are implicitly defined and are implemented in display
. (The sequences of coordinates in display
are the relative coordinates for the doors. For example, 2 1;2 2;2 3
are where we put spaces for each vertical door. The variable text
is an ascii representation of the maze grid before the doors are placed.)
- Example use (with ascii box drawing enabled):
display 8 maze 11
+ +---+---+---+---+---+---+---+---+---+---+
| | | | |
+ + + + +---+ + +---+---+ + +
| | | | | | | |
+ +---+---+ + +---+---+---+ + + +
| | | | | | |
+---+ +---+ + + +---+ + +---+---+
| | | | | | |
+ + +---+---+ +---+ + +---+---+ +
| | | | | | | | |
+ +---+ + + + + +---+---+ + +
| | | | |
+ +---+---+---+---+---+---+---+ +---+ +
| | | | | | | | |
+ + + + + + + + +---+ + +
| | | | |
+---+---+---+---+---+---+---+---+---+---+---+
Java
package org.rosettacode;
import java.util.Collections;
import java.util.Arrays;
/*
* recursive backtracking algorithm
* shamelessly borrowed from the ruby at
* http://weblog.jamisbuck.org/2010/12/27/maze-generation-recursive-backtracking
*/
public class MazeGenerator {
private final int x;
private final int y;
private final int[][] maze;
public MazeGenerator(int x, int y) {
this.x = x;
this.y = y;
maze = new int[this.x][this.y];
generateMaze(0, 0);
}
public void display() {
for (int i = 0; i < y; i++) {
// draw the north edge
for (int j = 0; j < x; j++) {
System.out.print((maze[j][i] & 1) == 0 ? "+---" : "+ ");
}
System.out.println("+");
// draw the west edge
for (int j = 0; j < x; j++) {
System.out.print((maze[j][i] & 8) == 0 ? "| " : " ");
}
System.out.println("|");
}
// draw the bottom line
for (int j = 0; j < x; j++) {
System.out.print("+---");
}
System.out.println("+");
}
private void generateMaze(int cx, int cy) {
DIR[] dirs = DIR.values();
Collections.shuffle(Arrays.asList(dirs));
for (DIR dir : dirs) {
int nx = cx + dir.dx;
int ny = cy + dir.dy;
if (between(nx, x) && between(ny, y)
&& (maze[nx][ny] == 0)) {
maze[cx][cy] |= dir.bit;
maze[nx][ny] |= dir.opposite.bit;
generateMaze(nx, ny);
}
}
}
private static boolean between(int v, int upper) {
return (v >= 0) && (v < upper);
}
private enum DIR {
N(1, 0, -1), S(2, 0, 1), E(4, 1, 0), W(8, -1, 0);
private final int bit;
private final int dx;
private final int dy;
private DIR opposite;
// use the static initializer to resolve forward references
static {
N.opposite = S;
S.opposite = N;
E.opposite = W;
W.opposite = E;
}
private DIR(int bit, int dx, int dy) {
this.bit = bit;
this.dx = dx;
this.dy = dy;
}
};
public static void main(String[] args) {
int x = args.length >= 1 ? (Integer.parseInt(args[0])) : 8;
int y = args.length == 2 ? (Integer.parseInt(args[1])) : 8;
MazeGenerator maze = new MazeGenerator(x, y);
maze.display();
}
}
- Output:
+---+---+---+---+---+---+---+---+---+---+ | | | | + +---+---+ +---+---+ + + +---+ | | | | | | | +---+---+ + + + +---+ +---+ + | | | | | | | + +---+---+ +---+ + +---+ + + | | | | | | | + + + +---+ +---+---+---+ + + | | | | | | + + +---+ + +---+---+ +---+---+ | | | | | | | + +---+ + +---+ +---+---+ + + | | | | | | +---+ + +---+ + +---+---+---+ + | | | | | | | + + +---+ + +---+---+ +---+ + | | | | | | | | + +---+ + +---+---+ + + + + | | | | +---+---+---+---+---+---+---+---+---+---+
JavaScript
function maze(x,y) {
var n=x*y-1;
if (n<0) {alert("illegal maze dimensions");return;}
var horiz =[]; for (var j= 0; j<x+1; j++) horiz[j]= [],
verti =[]; for (var j= 0; j<x+1; j++) verti[j]= [],
here = [Math.floor(Math.random()*x), Math.floor(Math.random()*y)],
path = [here],
unvisited = [];
for (var j = 0; j<x+2; j++) {
unvisited[j] = [];
for (var k= 0; k<y+1; k++)
unvisited[j].push(j>0 && j<x+1 && k>0 && (j != here[0]+1 || k != here[1]+1));
}
while (0<n) {
var potential = [[here[0]+1, here[1]], [here[0],here[1]+1],
[here[0]-1, here[1]], [here[0],here[1]-1]];
var neighbors = [];
for (var j = 0; j < 4; j++)
if (unvisited[potential[j][0]+1][potential[j][1]+1])
neighbors.push(potential[j]);
if (neighbors.length) {
n = n-1;
next= neighbors[Math.floor(Math.random()*neighbors.length)];
unvisited[next[0]+1][next[1]+1]= false;
if (next[0] == here[0])
horiz[next[0]][(next[1]+here[1]-1)/2]= true;
else
verti[(next[0]+here[0]-1)/2][next[1]]= true;
path.push(here = next);
} else
here = path.pop();
}
return {x: x, y: y, horiz: horiz, verti: verti};
}
function display(m) {
var text= [];
for (var j= 0; j<m.x*2+1; j++) {
var line= [];
if (0 == j%2)
for (var k=0; k<m.y*4+1; k++)
if (0 == k%4)
line[k]= '+';
else
if (j>0 && m.verti[j/2-1][Math.floor(k/4)])
line[k]= ' ';
else
line[k]= '-';
else
for (var k=0; k<m.y*4+1; k++)
if (0 == k%4)
if (k>0 && m.horiz[(j-1)/2][k/4-1])
line[k]= ' ';
else
line[k]= '|';
else
line[k]= ' ';
if (0 == j) line[1]= line[2]= line[3]= ' ';
if (m.x*2-1 == j) line[4*m.y]= ' ';
text.push(line.join('')+'\r\n');
}
return text.join('');
}
Variable meanings in function maze
:
x
,y
— dimensions of mazen
— number of openings to be generatedhoriz
— two dimensional array of locations of horizontal openings (true means wall is open)verti
— two dimensional array of locations of vertical openings (true means wall is open)here
— current location under considerationpath
— history (stack) of locations that might need to be revisitedunvisited
— two dimensional array of locations that have not been visited, padded to avoid need for boundary tests (true means location needs to be visited)potential
— locations adjacent tohere
neighbors
— unvisited locations adjacent tohere
Variable meanings in function display
:
m
— maze to be drawntext
— lines of text representing mazeline
— characters of current line
Note that this implementation relies on javascript arrays being treatable as infinite in size with false (null) values springing into existence as needed, to support referenced array locations. (This significantly reduces the bulk of the necessary initialization code.)
- Example use:
<html><head><title></title></head><body><pre id="out"></pre></body></html>
<script type="text/javascript">
/* ABOVE CODE GOES HERE */
document.getElementById('out').innerHTML= display(maze(8,11));
</script>
produced output:
+ +---+---+---+---+---+---+---+---+---+---+ | | | | +---+---+ + +---+ + +---+---+ + + | | | | | | | | + + + +---+ +---+ +---+---+ + + | | | | | | | + +---+ +---+---+---+---+---+ + + + | | | | | | +---+ +---+ +---+---+ + +---+---+ + | | | | | | | + + + +---+---+---+---+---+ + + + | | | | | | + +---+---+ +---+---+ + +---+---+ + | | | | | | | + + + +---+ +---+---+ + + +---+ | | | | +---+---+---+---+---+---+---+---+---+---+---+
For an animated presentation of the progress of this maze creation process, you can use display
in each iteration of the main loop. You would also need to take steps to make sure you could see each intermediate result.
For example, change replace the line while (0<n) {
with:
function step() {
if (0<n) {
And replace the closing brace for this while loop with:
document.getElementById('out').innerHTML= display({x: x, y: y, horiz: horiz, verti: verti, here: here});
setTimeout(step, 100);
}
}
step();
To better see the progress, you might want a marker in place, showing the position being considered. To do that, replace the line which reads if (0 == k%4) {
with
if (m.here && m.here[0]*2+1 == j && m.here[1]*4+2 == k)
line[k]= '#'
else if (0 == k%4) {
Note however that this leaves the final '#' in place on maze completion, and that the function maze
no longer returns a result which represents a generated maze.
Note also that this display suggests an optimization. You can replace the line reading path.push(here= next);
with:
here= next;
if (1 < neighbors.length)
path.push(here);
And this does indeed save a negligible bit of processing, but the maze algorithm will still be forced to backtrack through a number of locations which have no unvisited neighbors.
HTML Table
Using HTML, CSS and table cells for maze.
<html><head><title>Maze maker</title>
<style type="text/css">
table { border-collapse: collapse }
td { width: 1em; height: 1em; border: 1px solid }
td.s { border-bottom: none }
td.n { border-top: none }
td.w { border-left: none }
td.e { border-right: none }
td.v { background: skyblue}
</style>
<script type="application/javascript">
Node.prototype.add = function(tag, cnt, txt) {
for (var i = 0; i < cnt; i++)
this.appendChild(ce(tag, txt));
}
Node.prototype.ins = function(tag) {
this.insertBefore(ce(tag), this.firstChild)
}
Node.prototype.kid = function(i) { return this.childNodes[i] }
Node.prototype.cls = function(t) { this.className += ' ' + t }
NodeList.prototype.map = function(g) {
for (var i = 0; i < this.length; i++) g(this[i]);
}
function ce(tag, txt) {
var x = document.createElement(tag);
if (txt !== undefined) x.innerHTML = txt;
return x
}
function gid(e) { return document.getElementById(e) }
function irand(x) { return Math.floor(Math.random() * x) }
function make_maze() {
var w = parseInt(gid('rows').value || 8, 10);
var h = parseInt(gid('cols').value || 8, 10);
var tbl = gid('maze');
tbl.innerHTML = '';
tbl.add('tr', h);
tbl.childNodes.map(function(x) {
x.add('th', 1);
x.add('td', w, '*');
x.add('th', 1)});
tbl.ins('tr');
tbl.add('tr', 1);
tbl.firstChild.add('th', w + 2);
tbl.lastChild.add('th', w + 2);
for (var i = 1; i <= h; i++) {
for (var j = 1; j <= w; j++) {
tbl.kid(i).kid(j).neighbors = [
tbl.kid(i + 1).kid(j),
tbl.kid(i).kid(j + 1),
tbl.kid(i).kid(j - 1),
tbl.kid(i - 1).kid(j)
];
}
}
walk(tbl.kid(irand(h) + 1).kid(irand(w) + 1));
gid('solve').style.display='inline';
}
function shuffle(x) {
for (var i = 3; i > 0; i--) {
j = irand(i + 1);
if (j == i) continue;
var t = x[j]; x[j] = x[i]; x[i] = t;
}
return x;
}
var dirs = ['s', 'e', 'w', 'n'];
function walk(c) {
c.innerHTML = ' ';
var idx = shuffle([0, 1, 2, 3]);
for (var j = 0; j < 4; j++) {
var i = idx[j];
var x = c.neighbors[i];
if (x.textContent != '*') continue;
c.cls(dirs[i]), x.cls(dirs[3 - i]);
walk(x);
}
}
function solve(c, t) {
if (c === undefined) {
c = gid('maze').kid(1).kid(1);
c.cls('v');
}
if (t === undefined)
t = gid('maze') .lastChild.previousSibling
.lastChild.previousSibling;
if (c === t) return 1;
c.vis = 1;
for (var i = 0; i < 4; i++) {
var x = c.neighbors[i];
if (x.tagName.toLowerCase() == 'th') continue;
if (x.vis || !c.className.match(dirs[i]) || !solve(x, t))
continue;
x.cls('v');
return 1;
}
c.vis = null;
return 0;
}
</script></head>
<body><form><fieldset>
<label>rows </label><input id='rows' size="3"/>
<label>colums </label><input id='cols' size="3"/>
<a href="javascript:make_maze()">Generate</a>
<a id='solve' style='display:none' href='javascript:solve(); void(0)'>Solve</a>
</fieldset></form><table id='maze'/></body></html>
jq
Adapted from Wren
Works with jq, the C implementation of jq
Works with gojq, the Go implementation of jq
Since jq does not have a builtin PRNG, it is assumed that /dev/urandom is available. An invocation such as the following may be used:
< /dev/urandom tr -cd '0-9' | fold -w 1 | jq -Rcnr -f maze-generation.jq
In the following, a maze is represented by a matrix, each of whose elements is in turn a JSON object representing the bounding box: $box["N"] is true iff the northern (top) edge is open, and similarly for the other points of the compass.
Note that in the following, the "walk" always starts at the (0,0) cell; this is just to make it clear that walk starts at the top left of the display.
# Output: a prn in range(0;$n) where $n is .
def prn:
if . == 1 then 0
else . as $n
| (($n-1)|tostring|length) as $w
| [limit($w; inputs)] | join("") | tonumber
| if . < $n then . else ($n | prn) end
end;
# Input: an array
def knuthShuffle:
length as $n
| if $n <= 1 then .
else {i: $n, a: .}
| until(.i == 0;
.i += -1
| (.i + 1 | prn) as $j
| .a[.i] as $t
| .a[.i] = .a[$j]
| .a[$j] = $t)
| .a
end;
# Compass is a JSON object {n,s,e,w} representing the four possible
# directions in which to move, i.e. to open a gate.
# For example, Compass.n corresponds to a move north (i.e. dx is 0, dy is 1),
# and Compass.n.gates["N"] is true indicating that the "northern" gate should be opened.
def Compass:
{n: { gates: {"N": true}, dx: 0, dy: -1},
s: { gates: {"S": true}, dx: 0, dy: 1},
e: { gates: {"E": true}, dx: 1, dy: 0},
w: { gates: {"W": true}, dx:-1, dy: 0} }
| .n.opposite = .s
| .s.opposite = .n
| .e.opposite = .w
| .w.opposite = .e
;
# Produce a matrix representing an $x x $y maze.
# .[$i][$j] represents the box in row $i, column $j.
# Initially, all the gates of all the boxes are closed.
def MazeMatrix($x; $y):
[range(0;$x) | {} ] as $v | [range(0;$y) | $v];
# Input: a MazeMatrix
def generate($cx; $cy):
def interior($a; $upper): $a >= 0 and $a < $upper;
length as $x
| (.[0]|length) as $y
| Compass as $Compass
| ([$Compass.n, $Compass.s, $Compass.e, $Compass.w] | knuthShuffle) as $directions
| reduce $directions[] as $v (.;
($cx + $v.dx) as $nx
| ($cy + $v.dy) as $ny
| if interior($nx; $x) and interior($ny; $y) and .[$nx][$ny] == {}
then .[$cx][$cy] += $v.gates
| .[$nx][$ny] += $v.opposite.gates
| generate($nx; $ny)
end );
# Input: a MazeMatrix
def display:
. as $maze
| ($maze|length) as $x
| ($maze[0]|length) as $y
| ( range(0;$y) as $i
# draw the north edge
| ([range(0;$x) as $j
| if $maze[$j][$i]["N"] then "+ " else "+---" end] | join("")) + "+",
# draw the west edge
([range(0;$x) as $j
| if $maze[$j][$i]["W"] then " " else "| " end] | join("")) + "|" ),
# draw the bottom line
($x * "+---") + "+"
;
# Start the walk at the top left
def amaze($x;$y):
MazeMatrix($x; $y)
| generate(0; 0)
| display;
# Example
amaze(4; 5);
- Output:
Example output:
+---+---+---+---+---+ | | | +---+ + +---+ + | | | | | + + + + + + | | | | | | + + +---+ + + | | | +---+---+---+---+---+
Julia
Generating functions
using Random
check(bound::Vector) = cell -> all([1, 1] .≤ cell .≤ bound)
neighbors(cell::Vector, bound::Vector, step::Int=2) =
filter(check(bound), map(dir -> cell + step * dir, [[0, 1], [-1, 0], [0, -1], [1, 0]]))
function walk(maze::Matrix, nxtcell::Vector, visited::Vector=[])
push!(visited, nxtcell)
for neigh in shuffle(neighbors(nxtcell, collect(size(maze))))
if neigh ∉ visited
maze[round.(Int, (nxtcell + neigh) / 2)...] = 0
walk(maze, neigh, visited)
end
end
maze
end
function maze(w::Int, h::Int)
maze = collect(i % 2 | j % 2 for i in 1:2w+1, j in 1:2h+1)
firstcell = 2 * [rand(1:w), rand(1:h)]
return walk(maze, firstcell)
end
Printing functions
pprint(matrix) = for i = 1:size(matrix, 1) println(join(matrix[i, :])) end
function printmaze(maze)
walls = split("╹ ╸ ┛ ╺ ┗ ━ ┻ ╻ ┃ ┓ ┫ ┏ ┣ ┳ ╋")
h, w = size(maze)
f = cell -> 2 ^ ((3cell[1] + cell[2] + 3) / 2)
wall(i, j) = if maze[i,j] == 0 " " else
walls[Int(sum(f, filter(x -> maze[x...] != 0, neighbors([i, j], [h, w], 1)) .- [[i, j]]))]
end
mazewalls = collect(wall(i, j) for i in 1:2:h, j in 1:w)
pprint(mazewalls)
end
printmaze(maze(10, 10))
- Output:
┏━━━━━┳━━━━━┳━━━━━━━┓ ┃ ╻ ╻ ┃ ╺━┓ ╹ ┏━━━┓ ┃ ┣━┛ ┃ ┗━┓ ┗━━━┛ ╻ ┃ ┃ ┃ ╺━┻━┓ ┃ ╺━┳━━━┛ ┃ ┃ ┃ ╺━┓ ┃ ┗━━━┛ ┏━━━┛ ┃ ┣━━━┛ ┣━━━━━┳━┛ ┏━━━┫ ┃ ┏━━━┛ ┏━╸ ┃ ╺━┛ ╻ ┃ ┣━┛ ┏━━━┛ ╻ ┣━━━━━┛ ┃ ┃ ┏━┛ ┏━━━┻━┛ ┏━━━┳━┫ ┃ ┃ ╺━┛ ╺━━━━━┛ ╻ ╹ ┃ ┗━┻━━━━━━━━━━━━━┻━━━┛
Kotlin
import java.util.*
class MazeGenerator(val x: Int, val y: Int) {
private val maze = Array(x) { IntArray(y) }
fun generate(cx: Int, cy: Int) {
Direction.values().shuffle().forEach {
val nx = cx + it.dx
val ny = cy + it.dy
if (between(nx, x) && between(ny, y) && maze[nx][ny] == 0) {
maze[cx][cy] = maze[cx][cy] or it.bit
maze[nx][ny] = maze[nx][ny] or it.opposite!!.bit
generate(nx, ny)
}
}
}
fun display() {
for (i in 0..y - 1) {
// draw the north edge
for (j in 0..x - 1)
print(if (maze[j][i] and 1 == 0) "+---" else "+ ")
println('+')
// draw the west edge
for (j in 0..x - 1)
print(if (maze[j][i] and 8 == 0) "| " else " ")
println('|')
}
// draw the bottom line
for (j in 0..x - 1) print("+---")
println('+')
}
inline private fun <reified T> Array<T>.shuffle(): Array<T> {
val list = toMutableList()
Collections.shuffle(list)
return list.toTypedArray()
}
private enum class Direction(val bit: Int, val dx: Int, val dy: Int) {
N(1, 0, -1), S(2, 0, 1), E(4, 1, 0),W(8, -1, 0);
var opposite: Direction? = null
companion object {
init {
N.opposite = S
S.opposite = N
E.opposite = W
W.opposite = E
}
}
}
private fun between(v: Int, upper: Int) = v >= 0 && v < upper
}
fun main(args: Array<String>) {
val x = if (args.size >= 1) args[0].toInt() else 8
val y = if (args.size == 2) args[1].toInt() else 8
with(MazeGenerator(x, y)) {
generate(0, 0)
display()
}
}
Lua
math.randomseed( os.time() )
-- Fisher-Yates shuffle from http://santos.nfshost.com/shuffling.html
function shuffle(t)
for i = 1, #t - 1 do
local r = math.random(i, #t)
t[i], t[r] = t[r], t[i]
end
end
-- builds a width-by-height grid of trues
function initialize_grid(w, h)
local a = {}
for i = 1, h do
table.insert(a, {})
for j = 1, w do
table.insert(a[i], true)
end
end
return a
end
-- average of a and b
function avg(a, b)
return (a + b) / 2
end
dirs = {
{x = 0, y = -2}, -- north
{x = 2, y = 0}, -- east
{x = -2, y = 0}, -- west
{x = 0, y = 2}, -- south
}
function make_maze(w, h)
w = w or 16
h = h or 8
local map = initialize_grid(w*2+1, h*2+1)
function walk(x, y)
map[y][x] = false
local d = { 1, 2, 3, 4 }
shuffle(d)
for i, dirnum in ipairs(d) do
local xx = x + dirs[dirnum].x
local yy = y + dirs[dirnum].y
if map[yy] and map[yy][xx] then
map[avg(y, yy)][avg(x, xx)] = false
walk(xx, yy)
end
end
end
walk(math.random(1, w)*2, math.random(1, h)*2)
local s = {}
for i = 1, h*2+1 do
for j = 1, w*2+1 do
if map[i][j] then
table.insert(s, '#')
else
table.insert(s, ' ')
end
end
table.insert(s, '\n')
end
return table.concat(s)
end
print(make_maze())
- Output:
################################# # # # # # # # # # ### # # ### ##### # ##### # # # # # # # # # # # # # # # # ########### ### # # ##### # # # # # # # # # # ### ##### # ### # # ####### # # # # # # # # # # # # # # # # ### # # # # # ######### ### # # # # # # # # # # # ### ### # ### ### # ####### # # # # # # # # # # # # # # # ### # ####### ##### # # ### # # # # # # # # # # # # # ### # ####### # # # ### # # # # # # # # # # #################################
M2000 Interpreter
Random Generation
For Next is not the same as basic. In M2000 always a loop perform once. Step converted to absolute value if start<>end. To go down we have to place start>end. If start=end then the value after the loop is equal to start+step and here step used as is (with no conversion to absolute value).
We can use the for loop as in basic using a software switch: see Help Switch from console. Here we have positive steps so we can translate nicely from Basic without the use of the switch.
Also there is another switch if we want a Dim a(10) to have 11 items, from 0 to 10. But here we set lower and upper index (we may use negative numbers too, when we use number to number for each dimension) Variables with % in name as last character are like integers, but inside can be any numeric type, so width%=40 is internal a double, width$=40@ is internal a decimal. When we assign a number then this number rounded to integer, where w%=1.5 is 2 and w%=-1.5 is -2. If w%=1 then the statement w%/=2 not changed the w% (internal go to 0.5 so rounded to 1).
We can use integers, say a long, so a statement Long a=1 make a=1 and a/=2 set a to 0.
INT((currentx% + oldx%) / 2) return a double, because has 2 as double so we get (integer+integer)/double or integer/double or double. Int(0.5) return.
Module Maze {
width% = 40
height% = 20
\\ we can use DIM maze$(0 to width%,0 to height%)="#"
\\ so we can delete the two For loops
DIM maze$(0 to width%,0 to height%)
FOR x% = 0 TO width%
FOR y% = 0 TO height%
maze$(x%, y%) = "#"
NEXT y%
NEXT x%
currentx% = INT(RND * (width% - 1))
currenty% = INT(RND * (height% - 1))
IF currentx% MOD 2 = 0 THEN currentx%++
IF currenty% MOD 2 = 0 THEN currenty%++
maze$(currentx%, currenty%) = " "
done% = 0
WHILE done% = 0 {
FOR i% = 0 TO 99
oldx% = currentx%
oldy% = currenty%
SELECT CASE INT(RND * 4)
CASE 0
IF currentx% + 2 < width% THEN currentx%+=2
CASE 1
IF currenty% + 2 < height% THEN currenty%+=2
CASE 2
IF currentx% - 2 > 0 THEN currentx%-=2
CASE 3
IF currenty% - 2 > 0 THEN currenty%-=2
END SELECT
IF maze$(currentx%, currenty%) = "#" Then {
maze$(currentx%, currenty%) = " "
maze$(INT((currentx% + oldx%) / 2), ((currenty% + oldy%) / 2)) = " "
}
NEXT i%
done% = 1
FOR x% = 1 TO width% - 1 STEP 2
FOR y% = 1 TO height% - 1 STEP 2
IF maze$(x%, y%) = "#" THEN done% = 0
NEXT y%
NEXT x%
}
FOR y% = 0 TO height%
FOR x% = 0 TO width%
PRINT maze$(x%, y%);
NEXT x%
PRINT
NEXT y%
}
Maze
Depth-first search
We use here the stack of values (it is heap based), which used for calls. Each module get the parent stack. A function has always a fresh stack. Using statement Stack New { } we use a new stack for that block, and at the exit the old stack return as current stack. Identifier [] is the stack as a stack object and work as command and do a swap of a fresh stack with the current one, so Array([]) leave current stack empty and return an array with all elements of stack. !arraypointer in a module place a copy of items to current stack, so when we call NewForChoose(!entry) we pass to stack by value all elements of entry (has always two). Subs have same scope with module, so we use local statement for local variables. Module's variables are local by default. A inner module has own scope, and can't see parent modules variables. Modules call other modules with same stack of values. Functions in expressions have own stack of variables. Modules ans subs can return values to stack of values. A Push 10 write 10 to top of stack. A Read X read the top of stack to X. A Data 10 write to the end of stack, so a stack can be used as FIFO also. A lambda function also has own scope, so we have to include w and h as closures, which are mutable copies of h and w.
Module Maze2 {
\\ depth-first search
Profiler
Form 80,50
let w=60, h=40
Double
\\center proportional text double size
Report 2, Format$("Maze {0}x{1}",w,h)
Normal
Refresh
Set Fast !
Dim maze$(1 to w+1, 1 to h+1)="#"
Include=Lambda w,h (a,b) ->a>=1 and a<=w and b>=1 and b<=h
Flush ' empty stack
if random(1,2)=1 then {
entry=(if(random(1,2)=1->2, w),Random(1, h/2)*2)
} else {
entry=(random(1,w/2)*2,If(Random(1,2)=1->2,h))
}
maze$(entry#val(0), entry#val(1))=" "
forchoose=(,)
Push Entry
do {
do {
NewForChoose(!entry)
status=len(forchoose)
if status>0 then {
status--
forchoose=forchoose#val(random(0,status))
Push forchoose
OpenDoor(!Entry, !forchoose)
Rem : ShowMaze()
} else exit
entry=forchoose
} Always
if empty then exit
Read entry
} Always
ShowMaze()
Print timecount/1000
Sub NewForChoose(x,y)
Local x1=x-2, x2=x+2, y1=y-2, y2=y+2, arr=(,)
Stack New {
if include(x1,y) then if Maze$(x1,y)<>" " Then push (x1, y)
if include(x2,y) then if Maze$(x2,y)<>" " Then push (x2, y)
if include(x,y1) then if Maze$(x,y1)<>" " Then push (x, y1)
if include(x,y2) then if Maze$(x,y2)<>" " Then push (x, y2)
forchoose= Array([])
}
End Sub
Sub OpenDoor(x1,y1, x2,y2)
Local i
if x1=x2 then {
y1+=y2<=>y1
for i=y1 to y2 {maze$(x1, i)=" " }
} Else {
x1+=x2<=>x1
for i=x1 to x2 {maze$(i, y1)=" "}
}
End Sub
Sub ShowMaze()
Refresh 5000
cls ,4 ' split screen - preserve lines form 0 to 3
Local i, j
For j=1 to h+1 { Print @(10) : for i=1 to w+1 {Print maze$(i,j);}:Print}
Print
Refresh 100
End Sub
}
Maze2
Mathematica /Wolfram Language
MazeGraphics[m_, n_] :=
Block[{$RecursionLimit = Infinity,
unvisited = Tuples[Range /@ {m, n}], maze},
maze = Graphics[{Line[{{#, # - {0, 1}}, {#, # - {1, 0}}}] & /@
unvisited,
Line[{{0, n}, {0, 0}, {m, 0}}]}]; {unvisited =
DeleteCases[unvisited, #];
Do[If[MemberQ[unvisited, neighbor],
maze = DeleteCases[
maze, {#,
neighbor - {1, 1}} | {neighbor, # - {1, 1}}, {5}]; #0@
neighbor], {neighbor,
RandomSample@{# + {0, 1}, # - {0, 1}, # + {1, 0}, # - {1,
0}}}]} &@RandomChoice@unvisited; maze];
maze = MazeGraphics[21, 13]
- Output:
Graph
Here I generate a maze as a graph. Vertices of the graph are cells and edges of the graph are removed walls. This version is mush faster and is convenient to solve.
MazeGraph[m_, n_] :=
Block[{$RecursionLimit = Infinity, grid = GridGraph[{m, n}],
unvisitedQ}, unvisitedQ[_] := True;
Graph[Range[m n], Reap[{unvisitedQ[#] = False;
Do[
If[unvisitedQ[neighbor],
Sow[# <-> neighbor]; #0@neighbor], {neighbor,
RandomSample@AdjacencyList[grid, #]}]} &@
RandomChoice@VertexList@grid][[2, 1]],
GraphLayout -> {"GridEmbedding", "Dimension" -> {m, n}}]];
maze = MazeGraph[13, 21]
- Output:
MATLAB / Octave
function M = makeMaze(n)
showProgress = false;
colormap([1,1,1;1,1,1;0,0,0]);
set(gcf,'color','w');
NoWALL = 0;
WALL = 2;
NotVISITED = -1;
VISITED = -2;
m = 2*n+3;
M = NotVISITED(ones(m));
offsets = [-1, m, 1, -m];
M([1 2:2:end end],:) = WALL;
M(:,[1 2:2:end end]) = WALL;
currentCell = sub2ind(size(M),3,3);
M(currentCell) = VISITED;
S = currentCell;
while (~isempty(S))
moves = currentCell + 2*offsets;
unvistedNeigbors = find(M(moves)==NotVISITED);
if (~isempty(unvistedNeigbors))
next = unvistedNeigbors(randi(length(unvistedNeigbors),1));
M(currentCell + offsets(next)) = NoWALL;
newCell = currentCell + 2*offsets(next);
if (any(M(newCell+2*offsets)==NotVISITED))
S = [S newCell];
end
currentCell = newCell;
M(currentCell) = VISITED;
else
currentCell = S(1);
S = S(2:end);
end
if (showProgress)
image(M-VISITED);
axis equal off;
drawnow;
pause(.01);
end
end
image(M-VISITED);
axis equal off;
Nim
import random, sequtils, strutils
randomize()
iterator randomCover[T](xs: openarray[T]): T =
var js = toSeq 0..xs.high
for i in countdown(js.high, 0):
let j = random(i)
swap(js[i], js[j])
for j in js:
yield xs[j]
const
w = 14
h = 10
var
vis = newSeqWith(h, newSeq[bool](w))
hor = newSeqWith(h+1, newSeqWith(w, "+---"))
ver = newSeqWith(h, newSeqWith(w, "| ") & "|")
proc walk(x, y: int) =
vis[y][x] = true
for p in [[x-1,y], [x,y+1], [x+1,y], [x,y-1]].randomCover:
if p[0] notin 0 ..< w or p[1] notin 0 ..< h or vis[p[1]][p[0]]: continue
if p[0] == x: hor[max(y, p[1])][x] = "+ "
if p[1] == y: ver[y][max(x, p[0])] = " "
walk p[0], p[1]
walk rand(0..<w), rand(0..<h)
for a,b in zip(hor, ver & @[""]).items:
echo join(a & "+\n" & b)
- Output:
Example output:
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | | | | | | | + + + + + + +---+ + + + + + + + | | | | | | | | | | | + + + +---+ +---+ +---+ +---+---+---+---+ + | | | | | | | | +---+ +---+ + + +---+---+---+---+---+ + +---+ | | | | | | | | | +---+---+ + +---+---+ + + + +---+---+ + + | | | | | | | | | | + +---+ +---+ +---+ + + +---+---+ +---+ + | | | | | | | | | | + +---+---+ + + + + +---+ + +---+ + + | | | | | | | | | | + + +---+ + + + +---+---+ +---+ + + + | | | | | | | | | + +---+---+---+---+---+---+ +---+---+ +---+ + + | | | | | | | | + + + + + + +---+---+ + + +---+---+ + | | | | | | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Node.js
This difers of the basic Javascript in that in NodeJS we take advantage of the asynchronous behaviour. This code was modified from the plain Javascript section to make it Asynchronous and able to run under strict mode.
'use strict';
/*
* Imported from http://rosettacode.org/wiki/Maze_generation#JavaScript
* Added asynchronous behaviour to the maze generation.
*
* Port by sigmasoldier
*/
/**
* Generates the maze asynchronously.
* @param {Number} x Width of the maze.
* @param {Number} y Height of the maze.
* @returns {Promise} finished when resolved.
*/
function maze(x,y) {
return new Promise((resolve, reject) => {
let n=x*y-1;
if (n<0) {
reject(new Error(`illegal maze dimensions (${x} x ${y} < 1)`));
} else {
let horiz =[]; for (let j= 0; j<x+1; j++) horiz[j]= [];
let verti =[]; for (let j= 0; j<x+1; j++) verti[j]= [];
let here = [Math.floor(Math.random()*x), Math.floor(Math.random()*y)];
let path = [here];
let unvisited = [];
for (let j = 0; j<x+2; j++) {
unvisited[j] = [];
for (let k= 0; k<y+1; k++)
unvisited[j].push(j>0 && j<x+1 && k>0 && (j != here[0]+1 || k != here[1]+1));
}
while (0<n) {
let potential = [[here[0]+1, here[1]], [here[0],here[1]+1],
[here[0]-1, here[1]], [here[0],here[1]-1]];
let neighbors = [];
for (let j = 0; j < 4; j++)
if (unvisited[potential[j][0]+1][potential[j][1]+1])
neighbors.push(potential[j]);
if (neighbors.length) {
n = n-1;
let next= neighbors[Math.floor(Math.random()*neighbors.length)];
unvisited[next[0]+1][next[1]+1]= false;
if (next[0] == here[0])
horiz[next[0]][(next[1]+here[1]-1)/2]= true;
else
verti[(next[0]+here[0]-1)/2][next[1]]= true;
path.push(here = next);
} else
here = path.pop();
}
resolve({x: x, y: y, horiz: horiz, verti: verti});
}
});
}
/**
* A mere way of generating text.
* The text (Since it can be large) is generated in a non-blocking way.
* @param {Object} m Maze object.
* @param {Stream} writeTo Optinally, include here a function to write to.
* @returns {Promise} finished when the text is generated.
*/
function display(m, writeTo) {
return new Promise((resolve, reject) => {
let text = [];
for (let j= 0; j<m.x*2+1; j++) {
let line = [];
if (0 == j%2)
for (let k=0; k<m.y*4+1; k++)
if (0 == k%4)
line[k] = '+';
else
if (j>0 && m.verti[j/2-1][Math.floor(k/4)])
line[k] = ' ';
else
line[k] = '-';
else
for (let k=0; k<m.y*4+1; k++)
if (0 == k%4)
if (k>0 && m.horiz[(j-1)/2][k/4-1])
line[k] = ' ';
else
line[k] = '|';
else
line[k] = ' ';
if (0 == j) line[1] = line[2] = line[3] = ' ';
if (m.x*2-1 == j) line[4*m.y]= ' ';
text.push(line.join('')+'\r\n');
}
const OUTPUT = text.join('');
if (typeof writeTo === 'function')
writeTo(OUTPUT);
resolve(OUTPUT);
});
}
module.exports = {
maze: maze,
display: display
}
- Example use:
Image that you have a main.js
file, to run then invoke in your shell node main.js
Here is a basic example of what your main file should contain:
'use strict';
const maze = require('./maze.js');
const X = 20,
Y = 20;
console.log(`Generating a maze of ${X} x ${Y}...`);
const origin = new Date().getTime();
maze.maze(X, Y).then((m) => {
const time = new Date().getTime() - origin;
console.log(`Done in ${time <= 1000 ? time+'ms' : Math.round(time/1000)+'s'}!`);
maze.display(m, console.log); //Here you can pass a given stream (ie: stream) and it's write function;
//An example could be: maze.display(m, stream.write);
}, (err) => console.error(err));
Sample Output:
$ node main.js Generating a maze of 10 x 10... Done in 3ms! + +---+---+---+---+---+---+---+---+---+ | | | + +---+---+ +---+ + +---+ + + | | | | | | | | + + + +---+ +---+---+ + + + | | | | | | | | | + + + + +---+---+ + + +---+ | | | | | | | +---+ + + +---+---+ +---+---+ + | | | | | | | +---+---+ +---+ + +---+ + + + | | | | | | | | + +---+---+ + + + +---+ + + | | | | | +---+---+---+---+ +---+ +---+---+ + | | | | | + +---+ + +---+---+---+---+ + + | | | | | | +---+ + + +---+ +---+---+---+ + | | | +---+---+---+---+---+---+---+---+---+---+
OCaml
let seen = Hashtbl.create 7
let mark t = Hashtbl.add seen t true
let marked t = Hashtbl.mem seen t
let walls = Hashtbl.create 7
let ord a b = if a <= b then (a,b) else (b,a)
let join a b = Hashtbl.add walls (ord a b) true
let joined a b = Hashtbl.mem walls (ord a b)
let () =
let nx = int_of_string Sys.argv.(1) in
let ny = int_of_string Sys.argv.(2) in
let shuffle lst =
let nl = List.map (fun c -> (Random.bits (), c)) lst in
List.map snd (List.sort compare nl) in
let get_neighbours (x,y) =
let lim n k = (0 <= k) && (k < n) in
let bounds (x,y) = lim nx x && lim ny y in
List.filter bounds [(x-1,y);(x+1,y);(x,y-1);(x,y+1)] in
let rec visit cell =
mark cell;
let check k =
if not (marked k) then (join cell k; visit k) in
List.iter check (shuffle (get_neighbours cell)) in
let print_maze () =
begin
for i = 1 to nx do print_string "+---";done; print_endline "+";
let line n j k l s t u =
for i = 0 to n do print_string (if joined (i,j) (i+k,j+l) then s else t) done;
print_endline u in
for j = 0 to ny-2 do
print_string "| ";
line (nx-2) j 1 0 " " "| " "|";
line (nx-1) j 0 1 "+ " "+---" "+";
done;
print_string "| ";
line (nx-2) (ny-1) 1 0 " " "| " "|";
for i = 1 to nx do print_string "+---";done; print_endline "+";
end in
Random.self_init();
visit (Random.int nx, Random.int ny);
print_maze ();
Output from 'ocaml gen_maze.ml 10 10':
+---+---+---+---+---+---+---+---+---+---+ | | | | + +---+ +---+ + +---+ +---+ + | | | | | | + + +---+ +---+---+ +---+---+ + | | | | | | + + +---+ + +---+ +---+---+---+ | | | | | | | +---+---+ + + +---+---+ +---+ + | | | | | + +---+---+ +---+---+---+---+ +---+ | | | | | + +---+---+---+ + +---+ +---+ + | | | | | | +---+---+---+ +---+---+ +---+ + + | | | | | | + +---+ +---+ + + + +---+ + | | | | | | | +---+ + +---+ +---+---+---+ + + | | | | +---+---+---+---+---+---+---+---+---+---+
Ol
; maze generation
(import (otus random!))
(define WIDTH 30)
(define HEIGHT 8)
(define maze
(map (lambda (?)
(repeat #b01111 WIDTH)) ; 0 - unvisited, 1111 - all walls exists
(iota HEIGHT)))
(define (at x y)
(list-ref (list-ref maze y) x))
(define (unvisited? x y)
(if (and (< -1 x WIDTH) (< -1 y HEIGHT))
(zero? (band (at x y) #b10000))))
(define neighbors '((-1 . 0) (0 . -1) (+1 . 0) (0 . +1)))
(define walls '( #b10111 #b11011 #b11101 #b11110))
(define antiwalls '( #b11101 #b11110 #b10111 #b11011))
(let loop ((x (rand! WIDTH)) (y (rand! HEIGHT)))
(list-set! (list-ref maze y) x (bor (at x y) #b10000))
(let try ()
(if (or
(unvisited? (- x 1) y) ; left
(unvisited? x (- y 1)) ; top
(unvisited? (+ x 1) y) ; right
(unvisited? x (+ y 1))) ; bottom
(let*((p (rand! 4))
(neighbor (list-ref neighbors p)))
(let ((nx (+ x (car neighbor)))
(ny (+ y (cdr neighbor))))
(if (unvisited? nx ny)
(let ((ncell (at nx ny)))
(list-set! (list-ref maze y) x (band (at x y) (list-ref walls p)))
(list-set! (list-ref maze ny) nx (band ncell (list-ref antiwalls p)))
(loop nx ny)))
(try))))))
; maze printing:
(display "+")
(for-each (lambda (?) (display "--+")) (iota WIDTH))
(print)
(for-each (lambda (l)
; left wall (always)
(display "|")
; draw right wall
(for-each (lambda (x)
(display " ")
(display (if (zero? (band x #b10)) " " "|")))
l)
(print)
(display "+")
; draw bottom wall
(for-each (lambda (x)
(display (if (zero? (band x #b01)) " " "--"))
(display "+"))
l)
(print))
maze)
(print)
- Sample 30 x 8 output:
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | | | | | | | | | | + + + + +--+ +--+ + +--+ +--+ +--+--+ +--+ +--+ +--+ + +--+ +--+ + +--+ + | | | | | | | | | | | | | | | | | | | +--+ + +--+ +--+ + + + + + +--+--+--+ + +--+ +--+ + +--+ +--+ +--+ + + + | | | | | | | | | | | | | | | | | | | | + +--+--+--+--+ + +--+--+ +--+ + +--+--+--+ + +--+ + +--+ +--+ +--+ + + + + | | | | | | | | | | | | | | | +--+ + +--+ +--+--+--+--+ + +--+ +--+--+ + +--+--+ + + +--+--+ + +--+--+ + + | | | | | | | | | | | | | | | | | | | | | + +--+ + + + + + + +--+ + +--+--+ + +--+ + +--+ + + + + +--+ +--+--+--+ | | | | | | | | | | | | | | | | | | +--+ + + +--+--+ +--+--+ +--+ +--+ +--+--+ +--+--+--+--+--+--+ + + + + +--+ + | | | | | | | | | | | | | | | + +--+--+--+--+ + + + + +--+--+ +--+ +--+--+--+--+ +--+--+ + + + +--+--+ + + | | | | | | | | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
Perl
use List::Util 'max';
my ($w, $h) = @ARGV;
$w ||= 26;
$h ||= 127;
my $avail = $w * $h;
# cell is padded by sentinel col and row, so I don't check array bounds
my @cell = (map([(('1') x $w), 0], 1 .. $h), [('') x ($w + 1)]);
my @ver = map([("| ") x $w], 1 .. $h);
my @hor = map([("+--") x $w], 0 .. $h);
sub walk {
my ($x, $y) = @_;
$cell[$y][$x] = '';
$avail-- or return; # no more bottles, er, cells
my @d = ([-1, 0], [0, 1], [1, 0], [0, -1]);
while (@d) {
my $i = splice @d, int(rand @d), 1;
my ($x1, $y1) = ($x + $i->[0], $y + $i->[1]);
$cell[$y1][$x1] or next;
if ($x == $x1) { $hor[ max($y1, $y) ][$x] = '+ ' }
if ($y == $y1) { $ver[$y][ max($x1, $x) ] = ' ' }
walk($x1, $y1);
}
}
walk(int rand $w, int rand $h); # generate
for (0 .. $h) { # display
print @{$hor[$_]}, "+\n";
print @{$ver[$_]}, "|\n" if $_ < $h;
}
Run as maze.pl [width] [height]
or use default dimensions.
- Sample 4 x 1 output:
+--+--+--+--+ | | +--+--+--+--+
Phix
Adapted a couple of techniques from the excellent D submission
(however this holds the grid as an array of complete lines)
with javascript_semantics -- -- grid is eg for w=3,h=2: {"+---+---+---+", -- ("wall") -- "| ? | ? | ? |", -- ("cell") -- "+---+---+---+", -- ("wall") -- "| ? | ? | ? |", -- ("cell") -- "+---+---+---+", -- ("wall") -- ""} -- (for a trailing \n) -- -- note those ?(x,y) are at [y*2][x*4-1], and we navigate -- using y=2..2*h (+/-2), x=3..w*4-1 (+/-4) directly. -- constant w = 11, h = 8 sequence wall = join(repeat("+",w+1),"---")&"\n", cell = join(repeat("|",w+1)," ? ")&"\n", grid = split(join(repeat(wall,h+1),cell),'\n') procedure amaze(integer x, integer y) grid[y][x] = ' ' -- mark cell visited sequence p = shuffle({{x-4,y},{x,y+2},{x+4,y},{x,y-2}}) for i=1 to length(p) do integer {nx,ny} = p[i] if nx>1 and nx<w*4 and ny>1 and ny<=2*h and grid[ny][nx]='?' then integer mx = (x+nx)/2 grid[(y+ny)/2][mx-1..mx+1] = ' ' -- knock down wall amaze(nx,ny) end if end for end procedure function heads() return rand(2)=1 -- 50:50 true(1)/false(0) end function integer {x,y} = {(rand(w)*4)-1,rand(h)*2} amaze(x,y) -- mark start pos grid[y][x] = '*' -- add a random door (heads=rhs/lhs, tails=top/btm) if heads() then grid[rand(h)*2][heads()*w*4-1] = ' ' else x = rand(w)*4-1 grid[heads()*h*2+1][x-1..x+1] = ' ' end if printf(1,"%s\n",join(grid,'\n'))
- Output:
+---+---+---+---+---+---+---+---+---+---+---+ | | | | + + +---+---+---+---+---+---+ + + + | | | | * | | | + +---+ + +---+---+---+ + +---+---+ | | | | | | + + +---+ +---+ +---+ +---+---+ + | | | | | | | | + +---+ + + +---+ + + +---+---+ | | | | | | | + + +---+---+---+ +---+---+---+---+ + | | | | | + +---+---+---+ +---+ + + +---+ + | | | | | | + +---+ + +---+---+---+---+---+ +---+ | | +---+---+---+---+---+---+---+---+---+---+---+
PHP
Code inspired by the D and Python solutions (with the implementation of backtracking, or sometimes it wouldn't work). Could have been done procedurally or fully OO (with cells as class too). A debug flag has been provided to allow following the inner workings. Works on PHP > 5.6.
<?php
class Maze
{
protected $width;
protected $height;
protected $grid;
protected $path;
protected $horWalls;
protected $vertWalls;
protected $dirs;
protected $isDebug;
public function __construct($x, $y, $debug = false)
{
$this->width = $x;
$this->height = $y;
$this->path = [];
$this->dirs = [ [0, -1], [0, 1], [-1, 0], [1, 0]]; // array of coordinates of N,S,W,E
$this->horWalls = []; // list of removed horizontal walls (---+)
$this->vertWalls = [];// list of removed vertical walls (|)
$this->isDebug = $debug; // debug flag
// generate the maze:
$this->generate();
}
protected function generate()
{
$this->initMaze(); // init the stack and an unvisited grid
// start from a random cell and then proceed recursively
$this->walk(mt_rand(0, $this->width-1), mt_rand(0, $this->height-1));
}
/**
* Actually prints the Maze, on stdOut. Put in a separate method to allow extensibility
* For simplicity sake doors are positioned on the north wall and east wall
*/
public function printOut()
{
$this->log("Horizontal walls: %s", json_encode($this->horWalls));
$this->log("Vertical walls: %s", json_encode($this->vertWalls));
$northDoor = mt_rand(0,$this->width-1);
$eastDoor = mt_rand(0, $this->height-1);
$str = '+';
for ($i=0;$i<$this->width;$i++) {
$str .= ($northDoor == $i) ? ' +' : '---+';
}
$str .= PHP_EOL;
for ($i=0; $i<$this->height; $i++) {
for ($j=0; $j<$this->width; $j++) {
$str .= (!empty($this->vertWalls[$j][$i]) ? $this->vertWalls[$j][$i] : '| ');
}
$str .= ($i == $eastDoor ? ' ' : '|').PHP_EOL.'+';
for ($j=0; $j<$this->width; $j++) {
$str .= (!empty($this->horWalls[$j][$i]) ? $this->horWalls[$j][$i] : '---+');
}
$str .= PHP_EOL;
}
echo $str;
}
/**
* Logs to stdOut if debug flag is enabled
*/
protected function log(...$params)
{
if ($this->isDebug) {
echo vsprintf(array_shift($params), $params).PHP_EOL;
}
}
private function walk($x, $y)
{
$this->log('Entering cell %d,%d', $x, $y);
// mark current cell as visited
$this->grid[$x][$y] = true;
// add cell to path
$this->path[] = [$x, $y];
// get list of all neighbors
$neighbors = $this->getNeighbors($x, $y);
$this->log("Valid neighbors: %s", json_encode($neighbors));
if(empty($neighbors)) {
// Dead end, we need now to backtrack, if there's still any cell left to be visited
$this->log("Start backtracking along path: %s", json_encode($this->path));
array_pop($this->path);
if (!empty($this->path)) {
$next = array_pop($this->path);
return $this->walk($next[0], $next[1]);
}
} else {
// randomize neighbors, as per request
shuffle($neighbors);
foreach ($neighbors as $n) {
$nextX = $n[0];
$nextY = $n[1];
if ($nextX == $x) {
$wallY = max($nextY, $y);
$this->log("New cell is on the same column (%d,%d), removing %d, (%d-1) horizontal wall", $nextX, $nextY, $x, $wallY);
$this->horWalls[$x][min($nextY, $y)] = " +";
}
if ($nextY == $y) {
$wallX = max($nextX, $x);
$this->log("New cell is on the same row (%d,%d), removing %d,%d vertical wall", $nextX, $nextY, $wallX, $y);
$this->vertWalls[$wallX][$y] = " ";
}
return $this->walk($nextX, $nextY);
}
}
}
/**
* Initialize an empty grid of $width * $height dimensions
*/
private function initMaze()
{
for ($i=0;$i<$this->width;$i++) {
for ($j = 0;$j<$this->height;$j++) {
$this->grid[$i][$j] = false;
}
}
}
/**
* @param int $x
* @param int $y
* @return array
*/
private function getNeighbors($x, $y)
{
$neighbors = [];
foreach ($this->dirs as $dir) {
$nextX = $dir[0] + $x;
$nextY = $dir[1] + $y;
if (($nextX >= 0 && $nextX < $this->width && $nextY >= 0 && $nextY < $this->height) && !$this->grid[$nextX][$nextY]) {
$neighbors[] = [$nextX, $nextY];
}
}
return $neighbors;
}
}
$maze = new Maze(10,10);
$maze->printOut();
- Output:
+---+ +---+---+---+---+---+---+---+---+ | | +---+---+---+---+---+---+---+ +---+ + | | | | | + +---+---+ + + +---+---+ +---+ | | | | | | | | + + + +---+---+ +---+ + + + | | | | | | | + +---+ + +---+ + +---+---+ + | | | | | | | +---+---+---+ + +---+ + + + + | | | | | + +---+---+---+---+ +---+---+---+---+ | | | +---+ + +---+---+---+---+---+ + + | | | | | | + +---+ + + + + +---+---+ + | | | | | | | | | + + +---+---+ + + + + +---+ | | | | +---+---+---+---+---+---+---+---+---+---+
Picat
main =>
gen_maze(8,8).
main([RStr,CStr]) =>
MaxR = to_int(RStr),
MaxC = to_int(CStr),
gen_maze(MaxR,MaxC).
gen_maze(MaxR,MaxC) =>
Maze = new_array(MaxR,MaxC),
foreach (R in 1..MaxR, C in 1..MaxC)
Maze[R,C] = 0
end,
gen_maze(Maze,MaxR,MaxC,1,1),
display_maze(Maze,MaxR,MaxC).
gen_maze(Maze,MaxR,MaxC,R,C) =>
Dirs = shuffle([left, right, up, down]),
foreach (Dir in Dirs)
dir(Dir,Dr,Dc,B,OB),
R1 := R+Dr,
C1 := C+Dc,
if (R1 >= 1 && R1 =< MaxR && C1 >= 1 && C1 =< MaxC && Maze[R1,C1] == 0) then
Maze[R,C] := Maze[R,C] \/ B,
Maze[R1,C1] := Maze[R1,C1] \/ OB,
gen_maze(Maze,MaxR,MaxC,R1,C1)
end
end.
% dir(direction, Dr, Dc, Bit, OppBit)
dir(left, 0, -1, 1, 2).
dir(right, 0, 1, 2, 1).
dir(up, -1, 0, 4, 8).
dir(down, 1, 0, 8, 4).
shuffle(Arr) = Res =>
foreach (_ in 1..10)
I1 = random() mod 4 + 1,
I2 = random() mod 4 + 1,
T := Arr[I1],
Arr[I1] := Arr[I2],
Arr[I2] := T
end,
Res = Arr.
display_maze(Maze,MaxR,MaxC) =>
foreach (R in 1..MaxR)
foreach (C in 1..MaxC)
printf("%s", cond(Maze[R,C] /\ 4 == 0, "+---", "+ "))
end,
println("+"),
foreach (C in 1..MaxC)
printf("%s", cond(Maze[R,C] /\ 1 == 0, "| ", " ")),
end,
println("|")
end,
foreach (C in 1..MaxC)
print("+---")
end,
println("+").
- Output:
+---+---+---+---+---+---+---+---+ | | | | +---+ + +---+---+ + + + | | | | | | | + +---+ + + +---+ + + | | | | | | +---+ + +---+ + + + + | | | | | | | | + + +---+ + +---+ + + | | | | | | + + + +---+---+---+---+ + | | | | | | + +---+---+ + + + +---+ | | | | | + + +---+ +---+---+---+ + | | | +---+---+---+---+---+---+---+---+
PicoLisp
This solution uses 'grid' from "lib/simul.l" to generate the two-dimensional structure.
(load "@lib/simul.l")
(de maze (DX DY)
(let Maze (grid DX DY)
(let Fld (get Maze (rand 1 DX) (rand 1 DY))
(recur (Fld)
(for Dir (shuffle '((west . east) (east . west) (south . north) (north . south)))
(with ((car Dir) Fld)
(unless (or (: west) (: east) (: south) (: north))
(put Fld (car Dir) This)
(put This (cdr Dir) Fld)
(recurse This) ) ) ) ) )
(for (X . Col) Maze
(for (Y . This) Col
(set This
(cons
(cons
(: west)
(or
(: east)
(and (= Y 1) (= X DX)) ) )
(cons
(: south)
(or
(: north)
(and (= X 1) (= Y DY)) ) ) ) ) ) )
Maze ) )
(de display (Maze)
(disp Maze 0 '((This) " ")) )
- Output:
: (display (maze 11 8)) + +---+---+---+---+---+---+---+---+---+---+ 8 | | | | + + + + + + +---+ +---+---+ + 7 | | | | | | | | | +---+ +---+---+ + + +---+ + + + 6 | | | | | | | | + +---+ +---+ +---+---+---+ + +---+ 5 | | | | | | +---+ +---+ +---+---+---+ +---+---+ + 4 | | | | | | | + +---+ +---+ +---+ + + +---+ + 3 | | | | | | | | + +---+---+ + + + + +---+ + + 2 | | | | | | | | | + + + +---+ + +---+ + +---+ + 1 | | | | +---+---+---+---+---+---+---+---+---+---+---+ a b c d e f g h i j k
PL/I
*process source attributes xref or(!);
mgg: Proc Options(main);
/* REXX ***************************************************************
* 04.09.2013 Walter Pachl translated from REXX version 3
**********************************************************************/
Dcl (MIN,MOD,RANDOM,REPEAT,SUBSTR) Builtin;
Dcl SYSIN STREAM INPUT;
Dcl print Print;
Dcl imax Bin Fixed(31) init(10);
Dcl jmax Bin Fixed(31) init(15);
Dcl seed Bin Fixed(31) init(4711);
Get File(sysin) Data(imax,jmax,seed);
Dcl ii Bin Fixed(31);
Dcl jj Bin Fixed(31);
Dcl id Bin Fixed(31);
Dcl jd Bin Fixed(31);
id=2*imax+1; /* vertical dimension of a.i.j */
jd=2*jmax+1; /* horizontal dimension of a.i.j */
Dcl c Char(2000) Var;
c=repeat('123456789'!!'abcdefghijklmnopqrstuvwxyz'!!
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',20);
Dcl x Bin Float(53);
x=random(seed);
Dcl ps Bin Fixed(31) Init(1); /* first position */
Dcl na Bin Fixed(31) Init(1); /* number of points used */
Dcl si Bin Fixed(31); /* loop to compute paths */
Begin;
Dcl a(id,jd) Bin Fixed(15);
Dcl p(imax,jmax) Char(1);
Dcl 1 pl(imax*jmax),
2 ic Bin Fixed(15),
2 jc Bin Fixed(15);
Dcl 1 np(imax*jmax),
2 ic Bin Fixed(15),
2 jc Bin Fixed(15);
Dcl 1 pos(imax*jmax),
2 ic Bin Fixed(15),
2 jc Bin Fixed(15);
Dcl npl Bin Fixed(31) Init(0);
a=1; /* mark all borders present */
p='.'; /* Initialize all grid points */
ii=rnd(imax); /* find a start position */
jj=rnd(jmax);
Do si=1 To 1000; /* Do Forever - see Leave */
Call path(ii,jj); /* compute a path starting at ii/jj */
If na=imax*jmax Then /* all points used */
Leave; /* we are done */
Call select_next(ii,jj); /* get a new start from a path*/
End;
Call show;
Return;
path: Procedure(ii,jj);
/**********************************************************************
* compute a path starting from point (ii,jj)
**********************************************************************/
Dcl ii Bin Fixed(31);
Dcl jj Bin Fixed(31);
Dcl nb Bin Fixed(31);
Dcl ch Bin Fixed(31);
Dcl pp Bin Fixed(31);
p(ii,jj)='1';
pos.ic(ps)=ii;
pos.jc(ps)=jj;
Do pp=1 to 50; /* compute a path of maximum length 50*/
nb=neighbors(ii,jj); /* number of free neighbors */
Select;
When(nb=1) /* just one */
Call advance((1),ii,jj); /* go for it */
When(nb>0) Do; /* more Than 1 */
ch=rnd(nb); /* choose one possibility */
Call advance(ch,ii,jj); /* and go for that */
End;
Otherwise /* none available */
Leave;
End;
End;
End;
neighbors: Procedure(i,j) Returns(Bin Fixed(31));
/**********************************************************************
* count the number of free neighbors of point (i,j)
**********************************************************************/
Dcl i Bin Fixed(31);
Dcl j Bin Fixed(31);
Dcl in Bin Fixed(31);
Dcl jn Bin Fixed(31);
Dcl nb Bin Fixed(31) Init(0);
in=i-1; If in>0 Then Call check(in,j,nb);
in=i+1; If in<=imax Then Call check(in,j,nb);
jn=j-1; If jn>0 Then Call check(i,jn,nb);
jn=j+1; If jn<=jmax Then Call check(i,jn,nb);
Return(nb);
End;
check: Procedure(i,j,n);
/**********************************************************************
* check if point (i,j) is free and note it as possible successor
**********************************************************************/
Dcl i Bin Fixed(31);
Dcl j Bin Fixed(31);
Dcl n Bin Fixed(31);
If p(i,j)='.' Then Do; /* point is free */
n+=1; /* number of free neighbors */
np.ic(n)=i; /* note it as possible choice */
np.jc(n)=j;
End;
End;
advance: Procedure(ch,ii,jj);
/**********************************************************************
* move to the next point of the current path
**********************************************************************/
Dcl ch Bin Fixed(31);
Dcl ii Bin Fixed(31);
Dcl jj Bin Fixed(31);
Dcl ai Bin Fixed(31);
Dcl aj Bin Fixed(31);
Dcl pii Bin Fixed(31) Init((ii));
Dcl pjj Bin Fixed(31) Init((jj));
Dcl z Bin Fixed(31);
ii=np.ic(ch);
jj=np.jc(ch);
ps+=1; /* position number */
pos.ic(ps)=ii; /* note its coordinates */
pos.jc(ps)=jj;
p(ii,jj)=substr(c,ps,1); /* mark the point as used */
ai=pii+ii; /* vertical border position */
aj=pjj+jj; /* horizontal border position */
a(ai,aj)=0; /* tear the border down */
na+=1; /* number of used positions */
z=npl+1; /* add the point to the list */
pl.ic(z)=ii; /* of follow-up start pos. */
pl.jc(z)=jj;
npl=z;
End;
show: Procedure;
/*********************************************************************
* Show the resulting maze
*********************************************************************/
Dcl i Bin Fixed(31);
Dcl j Bin Fixed(31);
Dcl ol Char(300) Var;
Put File(print) Edit('mgg',imax,jmax,seed)(Skip,a,3(f(4)));
Put File(print) Skip Data(na);
Do i=1 To id;
ol='';
Do j=1 To jd;
If mod(i,2)=1 Then Do; /* odd lines */
If a(i,j)=1 Then Do; /* border to be drawn */
If mod(j,2)=0 Then
ol=ol!!'---'; /* draw the border */
Else
ol=ol!!'+';
End;
Else Do; /* border was torn down */
If mod(j,2)=0 Then
ol=ol!!' '; /* blanks instead of border */
Else
ol=ol!!'+';
End;
End;
Else Do; /* even line */
If a(i,j)=1 Then Do;
If mod(j,2)=0 Then /* even column */
ol=ol!!' '; /* moving space */
Else /* odd column */
ol=ol!!'!'; /* draw the border */
End;
Else /* border was torn down */
ol=ol!!' '; /* blank instead of border */
End;
End;
Select;
When(i=6) substr(ol,11,1)='A';
When(i=8) substr(ol, 3,1)='B';
Otherwise;
End;
Put File(print) Edit(ol,i)(Skip,a,f(3));
End;
End;
select_next: Procedure(is,js);
/**********************************************************************
* look for a point to start the nnext path
**********************************************************************/
Dcl is Bin Fixed(31);
Dcl js Bin Fixed(31);
Dcl n Bin Fixed(31);
Dcl nb Bin Fixed(31);
Dcl s Bin Fixed(31);
Do Until(nb>0); /* loop until one is found */
n=npl; /* number of points recorded */
s=rnd(n); /* pick a random index */
is=pl.ic(s); /* its coordinates */
js=pl.jc(s);
nb=neighbors(is,js); /* count free neighbors */
If nb=0 Then Do; /* if there is none */
pl.ic(s)=pl.ic(n); /* remove this point */
pl.jc(s)=pl.jc(n);
npl-=1;
End;
End;
End;
rnd: Proc(n) Returns(Bin Fixed(31));
/*********************************************************************
* return a pseudo-random integer between 1 and n
*********************************************************************/
dcl (r,n) Bin Fixed(31);
r=min(random()*n+1,n);
Return(r);
End;
End;
End;
Output:
�mgg 5 15 43 NA= 75; +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1 ! ! ! ! ! 2 + + + +---+---+---+ +---+---+ +---+ + + + + 3 ! ! ! ! ! ! ! ! 4 + +---+---+ +---+---+---+---+ +---+ +---+---+---+---+ 5 ! A ! ! ! ! ! ! 6 +---+---+ +---+---+ + +---+---+ +---+ + +---+---+ 7 ! B ! ! ! ! ! ! 8 + + +---+ + + +---+---+---+---+---+---+ + + + 9 ! ! ! ! ! ! 10 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 11
POV-Ray
This POV-Ray solution uses an iterative rather than recursive approach because POV-Ray has a small stack for recursion (about 100 levels) and so the program would crash on even quite small mazes. With the iterative approach it works on very large mazes.
#version 3.7;
global_settings {
assumed_gamma 1
}
#declare Rows = 15;
#declare Cols = 17;
#declare Seed = seed(2); // A seed produces a fixed sequence of pseudorandom numbers
#declare Wall = prism {
0, 0.8, 7,
<0, -0.5>, <0.05, -0.45>, <0.05, 0.45>, <0, 0.5>,
<-0.05, 0.45>, <-0.05, -0.45>, <0, -0.5>
texture {
pigment {
brick colour rgb 1, colour rgb <0.8, 0.25, 0.1> // Colour mortar, colour brick
brick_size 3*<0.25, 0.0525, 0.125>
mortar 3*0.01 // Size of the mortar
}
normal { wrinkles 0.75 scale 0.01 }
finish { diffuse 0.9 phong 0.2 }
}
}
#macro Fisher_Yates_Shuffle(Stack, Start, Top)
#for (I, Top, Start+1, -1)
#local J = floor(rand(Seed)*I + 0.5);
#if (J != I) // Waste of time swapping an element with itself
#local Src_Row = Stack[I][0];
#local Src_Col = Stack[I][1];
#local Dst_Row = Stack[I][2];
#local Dst_Col = Stack[I][3];
#declare Stack[I][0] = Stack[J][0];
#declare Stack[I][1] = Stack[J][1];
#declare Stack[I][2] = Stack[J][2];
#declare Stack[I][3] = Stack[J][3];
#declare Stack[J][0] = Src_Row;
#declare Stack[J][1] = Src_Col;
#declare Stack[J][2] = Dst_Row;
#declare Stack[J][3] = Dst_Col;
#end
#end
#end
#macro Initialise(Visited, North_Walls, East_Walls)
#for (R, 0, Rows-1)
#for (C, 0, Cols-1)
#declare Visited[R][C] = false;
#declare North_Walls[R][C] = true;
#declare East_Walls[R][C] = true;
#end
#end
#end
#macro Push(Stack, Top, Src_Row, Src_Col, Dst_Row, Dst_Col)
#declare Top = Top + 1;
#declare Stack[Top][0] = Src_Row;
#declare Stack[Top][1] = Src_Col;
#declare Stack[Top][2] = Dst_Row;
#declare Stack[Top][3] = Dst_Col;
#end
#macro Generate_Maze(Visited, North_Walls, East_Walls)
#local Stack = array[Rows*Cols][4]; // 0: from row, 1: from col, 2: to row, 3: to col
#local Row = floor(rand(Seed)*(Rows-1) + 0.5); // Random start row
#local Col = floor(rand(Seed)*(Cols-1) + 0.5); // Random start column
#local Top = -1;
Push(Stack, Top, Row, Col, Row, Col)
#while (Top >= 0)
#declare Visited[Row][Col] = true;
#local Start = Top + 1;
#if (Row < Rows-1) // Add north neighbour
#if (Visited[Row+1][Col] = false)
Push(Stack, Top, Row, Col, Row+1, Col)
#end
#end
#if (Col < Cols-1) // Add east neighbour
#if (Visited[Row][Col+1] = false)
Push(Stack, Top, Row, Col, Row, Col+1)
#end
#end
#if (Row > 0) // Add south neighbour
#if (Visited[Row-1][Col] = false)
Push(Stack, Top, Row, Col, Row-1, Col)
#end
#end
#if (Col > 0) // Add west neighbour
#if (Visited[Row][Col-1] = false)
Push(Stack, Top, Row, Col, Row, Col-1)
#end
#end
Fisher_Yates_Shuffle(Stack, Start, Top)
#local Removed_Wall = false;
#while (Top >= 0 & Removed_Wall = false)
#local Src_Row = Stack[Top][0];
#local Src_Col = Stack[Top][1];
#local Dst_Row = Stack[Top][2];
#local Dst_Col = Stack[Top][3];
#declare Top = Top - 1;
#if (Visited[Dst_Row][Dst_Col] = false)
#if (Dst_Row = Src_Row+1 & Dst_Col = Src_Col) // North wall
#declare North_Walls[Src_Row][Src_Col] = false;
#elseif (Dst_Row = Src_Row & Dst_Col = Src_Col+1) // East wall
#declare East_Walls[Src_Row][Src_Col] = false;
#elseif (Dst_Row = Src_Row-1 & Dst_Col = Src_Col) // South wall
#declare North_Walls[Dst_Row][Src_Col] = false;
#elseif (Dst_Row = Src_Row & Dst_Col = Src_Col-1) // West wall
#declare East_Walls[Src_Row][Dst_Col] = false;
#else
#error "Unknown wall!\n"
#end
#declare Row = Dst_Row;
#declare Col = Dst_Col;
#declare Removed_Wall = true;
#end
#end
#end
#end
#macro Draw_Maze(North_Walls, East_Walls)
merge {
#for (R, 0, Rows-1)
object { Wall translate <1, 0, 0.5> translate <-1, 0, R> } // West edge
#for (C, 0, Cols-1)
#if (R = 0) // South edge
object { Wall rotate y*90 translate <0.5, 0, 1> translate <C, 0, -1> }
#end
#if (North_Walls[R][C])
object { Wall rotate y*90 translate <0.5, 0, 1> translate <C, 0, R> }
#end
#if (East_Walls[R][C])
object { Wall translate <1, 0, 0.5> translate <C, 0, R> }
#end
#end
#end
translate <-0.5*Cols, 0, 0> // Centre maze on x=0
}
#end
camera {
location <0, 13, -2>
right x*image_width/image_height
look_at <0, 2, 5>
}
light_source {
<-0.1*Cols, Rows, 0.5*Rows>, colour rgb <1, 1, 1>
area_light
x, y, 3, 3
circular orient
}
box {
<-0.5*Cols, -0.1, 0>, <0.5*Cols, 0, Rows>
texture {
pigment {
checker colour rgb <0, 0.3, 0> colour rgb <0, 0.4, 0>
scale 0.5
}
finish { specular 0.5 reflection { 0.1 } }
}
}
#declare Visited = array[Rows][Cols];
#declare North_Walls = array[Rows][Cols];
#declare East_Walls = array[Rows][Cols];
Initialise(Visited, North_Walls, East_Walls)
Generate_Maze(Visited, North_Walls, East_Walls)
Draw_Maze(North_Walls, East_Walls)
- Output:
![POV-Ray maze with red brick walls](http://static.miraheze.org/rosettacodewiki/1/14/Povray_maze_solution_640px.png)
Processing
int g_size = 10;
color background_color = color (80, 80, 220);
color runner = color (255, 50, 50);
color visited_color = color(220, 240, 240);
color done_color = color (100, 160, 250);
int c_size;
Cell[][] cell;
ArrayList<Cell> done = new ArrayList<Cell>();
ArrayList<Cell> visit = new ArrayList<Cell>();
Cell run_cell;
void setup() {
size(600, 600);
frameRate(20);
smooth(4);
strokeCap(ROUND);
c_size = max(width/g_size, height/g_size);
cell = new Cell[g_size][g_size];
for (int i = 0; i < g_size; i++) {
for (int j = 0; j < g_size; j++) {
cell[i][j] = new Cell(i, j);
}
}
for (int i = 0; i < g_size; i++) {
for (int j = 0; j < g_size; j++) {
cell[i][j].add_neighbor();
}
}
run_cell = cell[0][0];
visit.add(run_cell);
}
void draw() {
background(background_color);
for (int i = 0; i < g_size; i++) {
for (int j = 0; j < g_size; j++) {
cell[i][j].draw_cell();
cell[i][j].draw_wall();
}
}
if (visit.size() < g_size*g_size) {
if (run_cell.check_sides()) {
Cell chosen = run_cell.pick_neighbor();
done.add(run_cell);
run_cell.stacked = true;
if (chosen.i - run_cell.i == 1) {
run_cell.wall[1] = false;
chosen.wall[3] = false;
} else if (chosen.i - run_cell.i == -1) {
run_cell.wall[3] = false;
chosen.wall[1] = false;
} else if (chosen.j - run_cell.j == 1) {
run_cell.wall[2] = false;
chosen.wall[0] = false;
} else {
run_cell.wall[0] = false;
chosen.wall[2] = false;
}
run_cell.current = false;
run_cell = chosen;
run_cell.current = true;
run_cell.visited = true;
} else if (done.size()>0) {
run_cell.current = false;
run_cell = done.remove(done.size()-1);
run_cell.stacked = false;
run_cell.current = true;
}
}
}
class Cell {
ArrayList<Cell> neighbor;
boolean visited, stacked, current;
boolean[] wall;
int i, j;
Cell(int _i, int _j) {
i = _i;
j = _j;
wall = new boolean[]{true,true,true,true};
}
Cell pick_neighbor() {
ArrayList<Cell> unvisited = new ArrayList<Cell>();
for(int i = 0; i < neighbor.size(); i++){
Cell nb = neighbor.get(i);
if(nb.visited == false) unvisited.add(nb);
}
return unvisited.get(floor(random(unvisited.size())));
}
void add_neighbor() {
neighbor = new ArrayList<Cell>();
if(i>0){neighbor.add(cell[i-1][j]);}
if(i<g_size-1){neighbor.add(cell[i+1][j]);}
if(j>0){neighbor.add(cell[i][j-1]);}
if(j<g_size-1){neighbor.add(cell[i][j+1]);}
}
boolean check_sides() {
for(int i = 0; i < neighbor.size(); i++){
Cell nb = neighbor.get(i);
if(!nb.visited) return true;
}
return false;
}
void draw_cell() {
noStroke();
noFill();
if(current) fill(runner);
else if(stacked) fill(done_color);
else if(visited) fill(visited_color);
rect(j*c_size,i*c_size,c_size,c_size);
}
void draw_wall() {
stroke(0);
strokeWeight(5);
if(wall[0]) line(j*c_size, i*c_size, j*c_size, (i+1)* c_size);
if(wall[1]) line(j*c_size, (i+1)*c_size, (j+1)*c_size, (i+1)*c_size);
if(wall[2]) line((j+1)*c_size, (i+1)*c_size, (j+1)*c_size, i*c_size);
if(wall[3]) line((j+1)*c_size, i*c_size, j*c_size, i*c_size);
}
}
It can be played on line :
here.
Processing Python mode
g_size = 10
background_color = color(80, 80, 220)
runner = color(255, 50, 50)
visited_color = color(220, 240, 240)
done_color = color(100, 160, 250)
def setup():
global cell, done, visit, run_cell, c_size
size(600, 600)
frameRate(20)
smooth(4)
strokeCap(ROUND)
c_size = max(width / g_size, height / g_size)
cell = [[None] * g_size for _ in range(g_size)]
for i in range(g_size):
for j in range(g_size):
cell[i][j] = Cell(i, j)
for i in range(g_size):
for j in range(g_size):
cell[i][j].add_neighbor()
run_cell = cell[0][0]
visit, done = [], []
visit.append(run_cell)
def draw():
global run_cell
background(background_color)
for i in range(g_size):
for j in range(g_size):
cell[i][j].draw_cell()
cell[i][j].draw_wall()
if len(visit) < g_size * g_size:
if run_cell.check_sides():
chosen = run_cell.pick_neighbor()
done.append(run_cell)
run_cell.stacked = True
if chosen.i - run_cell.i == 1:
run_cell.wall[1] = False
chosen.wall[3] = False
elif chosen.i - run_cell.i == -1:
run_cell.wall[3] = False
chosen.wall[1] = False
elif chosen.j - run_cell.j == 1:
run_cell.wall[2] = False
chosen.wall[0] = False
else:
run_cell.wall[0] = False
chosen.wall[2] = False
run_cell.current = False
run_cell = chosen
run_cell.current = True
run_cell.visited = True
elif done:
run_cell.current = False
run_cell = done.pop()
run_cell.stacked = False
run_cell.current = True
class Cell:
def __init__(self, i, j):
self.i = i
self.j = j
self.wall = [True, True, True, True]
self.visited = False
self.stacked = False
self.current = False
def pick_neighbor(self):
from random import choice
unvisited = [nb for nb in self.neighbor
if nb.visited == False]
return choice(unvisited)
def add_neighbor(self):
i, j = self.i, self.j
neighbor = []
if i > 0:
neighbor.append(cell[i - 1][j])
if i < g_size - 1:
neighbor.append(cell[i + 1][j])
if j > 0:
neighbor.append(cell[i][j - 1])
if j < g_size - 1:
neighbor.append(cell[i][j + 1])
self.neighbor = neighbor
def check_sides(self):
for nb in self.neighbor:
if not nb.visited:
return True
return False
def draw_cell(self):
s = c_size
noStroke()
noFill()
if self.current:
fill(runner)
elif self.stacked:
fill(done_color)
elif self.visited:
fill(visited_color)
rect(self.j * s, self.i * s, s, s)
def draw_wall(self):
i, j = self.i, self.j
wall = self.wall
stroke(0)
strokeWeight(5)
if wall[0]: line(j * c_size, i * c_size, j * c_size, (i + 1) * c_size)
if wall[1]: line(j * c_size, (i + 1) * c_size, (j + 1) * c_size, (i + 1) * c_size)
if wall[2]: line((j + 1) * c_size, (i + 1) * c_size, (j + 1) * c_size, i * c_size)
if wall[3]: line((j + 1) * c_size, i * c_size, j * c_size, i * c_size)
Prolog
Works with SWI-Prolog and XPCE.
:- dynamic cell/2.
maze(Lig,Col) :-
retractall(cell(_,_)),
new(D, window('Maze')),
% creation of the grid
forall(between(0,Lig, I),
(XL is 50, YL is I * 30 + 50,
XR is Col * 30 + 50,
new(L, line(XL, YL, XR, YL)),
send(D, display, L))),
forall(between(0,Col, I),
(XT is 50 + I * 30, YT is 50,
YB is Lig * 30 + 50,
new(L, line(XT, YT, XT, YB)),
send(D, display, L))),
SX is Col * 30 + 100,
SY is Lig * 30 + 100,
send(D, size, new(_, size(SX, SY))),
% choosing a first cell
L0 is random(Lig),
C0 is random(Col),
assert(cell(L0, C0)),
\+search(D, Lig, Col, L0, C0),
send(D, open).
search(D, Lig, Col, L, C) :-
Dir is random(4),
nextcell(Dir, Lig, Col, L, C, L1, C1),
assert(cell(L1,C1)),
assert(cur(L1,C1)),
erase_line(D, L, C, L1, C1),
search(D, Lig, Col, L1, C1).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
erase_line(D, L, C, L, C1) :-
( C < C1 -> C2 = C1; C2 = C),
XT is C2 * 30 + 50,
YT is L * 30 + 51, YR is (L+1) * 30 + 50,
new(Line, line(XT, YT, XT, YR)),
send(Line, colour, white),
send(D, display, Line).
erase_line(D, L, C, L1, C) :-
XT is 51 + C * 30, XR is 50 + (C + 1) * 30,
( L < L1 -> L2 is L1; L2 is L),
YT is L2 * 30 + 50,
new(Line, line(XT, YT, XR, YT)),
send(Line, colour, white),
send(D, display, Line).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
nextcell(Dir, Lig, Col, L, C, L1, C1) :-
next(Dir, Lig, Col, L, C, L1, C1);
( Dir1 is (Dir+3) mod 4,
next(Dir1, Lig, Col, L, C, L1, C1));
( Dir2 is (Dir+1) mod 4,
next(Dir2, Lig, Col, L, C, L1, C1));
( Dir3 is (Dir+2) mod 4,
next(Dir3, Lig, Col, L, C, L1, C1)).
% 0 => northward
next(0, _Lig, _Col, L, C, L1, C) :-
L > 0,
L1 is L - 1,
\+cell(L1, C).
% 1 => rightward
next(1, _Lig, Col, L, C, L, C1) :-
C < Col - 1,
C1 is C + 1,
\+cell(L, C1).
% 2 => southward
next(2, Lig, _Col, L, C, L1, C) :-
L < Lig - 1,
L1 is L + 1,
\+cell(L1, C).
% 3 => leftward
next(2, _Lig, _Col, L, C, L, C1) :-
C > 0,
C1 is C - 1,
\+cell(L, C1).
- Output:
PureBasic
Enumeration
;indexes for types of offsets from maze coordinates (x,y)
#visited ;used to index visited(x,y) in a given direction from current maze cell
#maze ;used to index maze() in a given direction from current maze cell
#wall ;used to index walls in maze() in a given direction from current maze cell
#numOffsets = #wall
;direction indexes
#dir_ID = 0 ;identity value, produces no changes
#firstDir
#dir_N = #firstDir
#dir_E
#dir_S
#dir_W
#numDirs = #dir_W
EndEnumeration
DataSection
;maze(x,y) offsets for visited, maze, & walls for each direction
Data.i 1, 1, 0, 0, 0, 0 ;ID
Data.i 1, 0, 0, -1, 0, 0 ;N
Data.i 2, 1, 1, 0, 1, 0 ;E
Data.i 1, 2, 0, 1, 0, 1 ;S
Data.i 0, 1, -1, 0, 0, 0 ;W
Data.i %00, %01, %10, %01, %10 ;wall values for ID, N, E, S, W
EndDataSection
#cellDWidth = 4
Structure mazeOutput
vWall.s
hWall.s
EndStructure
;setup reference values indexed by type and direction from current map cell
Global Dim offset.POINT(#numOffsets, #numDirs)
Define i, j
For i = 0 To #numDirs
For j = 0 To #numOffsets
Read.i offset(j, i)\x: Read.i offset(j, i)\y
Next
Next
Global Dim wallvalue(#numDirs)
For i = 0 To #numDirs: Read.i wallvalue(i): Next
Procedure makeDisplayMazeRow(Array mazeRow.mazeOutput(1), Array maze(2), y)
;modify mazeRow() to produce output of 2 strings showing the vertical walls above and horizontal walls across a given maze row
Protected x, vWall.s, hWall.s
Protected mazeWidth = ArraySize(maze(), 1), mazeHeight = ArraySize(maze(), 2)
vWall = "": hWall = ""
For x = 0 To mazeWidth
If maze(x, y) & wallvalue(#dir_N): vWall + "+ ": Else: vWall + "+---": EndIf
If maze(x, y) & wallvalue(#dir_W): hWall + " ": Else: hWall + "| ": EndIf
Next
mazeRow(0)\vWall = Left(vWall, mazeWidth * #cellDWidth + 1)
If y <> mazeHeight: mazeRow(0)\hWall = Left(hWall, mazeWidth * #cellDWidth + 1): Else: mazeRow(0)\hWall = "": EndIf
EndProcedure
Procedure displayMaze(Array maze(2))
Protected x, y, vWall.s, hWall.s, mazeHeight = ArraySize(maze(), 2)
Protected Dim mazeRow.mazeOutput(0)
For y = 0 To mazeHeight
makeDisplayMazeRow(mazeRow(), maze(), y)
PrintN(mazeRow(0)\vWall): PrintN(mazeRow(0)\hWall)
Next
EndProcedure
Procedure generateMaze(Array maze(2), mazeWidth, mazeHeight)
Dim maze(mazeWidth, mazeHeight) ;Each cell specifies walls present above and to the left of it,
;array includes an extra row and column for the right and bottom walls
Dim visited(mazeWidth + 1, mazeHeight + 1) ;Each cell represents a cell of the maze, an extra line of cells are included
;as padding around the representative cells for easy border detection
Protected i
;mark outside border as already visited (off limits)
For i = 0 To mazeWidth
visited(i + offset(#visited, #dir_N)\x, 0 + offset(#visited, #dir_N)\y) = #True
visited(i + offset(#visited, #dir_S)\x, mazeHeight - 1 + offset(#visited, #dir_S)\y) = #True
Next
For i = 0 To mazeHeight
visited(0 + offset(#visited, #dir_W)\x, i + offset(#visited, #dir_W)\y) = #True
visited(mazeWidth - 1 + offset(#visited, #dir_E)\x, i + offset(#visited, #dir_E)\y) = #True
Next
;generate maze
Protected x = Random(mazeWidth - 1), y = Random (mazeHeight - 1), cellCount, nextCell
visited(x + offset(#visited, #dir_ID)\x, y + offset(#visited, #dir_ID)\y) = #True
PrintN("Maze of size " + Str(mazeWidth) + " x " + Str(mazeHeight) + ", generation started at " + Str(x) + " x " + Str(y))
NewList stack.POINT()
Dim unvisited(#numDirs - #firstDir)
Repeat
cellCount = 0
For i = #firstDir To #numDirs
If Not visited(x + offset(#visited, i)\x, y + offset(#visited, i)\y)
unvisited(cellCount) = i: cellCount + 1
EndIf
Next
If cellCount
nextCell = unvisited(Random(cellCount - 1))
visited(x + offset(#visited, nextCell)\x, y + offset(#visited, nextCell)\y) = #True
maze(x + offset(#wall, nextCell)\x, y + offset(#wall, nextCell)\y) | wallvalue(nextCell)
If cellCount > 1
AddElement(stack())
stack()\x = x: stack()\y = y
EndIf
x + offset(#maze, nextCell)\x: y + offset(#maze, nextCell)\y
ElseIf ListSize(stack()) > 0
x = stack()\x: y = stack()\y
DeleteElement(stack())
Else
Break ;end maze generation
EndIf
ForEver
; ;mark random entry and exit point
; x = Random(mazeWidth - 1): y = Random(mazeHeight - 1)
; maze(x, 0) | wallvalue(#dir_N): maze(mazeWidth, y) | wallvalue(#dir_E)
ProcedureReturn
EndProcedure
If OpenConsole()
Dim maze(0, 0)
Define mazeWidth = Random(5) + 7: mazeHeight = Random(5) + 3
generateMaze(maze(), mazeWidth, mazeHeight)
displayMaze(maze())
Print(#CRLF$ + #CRLF$ + "Press ENTER to exit"): Input()
CloseConsole()
EndIf
The maze is represented by an array of cells where each cell indicates the walls present above (#dir_N) and to its left (#dir_W). Maze generation is done with a additional array marking the visited cells. Neither an entry nor an exit are created, these were not part of the task. A simple means of doing so is included but has been commented out.
- Sample output:
Maze of size 11 x 8, generation started at 9 x 3 +---+---+---+---+---+---+---+---+---+---+---+ | | | | | + + +---+ + +---+ + +---+ + + | | | | | | | | + +---+ +---+---+ +---+ + +---+---+ | | | | | | + + +---+---+ +---+---+---+---+---+ + | | | | | | + +---+ +---+ + +---+ + +---+---+ | | | | | | +---+---+---+ + + +---+---+ +---+ + | | | | | | + +---+---+ +---+ +---+---+ + +---+ | | | | | | | + + + +---+---+---+ + +---+---+ + | | | | +---+---+---+---+---+---+---+---+---+---+---+
Python
from random import shuffle, randrange
def make_maze(w = 16, h = 8):
vis = [[0] * w + [1] for _ in range(h)] + [[1] * (w + 1)]
ver = [["| "] * w + ['|'] for _ in range(h)] + [[]]
hor = [["+--"] * w + ['+'] for _ in range(h + 1)]
def walk(x, y):
vis[y][x] = 1
d = [(x - 1, y), (x, y + 1), (x + 1, y), (x, y - 1)]
shuffle(d)
for (xx, yy) in d:
if vis[yy][xx]: continue
if xx == x: hor[max(y, yy)][x] = "+ "
if yy == y: ver[y][max(x, xx)] = " "
walk(xx, yy)
walk(randrange(w), randrange(h))
s = ""
for (a, b) in zip(hor, ver):
s += ''.join(a + ['\n'] + b + ['\n'])
return s
if __name__ == '__main__':
print(make_maze())
- Output:
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | | | | | | + + + + + + + + +--+--+--+--+--+ +--+ + | | | | | | | | | | +--+ +--+--+ + +--+--+--+ + +--+ +--+--+ + | | | | | | | | | | + +--+ +--+ + + + + + +--+ + + +--+--+ | | | | | | | | | | + +--+ +--+--+ + +--+ +--+--+ +--+--+ + + | | | | | | | | +--+ + + +--+--+--+ +--+--+--+--+--+--+--+ + | | | | | | | | + +--+--+ +--+--+ +--+--+ +--+ +--+ + + + | | | | | | | | + +--+ +--+--+--+ + +--+--+--+--+ +--+ + + | | | | | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
Racket
Maze generator
#lang racket
;; the structure representing a maze of size NxM
(struct maze (N M tbl))
;; managing cell properties
(define (connections tbl c) (dict-ref tbl c '()))
(define (connect! tbl c n)
(dict-set! tbl c (cons n (connections tbl c)))
(dict-set! tbl n (cons c (connections tbl n))))
(define (connected? tbl a b) (member a (connections tbl b)))
;; Returns a maze of a given size
;; build-maze :: Index Index -> Maze
(define (build-maze N M)
(define tbl (make-hash))
(define (visited? tbl c) (dict-has-key? tbl c))
(define (neigbours c)
(filter
(match-lambda [(list i j) (and (<= 0 i (- N 1)) (<= 0 j (- M 1)))])
(for/list ([d '((0 1) (0 -1) (-1 0) (1 0))]) (map + c d))))
; generate the maze
(let move-to-cell ([c (list (random N) (random M))])
(for ([n (shuffle (neigbours c))] #:unless (visited? tbl n))
(connect! tbl c n)
(move-to-cell n)))
; return the result
(maze N M tbl))
Printing out the maze
;; Shows a maze
(define (show-maze m)
(match-define (maze N M tbl) m)
(for ([i N]) (display "+---"))
(displayln "+")
(for ([j M])
(display "|")
(for ([i (- N 1)])
(if (connected? tbl (list i j) (list (+ 1 i) j))
(display " ")
(display " |")))
(display " |")
(newline)
(for ([i N])
(if (connected? tbl (list i j) (list i (+ j 1)))
(display "+ ")
(display "+---")))
(displayln "+"))
(newline))
Example:
-> (define m (build-maze 10 7)) -> (show-maze m) +---+---+---+---+---+---+---+---+---+---+ | | | | | + +---+---+ + + +---+ +---+ + | | | | | | | + + +---+ + +---+ +---+---+ + | | | | | | | + +---+ +---+---+ +---+---+ + + | | | | | | + + +---+---+---+ + + +---+ + | | | | | | | +---+ + +---+ +---+ + + +---+ | | | | | | | + +---+ + +---+ +---+---+---+ + | | | +---+---+---+---+---+---+---+---+---+---+
Raku
(formerly Perl 6)
Supply a width and height and optionally the x,y grid coords for the starting cell. If no starting cell is supplied, a random one will be selected automatically. 0,0 is the top left corner.
constant mapping = :OPEN(' '),
:N< ╵ >,
:E< ╶ >,
:NE< └ >,
:S< ╷ >,
:NS< │ >,
:ES< ┌ >,
:NES< ├ >,
:W< ╴ >,
:NW< ┘ >,
:EW< ─ >,
:NEW< ┴ >,
:SW< ┐ >,
:NSW< ┤ >,
:ESW< ┬ >,
:NESW< ┼ >,
:TODO< x >,
:TRIED< · >;
enum Sym (mapping.map: *.key);
my @ch = mapping.map: *.value;
enum Direction <DeadEnd Up Right Down Left>;
sub gen_maze ( $X,
$Y,
$start_x = (^$X).pick * 2 + 1,
$start_y = (^$Y).pick * 2 + 1 )
{
my @maze;
push @maze, $[ flat ES, -N, (ESW, EW) xx $X - 1, SW ];
push @maze, $[ flat (NS, TODO) xx $X, NS ];
for 1 ..^ $Y {
push @maze, $[ flat NES, EW, (NESW, EW) xx $X - 1, NSW ];
push @maze, $[ flat (NS, TODO) xx $X, NS ];
}
push @maze, $[ flat NE, (EW, NEW) xx $X - 1, -NS, NW ];
@maze[$start_y][$start_x] = OPEN;
my @stack;
my $current = [$start_x, $start_y];
loop {
if my $dir = pick_direction( $current ) {
@stack.push: $current;
$current = move( $dir, $current );
}
else {
last unless @stack;
$current = @stack.pop;
}
}
return @maze;
sub pick_direction([$x,$y]) {
my @neighbors =
(Up if @maze[$y - 2][$x]),
(Down if @maze[$y + 2][$x]),
(Left if @maze[$y][$x - 2]),
(Right if @maze[$y][$x + 2]);
@neighbors.pick or DeadEnd;
}
sub move ($dir, @cur) {
my ($x,$y) = @cur;
given $dir {
when Up { @maze[--$y][$x] = OPEN; @maze[$y][$x-1] -= E; @maze[$y--][$x+1] -= W; }
when Down { @maze[++$y][$x] = OPEN; @maze[$y][$x-1] -= E; @maze[$y++][$x+1] -= W; }
when Left { @maze[$y][--$x] = OPEN; @maze[$y-1][$x] -= S; @maze[$y+1][$x--] -= N; }
when Right { @maze[$y][++$x] = OPEN; @maze[$y-1][$x] -= S; @maze[$y+1][$x++] -= N; }
}
@maze[$y][$x] = 0;
[$x,$y];
}
}
sub display (@maze) {
for @maze -> @y {
for @y.rotor(2) -> ($w, $c) {
print @ch[abs $w];
if $c >= 0 { print @ch[$c] x 3 }
else { print ' ', @ch[abs $c], ' ' }
}
say @ch[@y[*-1]];
}
}
display gen_maze( 29, 19 );
- Output:
┌ ╵ ────────────────────────────┬───────────────────────────────────────────┬───────────┬───────────────────────────┐ │ │ │ │ │ │ ╶───────────┬───────────┐ │ ┌───────────────────────╴ ┌───────┐ ├───╴ ╷ │ ┌───────────┬───┬───╴ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌───────┐ ╵ ┌───┐ ├───┘ │ ┌───────────┬───────────┤ ╶───┤ │ ╶───┴───┤ │ ┌───┐ │ │ ╶───┤ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └───╴ └───────┤ │ ╵ ┌───┘ │ ╷ ╶───┤ ┌───┐ │ ╷ │ ├───────╴ │ ╵ │ │ │ └───┐ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├───────┬───────┐ │ ├───────┤ ╶───┤ └───┐ ╵ │ │ ╵ │ ╵ │ ┌───┐ └───┬───┘ │ │ ╷ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╶───┤ ╷ ╵ │ ╵ ╷ └───┐ ├───┐ ├───────┤ └───────┴───────┘ │ └───┐ └───╴ │ ╵ ├───┘ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├───╴ │ ├───────┴───┐ ├───╴ │ │ │ ╵ ╷ └───┐ ╶───┬───────┬───┘ ┌───┴───────╴ │ ┌───┘ ╷ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╷ │ │ ╶───┐ └───┤ ╷ │ │ └───────┴───┐ └───╴ │ ╷ ╵ ╷ ╵ ╶───────┬───┤ │ ┌───┴───┤ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╷ ├───╴ ╵ │ │ │ ┌───────┐ └───────────┤ └───┬───┴───────────┐ ╵ │ │ ╵ ╷ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├───┘ │ ┌───────┴───┘ │ ╵ ┌───┴───────╴ ╷ ├───┐ │ ╶───────┐ └───┐ │ └───┬───┘ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └───┘ │ ┌───┘ │ ┌───┬───────┼───╴ │ ╶───┬───────┤ ╵ │ └───┬───╴ ├───┐ │ └───┐ │ ╷ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌───────┘ ├───────┤ │ ╵ ╷ │ ┌───┴───┐ │ ╶───┘ ┌───┴───╴ │ ┌───┘ │ │ ┌───┘ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └───╴ ╷ │ ╷ │ └───┐ │ ╵ │ ╷ ╵ ├───────┐ │ ╶───────┤ └───┐ │ └───┤ ┌───┘ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├───────────┤ │ │ └───╴ │ └───────┤ └───────┘ ╷ └───┴───┬───╴ ├───╴ │ └───┐ │ │ ╶───┴───┤ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌───╴ │ │ ├───╴ ┌───┴───────┬───┴───┐ ┌───────┴───────┐ ╵ ┌───┤ ╶───┤ ╷ │ ╵ └───┬───╴ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╶───┘ │ │ ╶───┤ ╶───┐ ╵ ╷ │ └───╴ ┌───╴ └───────┘ ├───╴ │ ├───┴───────┬───┘ ╷ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├───────┬───┘ ├───────┴───┐ ├───────┤ └───────────┤ ┌───────────┐ │ ┌───┘ │ ╶───┐ ╵ ┌───┘ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └───╴ │ ╷ │ ╶───┐ ╵ │ ╷ └───────────┐ │ │ ┌───┐ └───┘ │ ╶───┴───┐ └───────┴───────┤ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├───────╴ │ └───┴───╴ └───────┴───┴───────────╴ │ └───┘ ╵ └───────────┴───╴ ╷ └───────────────┐ │ │ │ │ │ │ │ └───────────┴───────────────────────────────────────────┴───────────────────────────────────┴───────────────────┴ │ ┘
Rascal
import IO;
import util::Math;
import List;
public void make_maze(int w, int h){
vis = [[0 | _ <- [1..w]] | _ <- [1..h]];
ver = [["| "| _ <- [1..w]] + ["|"] | _ <- [1..h]] + [[]];
hor = [["+--"| _ <- [1..w]] + ["+"] | _ <- [1..h + 1]];
void walk(int x, int y){
vis[y][x] = 1;
d = [<x - 1, y>, <x, y + 1>, <x + 1, y>, <x, y - 1>];
while (d != []){
<<xx, yy>, d> = takeOneFrom(d);
if (xx < 0 || yy < 0 || xx >= w || yy >= w) continue;
if (vis[yy][xx] == 1) continue;
if (xx == x) hor[max([y, yy])][x] = "+ ";
if (yy == y) ver[y][max([x, xx])] = " ";
walk(xx, yy);
}
}
walk(arbInt(w), arbInt(h));
for (<a, b> <- zip(hor, ver)){
println(("" | it + "<z>" | z <- a));
println(("" | it + "<z>" | z <- b));
}
}
rascal>make_maze(10,10) +--+--+--+--+--+--+--+--+--+--+ | | | | + +--+ +--+ +--+--+--+ + + | | | | | | | + + +--+ +--+--+--+ + + + | | | | | | | + +--+--+ + + +--+--+--+ + | | | | | + +--+--+--+ + + +--+--+--+ | | | | | | + +--+ +--+--+ + + +--+ + | | | | | | + + +--+ +--+--+ +--+--+ + | | | | | | | | +--+ + +--+ +--+ + + + + | | | | | | + + +--+ +--+--+--+--+ + + | | | | | | | | + +--+ +--+ + + + + + + | | | | | +--+--+--+--+--+--+--+--+--+--+ ok
Red
Red ["Maze generation"]
size: as-pair to-integer ask "Maze width: " to-integer ask "Maze height: "
random/seed now/time
offsetof: function [pos] [pos/y * size/x + pos/x + 1]
visited?: function [pos] [find visited pos]
newneighbour: function [pos][
nnbs: collect [
if all [pos/x > 0 not visited? p: pos - 1x0] [keep p]
if all [pos/x < (size/x - 1) not visited? p: pos + 1x0] [keep p]
if all [pos/y > 0 not visited? p: pos - 0x1] [keep p]
if all [pos/y < (size/y - 1) not visited? p: pos + 0x1] [keep p]
]
pick nnbs random length? nnbs
]
expand: function [pos][
insert visited pos
either npos: newneighbour pos [
insert exploring npos
do select [
0x-1 [o: offsetof npos walls/:o/y: 0]
1x0 [o: offsetof pos walls/:o/x: 0]
0x1 [o: offsetof pos walls/:o/y: 0]
-1x0 [o: offsetof npos walls/:o/x: 0]
] npos - pos
][
remove exploring
]
]
visited: []
walls: append/dup [] 1x1 size/x * size/y
exploring: reduce [random size - 1x1]
until [
expand exploring/1
empty? exploring
]
print append/dup "" " _" size/x
repeat j size/y [
prin "|"
repeat i size/x [
p: pick walls (j - 1 * size/x + i)
prin rejoin [pick " _" 1 + p/y pick " |" 1 + p/x]
]
print ""
]
- Output:
Maze width: 15 Maze height: 15 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | _ _ | _ _ _ _| _ _ | |_ _ |_ _| | _ _ |_ | |_ _| | | |_ |_ _ _ _| | _|_ _ | | |_|_ _ |_ | | | _ _| _| | _ _|_ _| |_ _| | _ _| | | | |_ _ _ _| | _ _| | _ _| |_ _ _ | | |_| | | _|_ _ | |_ _ | | | |_ _ _| | | _ | | | _ _| | |_ _ _ |_ _| | | | | _ | |_ | |_ |_ _ _| | | | | |_ _|_ _ _ |_ |_ _| _| | | | _ |_ _| | |_ | | | |_|_ |_ |_| _|_|_|_ _ _| | |_ _ | | |_ |_ _ _ _ _ | | |_ _ _ _|_ _ _ _ _ _ _ _|_ _ _|
REXX
prettified maze version
In order to preserve the aspect ratio (for most display terminals), several changestr invocations and
some other instructions were added to increase the horizontal dimension (cell size).
/*REXX program generates and displays a rectangular solvable maze (of any size). */
parse arg rows cols seed . /*allow user to specify the maze size. */
if rows='' | rows==',' then rows= 19 /*No rows given? Then use the default.*/
if cols='' | cols==',' then cols= 19 /* " cols " ? " " " " */
if datatype(seed, 'W') then call random ,,seed /*use a random seed for repeatability.*/
ht=0; @.=0 /*HT= # rows in grid; @.: default cell*/
call makeRow '┌'copies("~┬", cols - 1)'~┐' /*construct the top edge of the maze. */
/* [↓] construct the maze's grid. */
do r=1 for rows; _=; __=; hp= "|"; hj= '├'
do c=1 for cols; _= _ || hp'1'; __= __ || hj"~"; hj= '┼'; hp= "│"
end /*c*/
call makeRow _'│' /*construct the right edge of the cells*/
if r\==rows then call makeRow __'┤' /* " " " " " " maze.*/
end /*r*/ /* [↑] construct the maze's grid. */
call makeRow '└'copies("~┴", cols - 1)'~┘' /*construct the bottom edge of the maze*/
r!= random(1, rows) *2; c!= random(1, cols) *2; @.r!.c!= 0 /*choose 1st cell.*/
/* [↓] traipse through the maze. */
do forever; n= hood(r!, c!); if n==0 then if \fCell() then leave /*¬freecell?*/
call ?; @._r._c= 0 /*get the (next) maze direction to go. */
ro= r!; co= c!; r!= _r; c!= _c /*save original maze cell coordinates. */
?.zr= ?.zr % 2; ?.zc= ?.zc % 2 /*get the maze row and cell directions.*/
rw= ro + ?.zr; cw= co + ?.zc /*calculate the next row and column. */
@.rw.cw= . /*mark the maze cell as being visited. */
end /*forever*/
/* [↓] display maze to the terminal. */
do r=1 for ht; _=
do c=1 for cols*2 + 1; _= _ || @.r.c; end /*c*/
if \(r//2) then _= translate(_, '\', .) /*trans to backslash*/
@.r= _ /*save the row in @.*/
end /*r*/
do #=1 for ht; _= @.# /*display the maze to the terminal. */
call makeNice /*prettify cell corners and dead─ends. */
_= changestr( 1 , _ , 111 ) /*──────these four ────────────────────*/
_= changestr( 0 , _ , 000 ) /*───────── statements are ────────────*/
_= changestr( . , _ , " " ) /*────────────── used for preserving ──*/
_= changestr('~', _ , "───" ) /*────────────────── the aspect ratio. */
say translate( _ , '─│' , "═|\10" ) /*make it presentable for the screen. */
end /*#*/
exit /*stick a fork in it, we're all done. */
/*──────────────────────────────────────────────────────────────────────────────────────*/
@: parse arg _r,_c; return @._r._c /*a fast way to reference a maze cell. */
makeRow: parse arg z; ht= ht+1; do c=1 for length(z); @.ht.c=substr(z,c,1); end; return
hood: parse arg rh,ch; return @(rh+2,ch) + @(rh-2,ch) + @(rh,ch-2) + @(rh,ch+2)
/*──────────────────────────────────────────────────────────────────────────────────────*/
?: do forever; ?.= 0; ?= random(1, 4); if ?==1 then ?.zc= -2 /*north*/
if ?==2 then ?.zr= 2 /* east*/
if ?==3 then ?.zc= 2 /*south*/
if ?==4 then ?.zr= -2 /* west*/
_r= r! + ?.zr; _c= c! + ?.zc; if @._r._c == 1 then return
end /*forever*/
/*──────────────────────────────────────────────────────────────────────────────────────*/
fCell: do r=1 for rows; rr= r + r
do c=1 for cols; cc= c + c
if hood(rr,cc)==1 then do; r!= rr; c!= cc; @.r!.c!= 0; return 1; end
end /*c*/ /* [↑] r! and c! are used by invoker.*/
end /*r*/; return 0
/*──────────────────────────────────────────────────────────────────────────────────────*/
makeNice: width= length(_); old= # - 1; new= # + 1; old_= @.old; new_= @.new
if left(_, 2)=='├.' then _= translate(_, "|", '├')
if right(_,2)=='.┤' then _= translate(_, "|", '┤')
do k=1 for width while #==1; z= substr(_, k, 1) /*maze top row.*/
if z\=='┬' then iterate
if substr(new_, k, 1)=='\' then _= overlay("═", _, k)
end /*k*/
do k=1 for width while #==ht; z= substr(_, k, 1) /*maze bot row.*/
if z\=='┴' then iterate
if substr(old_, k, 1)=='\' then _= overlay("═", _, k)
end /*k*/
do k=3 to width-2 by 2 while #//2; z= substr(_, k, 1) /*maze mid rows*/
if z\=='┼' then iterate
le= substr(_ , k-1, 1)
ri= substr(_ , k+1, 1)
up= substr(old_, k , 1)
dw= substr(new_, k , 1)
select
when le== . & ri== . & up=='│' & dw=="│" then _= overlay('|', _, k)
when le=='~' & ri=="~" & up=='\' & dw=="\" then _= overlay('═', _, k)
when le=='~' & ri=="~" & up=='\' & dw=="│" then _= overlay('┬', _, k)
when le=='~' & ri=="~" & up=='│' & dw=="\" then _= overlay('┴', _, k)
when le=='~' & ri== . & up=='\' & dw=="\" then _= overlay('═', _, k)
when le== . & ri=="~" & up=='\' & dw=="\" then _= overlay('═', _, k)
when le== . & ri== . & up=='│' & dw=="\" then _= overlay('|', _, k)
when le== . & ri== . & up=='\' & dw=="│" then _= overlay('|', _, k)
when le== . & ri=="~" & up=='\' & dw=="│" then _= overlay('┌', _, k)
when le== . & ri=="~" & up=='│' & dw=="\" then _= overlay('└', _, k)
when le=='~' & ri== . & up=='\' & dw=="│" then _= overlay('┐', _, k)
when le=='~' & ri== . & up=='│' & dw=="\" then _= overlay('┘', _, k)
when le=='~' & ri== . & up=='│' & dw=="│" then _= overlay('┤', _, k)
when le== . & ri=="~" & up=='│' & dw=="│" then _= overlay('├', _, k)
otherwise nop
end /*select*/
end /*k*/; return
Some older REXXes don't have a changestr BIF, so one is included here ──► CHANGESTR.REX.
- output when using the input of: 23 19 6
(Shown at one-half size.)
┌───────────┬───────────────┬───────────────────────┬───────────────────┬───┐ │ │ │ │ │ │ │ ────┐ │ │ │ │ │ ┌───────┐ ────────┤ ────┐ ────┐ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └───┘ │ │ │ ────┤ │ └───┐ │ └──── ├──── │ ┌───┤ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌───────┤ │ └───────┘ ├──── │ └───┐ ┌───┘ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └──── │ ├───────┐ │ │ ┌───┴───┐ ├───┘ ┌───┘ │ ────┤ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├───────┐ │ │ │ │ │ │ │ │ └───┤ ────┤ ────┴───┐ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├──── │ │ ├───┘ │ └───┐ └───┐ └───┐ │ └───┤ │ │ │ │ │ │ │ │ │ │ │ │ │ ├───────┘ │ │ ────┤ │ │ │ └───┐ └───────┴──── ├───┴───┐ │ │ │ │ │ │ │ │ │ │ │ │ ├───────────┴───┴──── │ │ │ └───────┴───────────────────┤ │ │ │ │ │ │ │ │ │ │ │ │ ┌───────┬───────┬───┘ │ ├───────────────────────┬──── │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ────┼───┘ ┌───────────────┐ │ ┌───┘ ├───────┤ │ │ │ │ │ │ │ │ │ │ │ │ ────┤ │ │ │ │ │ ┌───┘ │ ────┐ │ ────┤ ┌───┴──── │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├──── │ └───┘ ├───┘ │ │ ┌───┴───────┤ │ │ │ │ │ ────┤ │ │ │ │ │ │ │ │ │ │ │ │ ────┴───┬───────┘ ┌───┴───────┘ ┌──── │ └───┤ └───┬───┴───┐ │ │ │ │ │ │ │ │ │ │ │ ┌──── │ ────────┘ ┌───────────┼───────┴──── │ │ └──── │ │ │ │ │ │ │ │ │ │ │ │ │ ┌───┴───┬───────────┤ ┌───┐ │ ┌───────┬───┤ │ ────────┤ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ────────┤ │ │ ┌───┘ │ │ │ ├───────┬───┘ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├───────┤ └──────── ├───────┤ │ │ │ │ ┌───┘ │ │ ────┤ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ────┐ ┌───┘ │ └───┘ │ │ ├───┘ │ │ └──── │ │ │ │ │ │ │ │ │ │ │ │ │ ├───┬───┘ ├──── ├───┘ │ └───────┬───┘ │ │ ────┴───┴───┐ ────┤ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌───┤ ┌───┴───┬───┐ │ │ │ └───┬───────┐ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌───┴───┘ │ │ └───┐ │ │ │ ├───┴───┐ │ │ │ ├───┘ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ────────┘ │ │ │ │ ────┤ │ │ └───────┤ │ │ ────┤ │ │ │ │ │ │ │ │ │ │ └───┴───────────────┴───┴───────┴───────┴───────┴───────────┴───────┴───────┘
simpler version of above
The above REXX version had a quite of bit of code to "dress up" the maze presentation, so a slimmed-down version
was included here for easier reading and understanding of the program's logic.
/*REXX program generates and displays a rectangular solvable maze (of any size). */
parse arg rows cols seed . /*allow user to specify the maze size. */
if rows='' | rows=="," then rows= 19 /*No rows given? Then use the default.*/
if cols='' | cols=="," then cols= 19 /* " cols " ? " " " " */
if datatype(seed, 'W') then call random ,,seed /*use a random seed for repeatability.*/
ht=0; @.=0 /*HT= # rows in grid; @.: default cell*/
call makeRow '┌'copies("─┬", cols-1)'─┐' /*construct the top edge of the maze.*/
do r=1 for rows; _=; __=; hp= "|"; hj= '├'
do c=1 for cols; _= _ || hp'1'; __= __ || hj"─"; hj= '┼'; hp= "│"
end /*c*/
call makeRow _'│' /*construct the right edge of the cells*/
if r\==rows then call makeRow __'┤' /* " " " " " " maze.*/
end /*r*/ /* [↑] construct the maze's grid. */
call makeRow '└'copies("─┴", cols-1)'─┘' /*construct the bottom edge of the maze*/
r!= random(1, rows)*2; c!= random(1, cols)*2; @.r!.c!= 0 /*choose the 1st cell.*/
/* [↓] traipse through the maze. */
do forever; n= hood(r!, c!) /*number of free maze cells. */
if n==0 then if \fCell() then leave /*if no free maze cells left, then done*/
call ?; @._r._c= 0 /*get the (next) maze direction to go. */
ro= r!; co= c!; r!= _r; c!= _c /*save the original cell coordinates. */
?.zr= ?.zr % 2; ?.zc= ?.zc % 2 /*get the maze row and cell directions.*/
rw= ro + ?.zr; cw= co + ?.zc /*calculate the next maze row and col. */
@.rw.cw=. /*mark the maze cell as being visited. */
end /*forever*/
/* [↓] display maze to the terminal. */
do r=1 for ht; _=
do c=1 for cols*2 + 1; _= _ || @.r.c; end /*c*/
if \(r//2) then _= translate(_, '\',.) /*translate a period to a backslash.*/
_= changestr( 1 , _ , 111 ) /*──────these four ────────────────────*/
_= changestr( 0 , _ , 000 ) /*───────── statements are ────────────*/
_= changestr( . , _ , " " ) /*────────────── used for preserving ──*/
_= changestr('─', _ , "───" ) /*────────────────── the aspect ratio. */
say translate( _ , '│' , "|\10") /*make it presentable for the screen. */
end /*r*/
exit /*stick a fork in it, we're all done. */
/*──────────────────────────────────────────────────────────────────────────────────────*/
@: parse arg _r,_c; return @._r._c /*a fast way to reference a maze cell. */
makeRow: parse arg z; ht= ht+1; do c=1 for length(z); @.ht.c=substr(z,c,1); end; return
hood: parse arg rh,ch; return @(rh+2,ch) + @(rh-2,ch) + @(rh,ch-2) + @(rh,ch+2)
/*──────────────────────────────────────────────────────────────────────────────────────*/
?: do forever; ?.= 0; ?= random(1, 4); if ?==1 then ?.zc= -2 /*north*/
if ?==2 then ?.zr= 2 /* east*/
if ?==3 then ?.zc= 2 /*south*/
if ?==4 then ?.zr= -2 /* west*/
_r= r! + ?.zr; _c= c! + ?.zc; if @._r._c == 1 then return
end /*forever*/
/*──────────────────────────────────────────────────────────────────────────────────────*/
fCell: do r=1 for rows; rr= r + r
do c=1 for cols; cc= c + c
if hood(rr,cc)==1 then do; r!= rr; c!= cc; @.r!.c!= 0; return 1; end
end /*c*/ /* [↑] r! and c! are used by invoker.*/
end /*r*/; return 0
- output when using input: 10 10
Shown at one-half size.)
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ │ │ │ ├ ┼ ┼───┼───┼───┼ ┼───┼───┼ ┼ ┤ │ │ │ │ │ │ ├ ┼ ┼───┼───┼───┼───┼ ┼ ┼───┼ ┤ │ │ │ │ │ │ ├ ┼───┼───┼ ┼───┼ ┼ ┼───┼ ┼───┤ │ │ │ │ │ │ │ │ ├ ┼───┼ ┼ ┼ ┼ ┼ ┼ ┼───┼ ┤ │ │ │ │ │ │ │ │ ├ ┼───┼───┼ ┼ ┼ ┼───┼───┼ ┼ ┤ │ │ │ │ │ │ ├───┼───┼ ┼───┼───┼ ┼ ┼ ┼───┼───┤ │ │ │ │ │ │ ├ ┼ ┼───┼───┼ ┼───┼ ┼ ┼───┼ ┤ │ │ │ │ │ │ ├ ┼───┼───┼───┼───┼ ┼───┼───┼ ┼ ┤ │ │ │ │ │ │ ├ ┼───┼───┼───┼ ┼───┼ ┼ ┼ ┼ ┤ │ │ │ │ │ └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
version 3
/* REXX ***************************************************************
* 04.09.2013 Walter Pachl
**********************************************************************/
Parse Arg imax jmax seed
If imax='' Then imax=10
If jmax='' Then jmax=15
If seed='' Then seed=4711
c='123456789'||,
'abcdefghijklmnopqrstuvwxyz'||,
translate('abcdefghijklmnopqrstuvwxyz')
c=copies(c,10)
call random 1,10,seed
id=2*imax+1 /* vertical dimension of a.i.j */
jd=2*jmax+1 /* horizontal dimension of a.i.j */
a.=1 /* mark all borders present */
p.='.' /* Initialize all grid points */
pl.=0 /* path list */
ii=random(1,imax) /* find a start position */
jj=random(1,jmax)
p=1 /* first position */
na=1 /* number of points used */
Do si=1 To 1000 /* Do Forever - see Leave */
/* Say 'loop' si na show progress */
Call path ii,jj /* compute a path starting at ii/jj */
If na=imax*jmax Then /* all points used */
Leave /* we are done */
Parse Value select_next() With ii jj /* get a new start from a path*/
End
/***************
Do i=1 To imax
ol=''
Do j=1 To jmax
ol=ol||p.i.j
End
Say ol
End
Say ' '
***************/
Call show
/***********************
Do pi=1 To imax*jmax
Say right(pi,3) pos.pi
End
***********************/
Exit
path: Procedure Expose p. np. p pl. c a. na imax jmax id jd pos.
/**********************************************************************
* compute a path starting from point (ii,jj)
**********************************************************************/
Parse Arg ii,jj
p.ii.jj='1'
pos.p=ii jj
Do pp=1 to 50 /* compute a path of maximum length 50*/
neighbors=neighbors(ii,jj) /* number of free neighbors */
Select
When neighbors=1 Then /* just one */
Call advance 1,ii,jj /* go for it */
When neighbors>0 Then Do /* more Than 1 */
ch=random(1,neighbors) /* choose one possibility */
Call advance ch,ii,jj /* and go for that */
End
Otherwise /* none available */
Leave
End
End
Return
neighbors: Procedure Expose p. np. imax jmax neighbors pl.
/**********************************************************************
* count the number of free neighbors of point (i,j)
**********************************************************************/
Parse Arg i,j
neighbors=0
in=i-1; If in>0 Then Call check in,j
in=i+1; If in<=imax Then Call check in,j
jn=j-1; If jn>0 Then Call check i,jn
jn=j+1; If jn<=jmax Then Call check i,jn
Return neighbors
check: Procedure Expose p. imax jmax np. neighbors pl.
/**********************************************************************
* check if point (i,j) is free and note it as possible successor
**********************************************************************/
Parse Arg i,j
If p.i.j='.' Then Do /* point is free */
neighbors=neighbors+1 /* number of free neighbors */
np.neighbors=i j /* note it as possible choice */
End
Return
advance: Procedure Expose p pos. np. p. c ii jj a. na pl. pos.
/**********************************************************************
* move to the next point of the current path
**********************************************************************/
Parse Arg ch,pii,pjj
Parse Var np.ch ii jj
p=p+1 /* position number */
pos.p=ii jj /* note its coordinates */
p.ii.jj=substr(c,p,1) /* mark the point as used */
ai=pii+ii /* vertical border position */
aj=pjj+jj /* horizontal border position */
a.ai.aj=0 /* tear the border down */
na=na+1 /* number of used positions */
z=pl.0+1 /* add the point to the list */
pl.z=ii jj /* of follow-up start pos. */
pl.0=z
Return
show: Procedure Expose id jd a. na
/*********************************************************************
* Show the resulting maze
*********************************************************************/
say 'mgg 6 18 4711'
say 'show na='na
Do i=1 To id
ol=''
Do j=1 To jd
If i//2=1 Then Do /* odd lines */
If a.i.j=1 Then Do /* border to be drawn */
If j//2=0 Then
ol=ol||'---' /* draw the border */
Else
ol=ol'+'
End
Else Do /* border was torn down */
If j//2=0 Then
ol=ol||' ' /* blanks instead of border */
Else
ol=ol||'+'
End
End
Else Do /* even line */
If a.i.j=1 Then Do
If j//2=0 Then /* even column */
ol=ol||' ' /* moving space */
Else /* odd column */
ol=ol||'|' /* draw the border */
End
Else /* border was torn down */
ol=ol||' ' /* blank instead of border */
End
End
Select
When i=6 Then ol=overlay('A',ol,11)
When i=8 Then ol=overlay('B',ol, 3)
Otherwise Nop
End
Say ol format(i,2)
End
Return
select_next: Procedure Expose p. pl. imax jmax
/*********************************************************************
* look for a point to start the nnext path
*********************************************************************/
Do Until neighbors>0 /* loop until one is found */
n=pl.0 /* number of points recorded */
s=random(1,n) /* pick a random index */
Parse Var pl.s is js /* its coordinates */
neighbors=neighbors(is,js) /* count free neighbors */
If neighbors=0 Then Do /* if there is none */
pl.s=pl.n /* remove this point */
pl.0=pl.0-1
End
End
Return is js /* return the new start point*/
Output:
rexx mgg 6 18 4711 show na=108 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1 | | | | | | | | 2 + +---+ + + + + +---+ + +---+---+ + +---+ + +---+ 3 | | | | | | | | | | | | 4 +---+---+ + +---+---+---+ + + + + +---+---+ + +---+ + 5 | | A | | | | | | | 6 + + +---+---+---+---+ +---+ +---+---+ + +---+ + +---+---+ 7 | B | | | | | | | | | | | 8 + + +---+ + + +---+ + + + +---+ +---+ + + + + 9 | | | | | | | | | | | | | | 10 + + + +---+---+---+ + + + +---+---+---+ + + +---+ + 11 | | | | | | | 12 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 13
Ruby
class Maze
DIRECTIONS = [ [1, 0], [-1, 0], [0, 1], [0, -1] ]
def initialize(width, height)
@width = width
@height = height
@start_x = rand(width)
@start_y = 0
@end_x = rand(width)
@end_y = height - 1
# Which walls do exist? Default to "true". Both arrays are
# one element bigger than they need to be. For example, the
# @vertical_walls[x][y] is true if there is a wall between
# (x,y) and (x+1,y). The additional entry makes printing easier.
@vertical_walls = Array.new(width) { Array.new(height, true) }
@horizontal_walls = Array.new(width) { Array.new(height, true) }
# Path for the solved maze.
@path = Array.new(width) { Array.new(height) }
# "Hack" to print the exit.
@horizontal_walls[@end_x][@end_y] = false
# Generate the maze.
generate
end
# Print a nice ASCII maze.
def print
# Special handling: print the top line.
puts @width.times.inject("+") {|str, x| str << (x == @start_x ? " +" : "---+")}
# For each cell, print the right and bottom wall, if it exists.
@height.times do |y|
line = @width.times.inject("|") do |str, x|
str << (@path[x][y] ? " * " : " ") << (@vertical_walls[x][y] ? "|" : " ")
end
puts line
puts @width.times.inject("+") {|str, x| str << (@horizontal_walls[x][y] ? "---+" : " +")}
end
end
private
# Reset the VISITED state of all cells.
def reset_visiting_state
@visited = Array.new(@width) { Array.new(@height) }
end
# Is the given coordinate valid and the cell not yet visited?
def move_valid?(x, y)
(0...@width).cover?(x) && (0...@height).cover?(y) && !@visited[x][y]
end
# Generate the maze.
def generate
reset_visiting_state
generate_visit_cell(@start_x, @start_y)
end
# Depth-first maze generation.
def generate_visit_cell(x, y)
# Mark cell as visited.
@visited[x][y] = true
# Randomly get coordinates of surrounding cells (may be outside
# of the maze range, will be sorted out later).
coordinates = DIRECTIONS.shuffle.map { |dx, dy| [x + dx, y + dy] }
for new_x, new_y in coordinates
next unless move_valid?(new_x, new_y)
# Recurse if it was possible to connect the current and
# the cell (this recursion is the "depth-first" part).
connect_cells(x, y, new_x, new_y)
generate_visit_cell(new_x, new_y)
end
end
# Try to connect two cells. Returns whether it was valid to do so.
def connect_cells(x1, y1, x2, y2)
if x1 == x2
# Cells must be above each other, remove a horizontal wall.
@horizontal_walls[x1][ [y1, y2].min ] = false
else
# Cells must be next to each other, remove a vertical wall.
@vertical_walls[ [x1, x2].min ][y1] = false
end
end
end
# Demonstration:
maze = Maze.new 20, 10
maze.print
- Output:
+---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+ | | | | | | | | + +---+ +---+ + + +---+---+---+ + + +---+ + +---+ +---+ + | | | | | | | | | | | | | | + + + +---+---+ + + +---+ +---+ +---+ +---+---+ + + +---+ | | | | | | | | | | | | + + +---+---+ +---+---+ + +---+---+ + +---+ + + +---+---+ + | | | | | | | | | | + +---+---+ +---+---+ + +---+---+ +---+---+ +---+ +---+---+---+ + | | | | | | | | | | +---+---+ +---+ + + +---+ + + +---+---+---+---+ +---+ + + + | | | | | | | | | | + + +---+ +---+---+---+---+---+ +---+---+ + +---+---+ +---+---+---+ | | | | | | | | | | | + +---+ +---+---+---+---+---+ + +---+ + +---+ + + +---+---+ + | | | | | | | | | | + + +---+---+ + + +---+---+---+---+ +---+ +---+---+ + +---+---+ | | | | | | | | | | + + +---+ +---+ +---+ + +---+---+---+ +---+ +---+---+---+---+ + | | | | | +---+---+---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+---+
Rust
Uses the rand library
use rand::{thread_rng, Rng, rngs::ThreadRng};
const WIDTH: usize = 16;
const HEIGHT: usize = 16;
#[derive(Clone, Copy)]
struct Cell {
col: usize,
row: usize,
}
impl Cell {
fn from(col: usize, row: usize) -> Cell {
Cell {col, row}
}
}
struct Maze {
cells: [[bool; HEIGHT]; WIDTH], //cell visited/non visited
walls_h: [[bool; WIDTH]; HEIGHT + 1], //horizontal walls existing/removed
walls_v: [[bool; WIDTH + 1]; HEIGHT], //vertical walls existing/removed
thread_rng: ThreadRng, //Random numbers generator
}
impl Maze {
///Inits the maze, with all the cells unvisited and all the walls active
fn new() -> Maze {
Maze {
cells: [[true; HEIGHT]; WIDTH],
walls_h: [[true; WIDTH]; HEIGHT + 1],
walls_v: [[true; WIDTH + 1]; HEIGHT],
thread_rng: thread_rng(),
}
}
///Randomly chooses the starting cell
fn first(&mut self) -> Cell {
Cell::from(self.thread_rng.gen_range(0, WIDTH), self.thread_rng.gen_range(0, HEIGHT))
}
///Opens the enter and exit doors
fn open_doors(&mut self) {
let from_top: bool = self.thread_rng.gen();
let limit = if from_top { WIDTH } else { HEIGHT };
let door = self.thread_rng.gen_range(0, limit);
let exit = self.thread_rng.gen_range(0, limit);
if from_top {
self.walls_h[0][door] = false;
self.walls_h[HEIGHT][exit] = false;
} else {
self.walls_v[door][0] = false;
self.walls_v[exit][WIDTH] = false;
}
}
///Removes a wall between the two Cell arguments
fn remove_wall(&mut self, cell1: &Cell, cell2: &Cell) {
if cell1.row == cell2.row {
self.walls_v[cell1.row][if cell1.col > cell2.col { cell1.col } else { cell2.col }] = false;
} else {
self.walls_h[if cell1.row > cell2.row { cell1.row } else { cell2.row }][cell1.col] = false;
};
}
///Returns a random non-visited neighbor of the Cell passed as argument
fn neighbor(&mut self, cell: &Cell) -> Option<Cell> {
self.cells[cell.col][cell.row] = false;
let mut neighbors = Vec::new();
if cell.col > 0 && self.cells[cell.col - 1][cell.row] { neighbors.push(Cell::from(cell.col - 1, cell.row)); }
if cell.row > 0 && self.cells[cell.col][cell.row - 1] { neighbors.push(Cell::from(cell.col, cell.row - 1)); }
if cell.col < WIDTH - 1 && self.cells[cell.col + 1][cell.row] { neighbors.push(Cell::from(cell.col + 1, cell.row)); }
if cell.row < HEIGHT - 1 && self.cells[cell.col][cell.row + 1] { neighbors.push(Cell::from(cell.col, cell.row + 1)); }
if neighbors.is_empty() {
None
} else {
let next = neighbors.get(self.thread_rng.gen_range(0, neighbors.len())).unwrap();
self.remove_wall(cell, next);
Some(*next)
}
}
///Builds the maze (runs the Depth-first search algorithm)
fn build(&mut self) {
let mut cell_stack: Vec<Cell> = Vec::new();
let mut next = self.first();
loop {
while let Some(cell) = self.neighbor(&next) {
cell_stack.push(cell);
next = cell;
}
match cell_stack.pop() {
Some(cell) => next = cell,
None => break,
}
}
}
///Displays a wall
fn paint_wall(h_wall: bool, active: bool) {
if h_wall {
print!("{}", if active { "+---" } else { "+ " });
} else {
print!("{}", if active { "| " } else { " " });
}
}
///Displays a final wall for a row
fn paint_close_wall(h_wall: bool) {
if h_wall { println!("+") } else { println!() }
}
///Displays a whole row of walls
fn paint_row(&self, h_walls: bool, index: usize) {
let iter = if h_walls { self.walls_h[index].iter() } else { self.walls_v[index].iter() };
for &wall in iter {
Maze::paint_wall(h_walls, wall);
}
Maze::paint_close_wall(h_walls);
}
///Paints the maze
fn paint(&self) {
for i in 0 .. HEIGHT {
self.paint_row(true, i);
self.paint_row(false, i);
}
self.paint_row(true, HEIGHT);
}
}
fn main() {
let mut maze = Maze::new();
maze.build();
maze.open_doors();
maze.paint();
}
- Output:
+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+ | | | | | + +---+---+ + + +---+ +---+---+---+ +---+---+---+ + | | | | | | | | | | + +---+ + + + + +---+ + + +---+---+ + + + | | | | | | | | | | +---+ + + + +---+---+ + +---+---+ +---+---+---+ + | | | | | | | | | | + +---+---+---+ + + +---+ + + +---+ +---+---+---+ | | | | | | | | | +---+---+ +---+ + +---+ + +---+---+ + + + + + | | | | | | | | | | | + +---+---+ +---+ + +---+---+---+ +---+ + + +---+ | | | | | | | | | | + + + + + +---+ +---+ + +---+ + +---+---+ + | | | | | | | | | | | | | + + +---+ +---+ +---+---+ +---+ + + + + + + | | | | | | | | | | | | | + + + +---+ + + + +---+---+---+ + + + + + | | | | | | | | | | | | + + + + + + + +---+ +---+ + +---+---+---+ + | | | | | | | | | | +---+ + + +---+---+ + +---+---+---+ + + +---+---+ | | | | | | | | | | + +---+ +---+---+ + +---+---+---+ + + + + + + | | | | | | | | | | | | +---+---+ + + +---+---+ + + +---+ + + +---+ + | | | | | | | | | | | +---+ +---+ +---+ + + + + +---+---+---+---+ + + | | | | | | | | | + +---+ +---+ + +---+---+ +---+---+---+---+ +---+ + | | | | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ +
Scala
import scala.util.Random
object MazeTypes {
case class Direction(val dx: Int, val dy: Int)
case class Loc(val x: Int, val y: Int) {
def +(that: Direction): Loc = Loc(x + that.dx, y + that.dy)
}
case class Door(val from: Loc, to: Loc)
val North = Direction(0,-1)
val South = Direction(0,1)
val West = Direction(-1,0)
val East = Direction(1,0)
val directions = Set(North, South, West, East)
}
object MazeBuilder {
import MazeTypes._
def shuffle[T](set: Set[T]): List[T] = Random.shuffle(set.toList)
def buildImpl(current: Loc, grid: Grid): Grid = {
var newgrid = grid.markVisited(current)
val nbors = shuffle(grid.neighbors(current))
nbors.foreach { n =>
if (!newgrid.isVisited(n)) {
newgrid = buildImpl(n, newgrid.markVisited(current).addDoor(Door(current, n)))
}
}
newgrid
}
def build(width: Int, height: Int): Grid = {
val exit = Loc(width-1, height-1)
buildImpl(exit, new Grid(width, height, Set(), Set()))
}
}
class Grid(val width: Int, val height: Int, val doors: Set[Door], val visited: Set[Loc]) {
def addDoor(door: Door): Grid =
new Grid(width, height, doors + door, visited)
def markVisited(loc: Loc): Grid =
new Grid(width, height, doors, visited + loc)
def isVisited(loc: Loc): Boolean =
visited.contains(loc)
def neighbors(current: Loc): Set[Loc] =
directions.map(current + _).filter(inBounds(_)) -- visited
def printGrid(): List[String] = {
(0 to height).toList.flatMap(y => printRow(y))
}
private def inBounds(loc: Loc): Boolean =
loc.x >= 0 && loc.x < width && loc.y >= 0 && loc.y < height
private def printRow(y: Int): List[String] = {
val row = (0 until width).toList.map(x => printCell(Loc(x, y)))
val rightSide = if (y == height-1) " " else "|"
val newRow = row :+ List("+", rightSide)
List.transpose(newRow).map(_.mkString)
}
private val entrance = Loc(0,0)
private def printCell(loc: Loc): List[String] = {
if (loc.y == height)
List("+--")
else List(
if (openNorth(loc)) "+ " else "+--",
if (openWest(loc) || loc == entrance) " " else "| "
)
}
def openNorth(loc: Loc): Boolean = openInDirection(loc, North)
def openWest(loc: Loc): Boolean = openInDirection(loc, West)
private def openInDirection(loc: Loc, dir: Direction): Boolean =
doors.contains(Door(loc, loc + dir)) || doors.contains(Door(loc + dir, loc))
}
- Output:
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | | | +--+--+ + +--+--+ + + +--+--+--+ +--+ + | | | | | | | | | | + + +--+--+ + + + +--+--+--+ +--+ + + | | | | | | | | + +--+--+--+--+ +--+--+--+ + +--+ + + + | | | | | | | | | | + + + +--+--+--+ +--+ + +--+ + + + + | | | | | | | | | | | + +--+ + +--+--+--+ + + + + +--+--+ + | | | | | | | | | | + + + +--+ +--+--+ + +--+--+--+ +--+ + | | | | | | | + + + +--+--+--+--+ +--+--+--+ +--+--+ + | | | | | | | | +--+ +--+--+ +--+--+ + + + +--+--+ +--+ | | | | | | | | | + +--+--+ +--+--+ + + +--+ + + +--+ + | | | | | | | | | + +--+ +--+--+ + +--+--+ +--+ +--+ + + | | | | | | | | | +--+ +--+ + +--+ +--+ +--+--+--+ + + + | | | | | | | | | | + +--+ +--+--+ +--+ + + +--+ +--+ + + | | | | | | | | + + +--+--+ + +--+--+--+--+--+--+ +--+ + | | | | | | | | + + + +--+--+--+ +--+--+--+--+--+--+ + + | | | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
Sidef
var(w:5, h:5) = ARGV.map{.to_i}...
var avail = (w * h)
# cell is padded by sentinel col and row, so I don't check array bounds
var cell = (1..h -> map {([true] * w) + [false]} + [[false] * w+1])
var ver = (1..h -> map {["| "] * w })
var hor = (0..h -> map {["+--"] * w })
func walk(x, y) {
cell[y][x] = false
avail-- > 0 || return nil # no more cells
var d = [[-1, 0], [0, 1], [1, 0], [0, -1]]
while (!d.is_empty) {
var i = d.pop_rand
var (x1, y1) = (x + i[0], y + i[1])
cell[y1][x1] || next
if (x == x1) { hor[[y1, y].max][x] = '+ ' }
if (y == y1) { ver[y][[x1, x].max] = ' ' }
walk(x1, y1)
}
}
walk(w.rand.int, h.rand.int) # generate
for i in (0 .. h) {
say (hor[i].join('') + '+')
if (i < h) {
say (ver[i].join('') + '|')
}
}
- Output:
+--+--+--+--+--+ | | | +--+ +--+ + + | | | + +--+--+--+--+ | | | +--+ + +--+ + | | | | + +--+--+ +--+ | | +--+--+--+--+--+
SuperCollider
// some useful functions
(
~grid = { 0 ! 60 } ! 60;
~at = { |coord|
var col = ~grid.at(coord[0]);
if(col.notNil) { col.at(coord[1]) }
};
~put = { |coord, value|
var col = ~grid.at(coord[0]);
if(col.notNil) { col.put(coord[1], value) }
};
~coord = ~grid.shape.rand;
~next = { |p|
var possible = [p] + [[0, 1], [1, 0], [-1, 0], [0, -1]];
possible = possible.select { |x|
var c = ~at.(x);
c.notNil and: { c == 0 }
};
possible.choose
};
~walkN = { |p, scale|
var next = ~next.(p);
if(next.notNil) {
~put.(next, 1);
Pen.lineTo(~topoint.(next, scale));
~walkN.(next, scale);
~walkN.(next, scale);
Pen.moveTo(~topoint.(p, scale));
};
};
~topoint = { |c, scale| (c + [1, 1] * scale).asPoint };
)
// do the drawing
(
var b, w;
b = Rect(100, 100, 700, 700);
w = Window("so-a-mazing", b);
w.view.background_(Color.black);
w.drawFunc = {
var p = ~grid.shape.rand;
var scale = b.width / ~grid.size * 0.98;
Pen.moveTo(~topoint.(p, scale));
~walkN.(p, scale);
Pen.width = scale / 4;
Pen.color = Color.white;
Pen.stroke;
};
w.front.refresh;
)
Swift
import Foundation
extension Array {
mutating func shuffle() {
guard count > 1 else { return }
for i in 0..<self.count - 1 {
let j = Int(arc4random_uniform(UInt32(count - i))) + i
guard i != j else { continue }
swap(&self[i], &self[j])
}
}
}
enum Direction:Int {
case north = 1
case south = 2
case east = 4
case west = 8
static var allDirections:[Direction] {
return [Direction.north, Direction.south, Direction.east, Direction.west]
}
var opposite:Direction {
switch self {
case .north:
return .south
case .south:
return .north
case .east:
return .west
case .west:
return .east
}
}
var diff:(Int, Int) {
switch self {
case .north:
return (0, -1)
case .south:
return (0, 1)
case .east:
return (1, 0)
case .west:
return (-1, 0)
}
}
var char:String {
switch self {
case .north:
return "N"
case .south:
return "S"
case .east:
return "E"
case .west:
return "W"
}
}
}
class MazeGenerator {
let x:Int
let y:Int
var maze:[[Int]]
init(_ x:Int, _ y:Int) {
self.x = x
self.y = y
let column = [Int](repeating: 0, count: y)
self.maze = [[Int]](repeating: column, count: x)
generateMaze(0, 0)
}
private func generateMaze(_ cx:Int, _ cy:Int) {
var directions = Direction.allDirections
directions.shuffle()
for direction in directions {
let (dx, dy) = direction.diff
let nx = cx + dx
let ny = cy + dy
if inBounds(nx, ny) && maze[nx][ny] == 0 {
maze[cx][cy] |= direction.rawValue
maze[nx][ny] |= direction.opposite.rawValue
generateMaze(nx, ny)
}
}
}
private func inBounds(_ testX:Int, _ testY:Int) -> Bool {
return inBounds(value:testX, upper:self.x) && inBounds(value:testY, upper:self.y)
}
private func inBounds(value:Int, upper:Int) -> Bool {
return (value >= 0) && (value < upper)
}
func display() {
let cellWidth = 3
for j in 0..<y {
// Draw top edge
var topEdge = ""
for i in 0..<x {
topEdge += "+"
topEdge += String(repeating: (maze[i][j] & Direction.north.rawValue) == 0 ? "-" : " ", count: cellWidth)
}
topEdge += "+"
print(topEdge)
// Draw left edge
var leftEdge = ""
for i in 0..<x {
leftEdge += (maze[i][j] & Direction.west.rawValue) == 0 ? "|" : " "
leftEdge += String(repeating: " ", count: cellWidth)
}
leftEdge += "|"
print(leftEdge)
}
// Draw bottom edge
var bottomEdge = ""
for _ in 0..<x {
bottomEdge += "+"
bottomEdge += String(repeating: "-", count: cellWidth)
}
bottomEdge += "+"
print(bottomEdge)
}
func displayInts() {
for j in 0..<y {
var line = ""
for i in 0..<x {
line += String(maze[i][j]) + "\t"
}
print(line)
}
}
func displayDirections() {
for j in 0..<y {
var line = ""
for i in 0..<x {
line += getDirectionsAsString(maze[i][j]) + "\t"
}
print(line)
}
}
private func getDirectionsAsString(_ value:Int) -> String {
var line = ""
for direction in Direction.allDirections {
if (value & direction.rawValue) != 0 {
line += direction.char
}
}
return line
}
}
let x = 20
let y = 10
let maze = MazeGenerator(x, y)
maze.display()
- Output:
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | | | | | | +---+---+ +---+ + +---+---+---+---+---+ +---+ + +---+---+ + + + | | | | | | | | | + +---+---+ + + + +---+ +---+ +---+---+---+---+---+---+---+---+ + | | | | | | | | | | | +---+ + +---+ + + + +---+ +---+---+---+ + +---+---+ + + + | | | | | | | | | | | | | | + +---+ + +---+ +---+ + + + + +---+---+---+ +---+ +---+ + | | | | | | | | | | | + + +---+---+ + +---+---+ +---+---+---+ + +---+---+ + + +---+ | | | | | | | | | | | | + +---+ + +---+---+ +---+---+---+ + + + + + +---+ +---+ + | | | | | | | | | | | | | + + + +---+---+---+ +---+ + +---+ + + + + +---+---+---+ + | | | | | | | | | | | | | + +---+---+ +---+ + + +---+---+ + +---+ + + + +---+---+---+ | | | | | | | | | | | | | +---+---+ + + +---+---+ + +---+---+---+ + + + +---+---+ + + | | | | | | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Tcl
package require TclOO; # Or Tcl 8.6
# Helper to pick a random number
proc rand n {expr {int(rand() * $n)}}
# Helper to pick a random element of a list
proc pick list {lindex $list [rand [llength $list]]}
# Helper _function_ to index into a list of lists
proc tcl::mathfunc::idx {v x y} {lindex $v $x $y}
oo::class create maze {
variable x y horiz verti content
constructor {width height} {
set y $width
set x $height
set n [expr {$x * $y - 1}]
if {$n < 0} {error "illegal maze dimensions"}
set horiz [set verti [lrepeat $x [lrepeat $y 0]]]
# This matrix holds the output for the Maze Solving task; not used for generation
set content [lrepeat $x [lrepeat $y " "]]
set unvisited [lrepeat [expr {$x+2}] [lrepeat [expr {$y+2}] 0]]
# Helper to write into a list of lists (with offsets)
proc unvisited= {x y value} {
upvar 1 unvisited u
lset u [expr {$x+1}] [expr {$y+1}] $value
}
lappend stack [set here [list [rand $x] [rand $y]]]
for {set j 0} {$j < $x} {incr j} {
for {set k 0} {$k < $y} {incr k} {
unvisited= $j $k [expr {$here ne [list $j $k]}]
}
}
while {0 < $n} {
lassign $here hx hy
set neighbours {}
foreach {dx dy} {1 0 0 1 -1 0 0 -1} {
if {idx($unvisited, $hx+$dx+1, $hy+$dy+1)} {
lappend neighbours [list [expr {$hx+$dx}] [expr {$hy+$dy}]]
}
}
if {[llength $neighbours]} {
lassign [set here [pick $neighbours]] nx ny
unvisited= $nx $ny 0
if {$nx == $hx} {
lset horiz $nx [expr {min($ny, $hy)}] 1
} else {
lset verti [expr {min($nx, $hx)}] $ny 1
}
lappend stack $here
incr n -1
} else {
set here [lindex $stack end]
set stack [lrange $stack 0 end-1]
}
}
rename unvisited= {}
}
# Maze displayer; takes a maze dictionary, returns a string
method view {} {
set text {}
for {set j 0} {$j < $x*2+1} {incr j} {
set line {}
for {set k 0} {$k < $y*4+1} {incr k} {
if {$j%2 && $k%4==2} {
# At the centre of the cell, put the "content" of the cell
append line [expr {idx($content, $j/2, $k/4)}]
} elseif {$j%2 && ($k%4 || $k && idx($horiz, $j/2, $k/4-1))} {
append line " "
} elseif {$j%2} {
append line "|"
} elseif {0 == $k%4} {
append line "+"
} elseif {$j && idx($verti, $j/2-1, $k/4)} {
append line " "
} else {
append line "-"
}
}
if {!$j} {
lappend text [string replace $line 1 3 " "]
} elseif {$x*2-1 == $j} {
lappend text [string replace $line end end " "]
} else {
lappend text $line
}
}
return [join $text \n]
}
}
# Demonstration
maze create m 11 8
puts [m view]
- Output:
+ +---+---+---+---+---+---+---+---+---+---+ | | | | +---+---+ +---+---+ + +---+ +---+ + | | | | | | + + +---+ +---+---+ +---+ + + + | | | | | | | | + +---+ +---+---+---+ + + + + + | | | | | | | | + + + + +---+---+ + +---+---+ + | | | | | | | | +---+---+---+---+ + +---+ + + +---+ | | | | | | | | + +---+---+ + + + + + +---+ + | | | | | | | | +---+ + +---+---+---+---+ + +---+ + | | +---+---+---+---+---+---+---+---+---+---+---+
TXR
Simple, Depth-First
Legend: cu = current location; vi = boolean hash of visited locations; pa = hash giving a list neighboring cells to which there is a path from a given cell.
@(bind (width height) (15 15))
@(do
(defvar *r* (make-random-state nil))
(defvar vi)
(defvar pa)
(defun neigh (loc)
(let ((x (from loc))
(y (to loc)))
(list (- x 1)..y (+ x 1)..y
x..(- y 1) x..(+ y 1))))
(defun make-maze-rec (cu)
(set [vi cu] t)
(each ((ne (shuffle (neigh cu))))
(cond ((not [vi ne])
(push ne [pa cu])
(push cu [pa ne])
(make-maze-rec ne)))))
(defun make-maze (w h)
(let ((vi (hash :equal-based))
(pa (hash :equal-based)))
(each ((x (range -1 w)))
(set [vi x..-1] t)
(set [vi x..h] t))
(each ((y (range* 0 h)))
(set [vi -1..y] t)
(set [vi w..y] t))
(make-maze-rec 0..0)
pa))
(defun print-tops (pa w j)
(each ((i (range* 0 w)))
(if (memqual i..(- j 1) [pa i..j])
(put-string "+ ")
(put-string "+----")))
(put-line "+"))
(defun print-sides (pa w j)
(let ((str ""))
(each ((i (range* 0 w)))
(if (memqual (- i 1)..j [pa i..j])
(set str `@str `)
(set str `@str| `)))
(put-line `@str|\n@str|`)))
(defun print-maze (pa w h)
(each ((j (range* 0 h)))
(print-tops pa w j)
(print-sides pa w j))
(print-tops pa w h)))
@;;
@(bind m @(make-maze width height))
@(do (print-maze m width height))
- Output:
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ | | | | | | | | | | + + + + + +----+----+ + +----+----+----+ + +----+ | | | | | | | | | | | | | | | | | | | | + +----+----+----+----+ +----+----+ +----+ + + +----+ + | | | | | | | | | | | | | | | | +----+----+----+ + + + +----+----+ +----+ + + +----+ | | | | | | | | | | | | | | | | | | | | | | | | + +----+ + + +----+ + +----+ + + + +----+ + | | | | | | | | | | | | | | | | | | | | +----+ + + +----+----+----+----+ +----+ + +----+----+ + | | | | | | | | | | | | | | | | + +----+ +----+----+----+ + +----+ +----+----+----+ + + | | | | | | | | | | | | | | +----+ + +----+----+----+----+----+----+----+----+----+ + + + | | | | | | | | | | | | + +----+ + +----+----+ +----+----+----+ + + +----+ + | | | | | | | | | | | | | | | | | | | | + +----+ + + + +----+----+ + +----+----+ + +----+ | | | | | | | | | | | | | | | | | | | | + + +----+ +----+ + +----+----+----+----+----+ + + + | | | | | | | | | | | | | | | | + +----+----+----+ +----+----+ + +----+----+ + +----+ + | | | | | | | | | | | | | | | | | | +----+----+ + + +----+ +----+ + + +----+----+ +----+ | | | | | | | | | | | | | | | | + + +----+----+----+ + +----+----+----+----+----+ + + + | | | | | | | | | | | | | | + +----+ +----+----+----+----+----+----+ +----+ +----+----+ + | | | | | | | | +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
Quality Breadth-First
The following is a complete, self-contained command line utility. We also drop use of the TXR pattern extraction language and work purely in TXR Lisp.
The algorithm is quite different from the previous. This version is not recursive. This algorithm divides the maze cells into visited cells, frontier cells and unvisited cells. As in the DFS version, border cells outside of the maze area are pre-initialized as visited, for convenience. The frontier set initially contains the upper left hand corner.
The algorithm's main loop iterates while there are frontier cells. As the generation progresses, unvisited cells adjacent to frontier cells added to the frontier set. Frontier cells that are only surrounded by other frontier cells or visited cells are removed from the frontier set and become visited cells. Eventually, all unvisited cells become frontier cells and then visited cells, at which point the frontier set becomes empty and the algorithm terminates.
At every step, the algorithm picks the first cell in the frontier list. In the code, the frontier cells are kept in a hash called fr
and also in a queue q
. The algorithm tries to extend the frontier around the frontier cell which is at the head of the queue q
by randomly choosing an adjacent unvisited cell. (If there is no such cell, the node is not a frontier node any more and is popped from the queue and fr
set). If an unvisited node is picked, then a two-way path is broken from the given frontier cell to that cell, and that cell is added to the frontier set. Important: the new frontier cell is added to the head of the queue, rather than the tail.
The algorithm is modified by a "straightness" parameter, which is used to initialize a counter. Every time a new frontier node is added to the front of the queue, the counter decrements. When it reaches zero, the frontier queue is scrambled, and the counter is reset. As long as the count is nonzero, the maze growth proceeds from the previously traversed node, because the new node is placed at the head of the queue. This behavior mimics the DFS algorithm, resulting in long corridors without a lot of branching.
At the user interface level, the straightness parameter is represented as a percentage. This percentage is converted to a number of cells based on the width and height of the maze. For instance if the straightness parameter is 15, and the maze size is 20x20, it means that 15% out of 400 cells, or 60 cells will be traversed before the queue is scrambled. Then another 60 will be traversed and the queue will be scrambled, and so forth.
(defvar vi) ;; visited hash
(defvar pa) ;; path connectivity hash
(defvar sc) ;; count, derived from straightness fator
(defun rnd-pick (list)
(if list [list (rand (length list))]))
(defun neigh (loc)
(let ((x (from loc))
(y (to loc)))
(list (- x 1)..y (+ x 1)..y
x..(- y 1) x..(+ y 1))))
(defun make-maze-impl (cu)
(let ((q (list cu))
(c sc))
(set [vi cu] t)
(while q
(let* ((cu (first q))
(ne (rnd-pick (remove-if vi (neigh cu)))))
(cond (ne (set [vi ne] t)
(push ne [pa cu])
(push cu [pa ne])
(push ne q)
(cond ((<= (dec c) 0)
(set q (shuffle q))
(set c sc))))
(t (pop q)))))))
(defun make-maze (w h sf)
(let ((vi (hash :equal-based))
(pa (hash :equal-based))
(sc (max 1 (trunc (* sf w h) 100))))
(each ((x (range -1 w)))
(set [vi x..-1] t)
(set [vi x..h] t))
(each ((y (range* 0 h)))
(set [vi -1..y] t)
(set [vi w..y] t))
(make-maze-impl 0..0)
pa))
(defun print-tops (pa w j)
(each ((i (range* 0 w)))
(if (memqual i..(- j 1) [pa i..j])
(put-string "+ ")
(put-string "+----")))
(put-line "+"))
(defun print-sides (pa w j)
(let ((str ""))
(each ((i (range* 0 w)))
(if (memqual (- i 1)..j [pa i..j])
(set str `@str `)
(set str `@str| `)))
(put-line `@str|\n@str|`)))
(defun print-maze (pa w h)
(each ((j (range* 0 h)))
(print-tops pa w j)
(print-sides pa w j))
(print-tops pa w h))
(defun usage ()
(let ((invocation (ldiff *full-args* *args*)))
(put-line "usage: ")
(put-line `@invocation <width> <height> [<straightness>]`)
(put-line "straightness-factor is a percentage, defaulting to 15")
(exit 1)))
(let ((args [mapcar int-str *args*])
(*random-state* (make-random-state nil)))
(if (memq nil args)
(usage))
(tree-case args
((w h s ju . nk) (usage))
((w h : (s 15)) (set w (max 1 w))
(set h (max 1 h))
(print-maze (make-maze w h s) w h))
(else (usage))))
- Output:
Three mazes are generated, at the lowest, intermediate and highest "straightness factors".
It is immediately obvious that the style of each maze is quite different.
# 10x10 maze with zero percent "straightness factor" $ txr maze-generation3.txr 10 10 0 +----+----+----+----+----+----+----+----+----+----+ | | | | | | | | + + +----+----+ + + +----+----+----+ | | | | | | | | | | | | + + +----+ +----+----+----+----+ + + | | | | | | | | | | | | + +----+ +----+ +----+ +----+----+----+ | | | | | | +----+ + + + + + +----+----+----+ | | | | | | | | | | | | | | +----+ + +----+----+ +----+----+----+ + | | | | | | | | | | + +----+ +----+----+ + + +----+ + | | | | | | | | | | | | | | + +----+ + + + + + + + + | | | | | | | | | | | | | | | | | | | | +----+ + +----+ + + +----+----+ + | | | | | | | | | | | | | | + + + + +----+----+----+----+----+ + | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ # with 10% straightnes factor $ txr maze-generation3.txr 10 10 10 +----+----+----+----+----+----+----+----+----+----+ | | | | | | | | | | | | + + +----+ + + + + +----+ + | | | | | | | | | | + +----+----+ +----+----+----+----+----+----+ | | | | | | | | | | +----+ + +----+ + +----+----+ + + | | | | | | | | | | | | | | + +----+----+ +----+ + + + +----+ | | | | | | | | | | | | | | + + +----+----+----+----+----+----+ + + | | | | | | | | + + +----+ + + + +----+----+----+ | | | | | | | | | | | | | | | | | | + +----+ +----+ +----+ + + + + | | | | | | | | | | | | + + +----+----+ +----+ + +----+----+ | | | | | | | | | | | | | | +----+----+ +----+ + +----+----+ + + | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ # with 100 percent straight factor $ txr maze-generation3.txr 10 10 100 +----+----+----+----+----+----+----+----+----+----+ | | | | | | | | +----+ +----+ +----+----+ + + + + | | | | | | | | | | | | | | | | + +----+ +----+----+----+ + + + + | | | | | | | | | | | | | | | | + +----+ + + + +----+ +----+ + | | | | | | | | | | | | | | + + +----+ + + + +----+----+ + | | | | | | | | | | | | | | | | + + +----+ + +----+ + +----+----+ | | | | | | | | | | | | + +----+----+----+----+ +----+----+----+ + | | | | | | | | | | + +----+----+ + +----+----+ + + + | | | | | | | | | | | | | | + + + +----+ + + +----+----+ + | | | | | | | | | | | | + +----+----+ +----+----+----+----+----+ + | | | | | | +----+----+----+----+----+----+----+----+----+----+
TypeScript
randomized depth first search function to create maze
interface mazeData{
nodes:Node[]
x:number
y:number
blockSize:number
}
class Node{
x:number
y:number
wallsTo:Node[]
visited = false
constructor(x:number,y:number){
this.x = x
this.y = y
}
getTouchingNodes(nodes:Node[],blockSize:number){
return nodes.filter(n=>
(this != n) &&
(Math.hypot(this.x-n.x,this.y-n.y) == blockSize )
)
}
draw(ctx:CanvasRenderingContext2D,blockSize:number){
ctx.fillStyle ='black'
this.wallsTo.forEach(el=>{
ctx.save()
ctx.translate(this.x,this.y)
ctx.rotate(Math.atan2(this.y-el.y,this.x-el.x)+ Math.PI)
ctx.beginPath()
ctx.moveTo(blockSize/2,blockSize/2)
ctx.lineTo(blockSize/2,-blockSize/2)
ctx.stroke()
ctx.restore()
})
}
}
export function maze(x:number,y:number):mazeData {
let blockSize = 20
x *= blockSize
y *= blockSize
let nodes = Array((x/blockSize)*(y/blockSize)).fill(0).map((_el,i)=>
new Node(
(i%(x/blockSize)*(blockSize))+(blockSize/2),
(Math.floor(i/(x/blockSize))*(blockSize))+(blockSize/2)
)
)
nodes.forEach(n=>n.wallsTo = n.getTouchingNodes(nodes,blockSize))
let que = [nodes[0]]
while(que.length > 0){
let current = que.shift()
let unvisited = current
.getTouchingNodes(nodes,blockSize)
.filter(el=>!el.visited)
if(unvisited.length >0){
que.push(current)
let chosen = unvisited[Math.floor(Math.random()*unvisited.length)];
current.wallsTo = current.wallsTo.filter((el)=>el != chosen)
chosen.wallsTo = chosen.wallsTo.filter((el)=>el != current)
chosen.visited = true
que.unshift(chosen)
}
}
return {x:x,y:y,nodes:nodes,blockSize:blockSize}
}
export function display(c:HTMLCanvasElement,mazeData:mazeData){
let ctx = c.getContext('2d')
c.width = mazeData.x
c.height = mazeData.y
ctx.fillStyle = 'white'
ctx.strokeStyle = 'black'
ctx.fillRect(0,0,mazeData.x,mazeData.y)
ctx.strokeRect(0,0,mazeData.x,mazeData.y)
mazeData.nodes.forEach(el=>el.draw(ctx,mazeData.blockSize))
}
maze creation using html canvas
import { maze,display } from "./maze"
const X = 10
const Y = 10
let canvas = document.createElement('canvas')
document.body.appendChild(canvas)
let m = maze(X,Y)
display(canvas,m)
- Output:
rendered 10x20 maze
Uiua
Inspired by the Python algorithm, so will produce very similar output.
NOTE: this code is extended in the Maze Solving page so please update that page if you change this code.
# Build and solve a maze.
# Experimental!
W ← 10
H ← 6
GetWall ← -:⊡1:÷2/-.
# ([here, next], maze) -> (maze')
BreakWall ← ⍜(⊡|⋅@ )GetWall
Nfour ← +⊙¤[¯2_0 2_0 0_2 0_¯2] # Gives N4
Shuffle ← ⊏⍏[⍥⚂]⧻.
# (pos maze) -> T if it's already a star.
IsVisited ← ≠@.⊡
MarkAsVisited ← ⟜(⍜⊡⋅@.)
# (pos) -> (bool)
InBounds ← ▽⊸≡(↧⊃(/↧≥1_1|/↧< +1 × 2 H_W))
# (here, maze) -> (maze')
Walk ← |2 (
MarkAsVisited
# (here, maze) -> ([[here, next] x(up to)4], maze)
≡⊟¤⟜(Shuffle InBounds Nfour)
# Update maze for each in turn. For each, if it
# still isn't visited, break the wall, recurse into it.
∧(⨬(◌|Walk⊡1⟜BreakWall)IsVisited◌°⊟,,)
)
# Generate a maze.
Maze ← (
↘¯1☇1↯(+1H)⊟⊂/⊂↯W"+-" @+⊂/⊂↯W"| " @| # Build a filled maze.
Walk 1_1 # Walk around breaking walls.
)
Maze
- Output:
╭─ ╷ "+-+-+-+-+-+-+-+-+-+-+" "|.|.|. .|. . . . . .|" "+ + + + + +-+-+ +-+ +" "|.|. .|. . .|. .|. .|" "+ + +-+-+-+-+ +-+ +-+" "|.|.|. . . .|.|. . .|" "+ + + +-+-+ + +-+-+ +" "|.|.|. .|. .|. .|. .|" "+ +-+-+ + +-+-+ + +-+" "|. . .|.|. . . .|. .|" "+-+-+ + +-+-+-+-+-+ +" "|. . . .|. . . . . .|" "+-+-+-+-+-+-+-+-+-+-+" ╯
Wren
import "random" for Random
import "os" for Process
var Rand = Random.new()
class Direction {
static n { __n }
static s { __s }
static e { __e }
static w { __w }
static init() {
__n = new_(1, 0, -1)
__s = new_(2, 0, 1)
__e = new_(4, 1, 0)
__w = new_(8, -1, 0)
__n.opposite = __s
__s.opposite = __n
__e.opposite = __w
__w.opposite = __e
}
construct new_(bit, dx, dy) {
_bit = bit
_dx = dx
_dy = dy
_opposite = null
}
bit { _bit }
dx { _dx }
dy { _dy }
opposite { _opposite }
opposite=(d) { _opposite = d }
}
Direction.init()
class MazeGenerator {
construct new(x, y) {
_x = x
_y = y
_maze = List.filled(x, null)
for (i in 0...x) _maze[i] = List.filled(y, 0)
}
between_(v, upper) { v >= 0 && v < upper }
generate(cx, cy) {
var values = [Direction.n, Direction.s, Direction.e, Direction.w]
Rand.shuffle(values)
values.each { |v|
var nx = cx + v.dx
var ny = cy + v.dy
if (between_(nx, _x) && between_(ny, _y) && _maze[nx][ny] == 0) {
_maze[cx][cy] = _maze[cx][cy] | v.bit
_maze[nx][ny] = _maze[nx][ny] | v.opposite.bit
generate(nx, ny)
}
}
}
display() {
for (i in 0..._y) {
// draw the north edge
for (j in 0..._x) System.write((_maze[j][i] & 1) == 0 ? "+---" : "+ ")
System.print("+")
// draw the west edge
for (j in 0..._x) System.write((_maze[j][i] & 8) == 0 ? "| " : " ")
System.print("|")
}
// draw the bottom line
for (j in 0..._x) System.write("+---")
System.print("+")
}
}
var args = Process.arguments
var x = (args.count >= 1) ? Num.fromString(args[0]) : 8
var y = (args.count == 2) ? Num.fromString(args[1]) : 8
var mg = MazeGenerator.new(x, y)
mg.generate(0, 0)
mg.display()
- Output:
Sample 8 x 8 maze:
+---+---+---+---+---+---+---+---+ | | | | | + + + + + + +---+ + | | | | | | + + + +---+---+---+---+ + | | | | | | | + +---+ +---+ + + + + | | | | | +---+---+---+ +---+ +---+---+ | | | | + + + +---+---+---+---+ + | | | | | | + + + + +---+---+ + + | | | | | | | + + +---+---+ +---+ + + | | | +---+---+---+---+---+---+---+---+
XPL0
code Ran=1, CrLf=9, Text=12; \intrinsic routines
def Cols=20, Rows=6; \dimensions of maze (cells)
int Cell(Cols+1, Rows+1, 3); \cells (plus right and bottom borders)
def LeftWall, Ceiling, Connected; \attributes of each cell (= 0, 1 and 2)
proc ConnectFrom(X, Y); \Connect cells starting from cell X,Y
int X, Y;
int Dir, Dir0;
[Cell(X, Y, Connected):= true; \mark current cell as connected
Dir:= Ran(4); \randomly choose a direction
Dir0:= Dir; \save this initial direction
repeat case Dir of \try to connect to cell at Dir
0: if X+1<Cols & not Cell(X+1, Y, Connected) then \go right
[Cell(X+1, Y, LeftWall):= false; ConnectFrom(X+1, Y)];
1: if Y+1<Rows & not Cell(X, Y+1, Connected) then \go down
[Cell(X, Y+1, Ceiling):= false; ConnectFrom(X, Y+1)];
2: if X-1>=0 & not Cell(X-1, Y, Connected) then \go left
[Cell(X, Y, LeftWall):= false; ConnectFrom(X-1, Y)];
3: if Y-1>=0 & not Cell(X, Y-1, Connected) then \go up
[Cell(X, Y, Ceiling):= false; ConnectFrom(X, Y-1)]
other []; \(never occurs)
Dir:= Dir+1 & $03; \next direction
until Dir = Dir0;
];
int X, Y;
[for Y:= 0 to Rows do
for X:= 0 to Cols do
[Cell(X, Y, LeftWall):= true; \start with all walls and
Cell(X, Y, Ceiling):= true; \ ceilings in place
Cell(X, Y, Connected):= false; \ and all cells disconnected
];
Cell(0, 0, LeftWall):= false; \make left and right doorways
Cell(Cols, Rows-1, LeftWall):= false;
ConnectFrom(Ran(Cols), Ran(Rows)); \randomly pick a starting cell
for Y:= 0 to Rows do \display the maze
[CrLf(0);
for X:= 0 to Cols do
Text(0, if X#Cols & Cell(X, Y, Ceiling) then "+--" else "+ ");
CrLf(0);
for X:= 0 to Cols do
Text(0, if Y#Rows & Cell(X, Y, LeftWall) then "| " else " ");
];
]
Output:
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | | | | + +--+ +--+ +--+--+--+ + +--+ + +--+--+--+--+--+--+ + | | | | | | | | | | | | + + + + +--+ + +--+--+ + +--+--+ +--+--+ +--+ +--+ | | | | | | | | | | | + +--+--+ + +--+ + +--+--+--+--+--+--+ + +--+ +--+ + | | | | | | | | | | +--+ + +--+--+ + + + +--+--+ + +--+--+--+--+--+--+ + | | | | | | | | | | | | | + + + + +--+--+ + + + + +--+--+ + + + + +--+--+ | | | | | | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
zkl
Generate a maze without marked entry or exit points.
fcn make_maze(w = 16, h = 8){
// make arrays with lists of lists (all mutable)
vis:=(w.pump(List().write,0)+1)*h + w.pump(List().write,1);
ver:=(w.pump(List().write,T(Void,"| ")) + "|")*h + T;
hor:=(w.pump(List().write,T(Void,"+---")) + "+")*(h + 1);
fcn(x,y,vis,ver,hor){
vis[y][x] = 1;
d:=L(T(x - 1, y), T(x, y + 1), T(x + 1, y), T(x, y - 1)).shuffle();
foreach xx,yy in (d){
if(vis[yy][xx]) continue;
if(xx==x) hor[y.max(yy)][x]="+ ";
if(yy==y) ver[y][x.max(xx)]=" ";
self.fcn(xx,yy,vis,ver,hor);
}
}((0).random(w),(0).random(h),vis,ver,hor);
foreach a,b in (hor.zip(ver)) { println(a.concat(),"\n",b.concat()) }
return(ver,hor);
}
make_maze();
- Output:
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | | | | | | +---+---+ + + +---+ + + + + +---+---+---+ + + | | | | | | | | | | | + +---+ + + +---+---+ +---+---+ + +---+ + + + | | | | | | | | | | | | + + +---+ + + + + +---+ +---+ + +---+ + + | | | | | | | | | | | + + +---+ +---+---+---+---+ +---+ +---+ + +---+ + | | | | | | | | | | + +---+ +---+ + + + +---+ +---+ +---+---+ +---+ | | | | | | | | | | + +---+---+---+---+ + + + + + +---+---+---+---+ + | | | | | | | | | | + + + + + + + + +---+---+---+ +---+---+ + + | | | | | | | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
- Programming Tasks
- Games
- WikipediaSourced
- 11l
- Action!
- Ada
- Aime
- APL
- AutoHotkey
- AWK
- BASIC
- QB64
- BASIC256
- Batch File
- BBC BASIC
- Befunge
- C
- C sharp
- C++
- Chapel
- Clojure
- Commodore BASIC
- Common Lisp
- D
- Delphi
- EasyLang
- EDSAC order code
- EGL
- Elixir
- Elm
- Emacs Lisp
- Cl-lib
- Erlang
- F Sharp
- Forth
- FreeBASIC
- Fōrmulæ
- FutureBasic
- Pages with broken file links
- Go
- Haskell
- Huginn
- Icon
- Unicon
- Icon Programming Library
- J
- Java
- JavaScript
- Jq
- Julia
- Kotlin
- Lua
- M2000 Interpreter
- Mathematica
- Wolfram Language
- MATLAB
- Octave
- Nim
- Node.js
- OCaml
- Ol
- Perl
- Phix
- PHP
- Picat
- PicoLisp
- PL/I
- POV-Ray
- Processing
- Processing Python mode
- Prolog
- PureBasic
- Python
- Racket
- Raku
- Rascal
- Red
- REXX
- Ruby
- Rust
- Scala
- Sidef
- SuperCollider
- Swift
- Tcl
- TXR
- TypeScript
- Uiua
- Wren
- XPL0
- Zkl