Advent of Code Day 12 - sailing to a pause

This was another fun one. It took me some time to get the logic right (or left?), but in the end I came up with a nice solution. BTW, this will be the last daily blog post, as I'll have to finish some work projects, and start with some holiday projects.

https://adventofcode.com/2020/day/12

Part 1

my @in  = map { [/^(\w)(\d+)$/] } <>;
my @dir = qw(N E S W);
my $cur = 1;
my $r   = 0;
my $c   = 0;

We again parse the map into a list of ops and values. Then we set up some things we'll need, like the list of directions and the current heading ($cur) which is an index into the dir-array. $r and $c are the row and col where the ship currently is location (I prefer row/col to x/y).

my %ops;
%ops = (
    N => sub { $r -= shift },
    S => sub { $r += shift },
    E => sub { $c += shift },
    W => sub { $c -= shift },
    F => sub { $ops{ $dir[$cur] }(shift) },
    L => sub { $cur = ( $cur - $_[0] / 90 ) % 4 },
    R => sub { $cur = ( $cur + $_[0] / 90 ) % 4 },
);

Then we implement the ops. The first four simple move the ship around. F (forward) gets the current direction ("E" in the example), and calls the corresponding function. L and R where hard to get right. We calculate the number of turns (1, 2 or 3, depending on the degree value), and add or subtract it from the current index, using mod to stay inside the array of allowed directions (using negative modulus is always fun!)

for (@in) {
    my ( $op, $val ) = @$_;
    $ops{$op}($val);
}
say abs( 0 - $r ) + abs( 0 - $c );

Finally we just need to go through the instruction set and call each op; and then calculate the Manhattan distance...

In my first version I used plain functions instead of the dispatch table, which I liked a bit better, but required me to turn off 'refs' strictures.

Part 2

Again it took me quite some time to understand the description, but after some scribbling and counting I figured it out.

my $wr = -1;
my $wc = 10;
my $sr = 0;
my $sc = 0;

For the second part, we "just" need to add a second set of coordinates for the waypoint ($wr, $wc).

my %ops;
%ops = (
    N => sub { $wr -= shift },
    S => sub { $wr += shift },
    E => sub { $wc += shift },
    W => sub { $wc -= shift },
    F => sub { my $val = shift; $sr += $val * $wr; $sc += $val * $wc },
    R => sub { for ( 1 .. $_[0] / 90 ) { ( $wr, $wc ) = ( $wc, $wr ); $wc *= -1 } },
    L => sub { for ( 1 .. $_[0] / 90 ) { ( $wr, $wc ) = ( $wc, $wr ); $wr *= -1 } },
);

The ops change a bit, mostly moving the waypoint around. F will move the ship. I used pen & paper to discover that to rotate the waypoint I need to switch row and col and (depending on the direction) change the sign on the row or col (i.e. multiply by -1). To switch row and col, we use a nice Perl trick to switch two values around using fancy list context: ( $a, $b ) = ($b, $a).

The rest of the code stays the same...

Stats & Links