Maze generation: Difference between revisions

Content added Content deleted
m (→‎{{header|Perl 5}}: fix headers)
(→‎{{header|Perl}}: shortened somewhat)
Line 906: Line 906:


=={{header|Perl}}==
=={{header|Perl}}==
<lang perl>use List::Util 'max';


my ($w, $h) = @ARGV;
Tested on Perl 5.8, but should work on any Perl5 installation on most platforms.
$w ||= 26;
$h ||= 127;
my $avail = $w * $h;


# cell is padded by sentinel col and row, so I don't check array bounds
This implementation observes that due to the grid formation of the maze, reflection can be used to simplify the algorithm. Walls are only removed on the East (right) and South (bottom) walls (if those walls are not part of the maze boundary).
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 {
<lang perl>use strict;
my ($x, $y) = @_;
$cell[$y][$x] = '';
$avail-- or return; # no more bottles, er, cells


my $dimension = $ARGV[0] || 7;
my @d = ([-1, 0], [0, 1], [1, 0], [0, -1]);
while (@d) {
my $maze = init_maze($dimension, $dimension);
my $i = splice @d, int(rand @d), 1;
my ($x1, $y1) = ($x + $i->[0], $y + $i->[1]);


$cell[$y1][$x1] or next;
my ($startRow, $startCol) = (int(rand($dimension)), int(rand($dimension)));
make_maze($maze, $startRow, $startCol, undef);


if ($x == $x1) { $hor[ max($y1, $y) ][$x] = '+ ' }
print_maze($maze);
if ($y == $y1) { $ver[$y][ max($x1, $x) ] = ' ' }

walk($x1, $y1);
#---Subs---
}
sub init_maze {
my ($max_row, $max_col) = @_;

my $maze = [];
for (my $r=0; $r < $max_row; $r++) {
for (my $c=0; $c < $max_col; $c++) {
push @{$maze->[$r]}, {'visited' => 0,
'bottom' => 1,
'right' => 1,
};
}
}
return $maze;
}
}


walk(int rand $w, int rand $h); # generate
sub make_maze {
my ($maze, $row, $col) = @_;

$maze->[$row]->[$col]->{'visited'} = 1;

while (my $unvisited = get_unvisited($maze, $row, $col)) {
last unless @$unvisited;

# Randomly select a neighbor
my $choice = $unvisited->[ rand(@$unvisited) ];

# Knock down the wall between them
remove_wall($maze, [$row, $col], $choice);

# move to this new cell
make_maze($maze, $choice->[0], $choice->[1]);
}
}

sub remove_wall {
my ($maze, $r1, $r2) = @_;
my $selected;

if ( $r1->[0] == $r2->[0] ) {
# Rows are equal, must be East/West neighbors
$selected = ($r1->[1] < $r2->[1]) ? $r1 : $r2;
$maze->[ $selected->[0] ]->[ $selected->[1] ]->{'right'} = 0;

} elsif ( $r1->[1] == $r2->[1] ) {
# Columns are the same, must be North/South neighbors
$selected = ($r1->[0] < $r2->[0]) ? $r1 : $r2;
$maze->[ $selected->[0] ]->[ $selected->[1] ]->{'bottom'} = 0;
} else {
die("ERROR: bad neighbors ($r1->[0], $r1->[1]) and ($r2->[0], $r2->[1])\n");
}
return;
}

sub get_unvisited {
my ($maze, $row, $col) = @_;
my @found;

# look for neighbors in cardinal directions;
# be mindful of maze boundaries
if ($row == 0) {
push @found, [$row + 1, $col] unless $maze->[$row + 1]->[$col]{'visited'};
} elsif ($row == @$maze - 1) {
push @found, [$row - 1, $col] unless $maze->[$row - 1]->[$col]->{'visited'};
} else {
if ($row + 1 < @$maze) {
push @found, [$row + 1, $col] unless $maze->[$row + 1]->[$col]->{'visited'};
}
push @found, [$row - 1, $col] unless $maze->[$row - 1]->[$col]->{'visited'};
}

if ($col == 0) {
push @found, [$row, $col + 1] unless $maze->[$row]->[$col + 1]->{'visited'};
} elsif ($col == (@{$maze->[0]} - 1)) {
push @found, [$row, $col - 1] unless $maze->[$row]->[$col - 1]->{'visited'};
} else {
if ($col + 1 < @{$maze->[0]}) {
push @found, [$row, $col + 1] unless $maze->[$row]->[$col + 1]->{'visited'};
}
push @found, [$row, $col - 1] unless $maze->[$row]->[$col - 1]->{'visited'};
}

return \@found;
}

sub print_maze {
my ($maze) = @_;
my $screen = [];

my $screen_row = 0;
for (my $r=0; $r < @$maze; $r++) {
if ($r == 0) {
# Top border
push @{$screen->[$r]}, '+';
for (@{$maze->[0]}) {
push @{$screen->[$r]}, '--', '+';
}
}

for (my $c=0; $c < @{$maze->[0]}; $c++) {
my @middle;
if ($c == 0) {
push @middle, "|";
}

push @middle, " "; # room center
if ($maze->[$r]->[$c]->{'right'}) {
push @middle, "|";
} else {
push @middle, " ";
}
push @{$screen->[$screen_row + 1]}, @middle;

my @bottom;
if ($c == 0) {
push @bottom, "+";
}

if ($maze->[$r]->[$c]->{'bottom'}) {
push @bottom, "--";
} else {
push @bottom, " ";
}
push @bottom, "+";
push @{$screen->[$screen_row + 2]}, @bottom;
}
$screen_row += 2;
}

for (my $r=0; $r < @$screen; $r++) {
for (my $c=0; $c < @{$screen->[0]}; $c++) {
print $screen->[$r]->[$c];
}
print "\n";
}
print "\n";
}

</lang>

Sample output (7x7):


for (0 .. $h) { # display
<pre>
print @{$hor[$_]}, "+\n";
+--+--+--+--+--+--+--+
print @{$ver[$_]}, "|\n" if $_ < $h;
| | |
}</lang>run as <code>maze.pl [width] [height]</code> or use default dimensions. Sample 4 x 1 output:<lang>+--+--+--+--+
+ +--+ + +--+--+ +
| | | | |
| |
+--+ +--+--+--+ + +
+--+--+--+--+</lang>
| | | | |
+ +--+ + +--+ + +
| | | | |
+ + +--+--+ +--+--+
| | | |
+ +--+--+ +--+--+ +
| | | |
+--+ + +--+ + + +
| | | | |
+--+--+--+--+--+--+--+
</pre>


=={{header|Perl 6}}==
=={{header|Perl 6}}==