#!/usr/bin/env perl6
use v6.c;
constant WALL = '█' but False;
constant SPACE = '░' but True;
constant PATH = '·' but True;
class Pos
{
has $.x;
has $.y;
method Str { "<$!x,$!y>" }
method gist { self.Str }
method infix:<eq>(Pos $a, Pos $b) { $a.x == $b.x && $a.y == $b.y }
method WHICH { "Pos|$!x|$!y" }
}
sub pos($x,$y) { Pos.new(:$x,:$y) }
class Maze
{
has $.id;
has @!grid;
#| Determine the contents of cell <$x,$y> with an optional path
multi method cell(Int $x, Int $y, $path=())
{
# Don't go outside the boundaries
return WALL unless $x >= 0 && $y >= 0;
# Check if we're on the path
return PATH if $path.elems && pos($x,$y) eq any(@$path);
# Calculate cell value if not yet cached, and return it
@!grid[$x;$y] //= ($x*$x + 3*$x +2*$x*$y + $y + $y*$y + $!id)\
.base(2).comb('1').elems %% 2 ?? SPACE !! WALL;
return @!grid[$x;$y];
}
#| Determine the contents of cell $p in the maze with an optional $path
multi method cell(Pos $p, $path=()) { self.cell($p.x, $p.y, $path); }
#| Draw the maze up to a given width and height
method draw(Int $width, Int $height)
{
for ^$height -> $y {
say (^$width).map({ self.cell($_, $y) }).join;
}
}
#| Draw the maze with a given path
method draw-path($path)
{
my $width = $path».x.max + 2;
my $height = $path».y.max + 2;
for ^$height -> $y {
say (^$width).map({ self.cell($_, $y, $path) }).join;
}
}
#| Find a path from $from to $to
method find-path(Pos $from, Pos $to)
{
my $visited = SetHash.new;
my @moves = [[$from, [],],];
while @moves {
# Try the next move in the queue
my ($pos, $path0) = @moves.shift;
my @path = |$path0[*], $pos;
$visited{$pos} = True;
# Are we there yet?
return @path if $pos eq $to;
# Add possible moves from this location
for pos($pos.x+1,$pos.y), pos($pos.x,$pos.y+1),
pos($pos.x-1,$pos.y), pos($pos.x,$pos.y-1) -> $new {
if self.cell($new) && $new ∉ $visited {
@moves.push([$new, @path]);
}
}
}
# No moves remailing, give up.
return ();
}
#| Find all positions that can be reached from $from in $max-moves moves
method find-positions(Pos $from, Int $max-moves)
{
my $visited = SetHash.new;
my @moves = [[$from, [],],];
while @moves {
# Try the next move in the queue
my ($pos, $path0) = @moves.shift;
my @path = |$path0[*], $pos;
$visited{$pos} = True;
# Are we done yet?
next if @path.elems - 1 == $max-moves;
# Add possible moves from this location
for pos($pos.x+1,$pos.y), pos($pos.x,$pos.y+1),
pos($pos.x-1,$pos.y), pos($pos.x,$pos.y-1) -> $new {
if self.cell($new) && $new ∉ $visited {
@moves.push([$new, @path]);
}
}
}
# Return all visited positions
return $visited.keys;
}
}
#| Solve maze with ID from input file
multi sub MAIN(IO() $inputfile where *.f,
Int :$x = 31, Int :$y = 39,
Int :$max-moves = 50)
{
my ($maze-id) = $inputfile.words;
MAIN($maze-id, :$x, :$y, :$max-moves);
}
#| Solve maze with ID on command line
multi sub MAIN(Str $maze-id where !*.IO.f,
Int :$x = 31, Int :$y = 39,
Int :$max-moves = 50)
{
my $maze = Maze.new(:id($maze-id));
say '';
say 'Part 1:';
my $from = pos(1,1);
my $to = pos($x,$y);
my @path = $maze.find-path($from, $to);
if @path {
say "Shortest path from $from to $to: {@path.elems-1} moves.";
say "";
$maze.draw-path(@path);
}
else {
say "Sorry, can't find a path from $from to $to!";
}
say '';
say 'Part 2:';
my @pos = $maze.find-positions($from, $max-moves);
if @pos {
say "Number of reachable positions in $max-moves moves: @pos.elems().";
say "";
$maze.draw-path(@pos);
}
else {
say "Sorry, can't find any positions from $from in $max-moves moves!";
}
}
Comments