mscha icon

AoC 2016 day 24 - second version

mscha | PRO | 12/24/16 09:10:21 PM UTC | 0 ⭐ | 870 👁️ | Never ⏰ | []
Perl 6 |

0 B

|

None

|

0 👍

/

0 👎

#!/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:<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 $.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.";
}

Comments