#!/usr/bin/env perl6 use v6.c; constant WALL = '█' but False; constant SPACE = '░' but True; constant TARGET = '★' but True; constant PATH = '·' but True; class Pos { has Int $.x; has Int $.y; method Str { "<$!x,$!y>" } method gist { self.Str } method infix:(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 $.width; has $.height; has @.grid; has @.target; has %.target-at; submethod BUILD(:@lines) { for @lines.kv -> $y, $line { @!grid.push: $line.comb.map({ when '#' { WALL }; when '.' { SPACE }; when '0'..'9' { TARGET }; }); for $line.match(/ <[0..9]> /, :g) { my $pos = pos($_.from, $y); @!target[$_] = $pos; %!target-at{$pos} = +$_; } } $!width = +@!grid[0;*]; $!height = +@!grid[*;0]; } #| 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 $!width > $x >= 0 && $!height > $y >= 0; # Check if we're on the path return PATH if $path.elems && pos($x,$y) eq any(@$path); # Return the cell return @!grid[$y;$x]; } #| 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 method draw() { for ^$!height -> $y { say (^$!width).map({ self.cell($_, $y) }).join; } } #| Draw the maze with a given path method draw-path($path) { 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; # 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]); $visited{$new} = True; } } } # No moves remailing, give up. return (); } } sub MAIN(IO() $inputfile where *.f, Bool :v(:$verbose)=False) { my $maze = Maze.new(:lines($inputfile.lines)); $maze.draw if $verbose; say '' if $verbose; # Create a table of all distances between targets my @distance; for $maze.target.keys.combinations(2) -> ($t1, $t2) { my @path = $maze.find-path($maze.target[$t1], $maze.target[$t2]); say "Distance from target $t1 to target $t2: { @path - 1 } steps" if $verbose; @distance[$t1;$t2] = @distance[$t2;$t1] = @path-1; } # Calculate distance for all permutations of targets my %total-distance; my %return-distance; for $maze.target.keys[1..*].permutations -> @p { my $key = @p.join('-'); %total-distance{$key} = (0,|@p).rotor(2=>-1).map({ @distance[$_[0];$_[1]] }).sum; %return-distance{$key} = %total-distance{$key} + @distance[@p[*-1];0]; } # Find (one of) the best path(s), both without and with return to target 0 my $best-total = %total-distance.keys.sort({ %total-distance{$_} })[0]; my $best-return = %return-distance.keys.sort({ %return-distance{$_} })[0]; say '' if $verbose; say "The shortest path without return is 0-{$best-total}; %total-distance{$best-total} steps."; say "The shortest path with return is 0-{$best-return}-0; %return-distance{$best-return} steps."; }