Last active
December 15, 2024 18:29
-
-
Save mscha/6326e187635af590891a62723dd9cf09 to your computer and use it in GitHub Desktop.
Advent of Code 2024 day 15
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env raku | |
use v6.d; | |
$*OUT.out-buffer = False; # Autoflush | |
# Advent of Code 2024 day 15 -- https://adventofcode.com/2024/day/15 | |
enum Direction <north east south west>; | |
sub left(Direction $d --> Direction) { Direction(($d - 1) % 4) } | |
sub right(Direction $d --> Direction) { Direction(($d + 1) % 4) } | |
sub turn(Direction $d --> Direction) { Direction(($d + 2) % 4) } | |
constant %DIRECTION = «^ > v <» »=>« Direction::.values.sort; | |
class Position | |
{ | |
has Int $.x; | |
has Int $.y; | |
my Int %dx{Direction} = north, 0, east,1, south,0, west,-1; | |
my Int %dy{Direction} = north,-1, east,0, south,1, west, 0; | |
method step(Direction $d, Int $count = 1) | |
{ | |
pos($!x + $count*%dx{$d}, $!y + $count*%dy{$d}) | |
} | |
method neighbours { Direction::.values.map({ self.step($_) }) } | |
method dir-neighbours { Direction::.values.map({ $_ => self.step($_) }) } | |
method Str { "($!x,$!y)" } | |
method gist { self.Str } | |
method WHICH { ValueObjAt.new("Position|$!x|$!y") } | |
} | |
multi sub pos(Int() $x, Int() $y) { Position.new(:$x, :$y) } | |
multi sub pos(@xy) { pos(@xy[0], @xy[1]) } | |
class Warehouse | |
{ | |
has $.map; | |
has $.instructions; | |
has $.verbose = False; | |
has @!grid = $!map.lines.map({ [.comb] }); | |
has $!width = @!grid[0].elems; | |
has $!height = @!grid.elems; | |
has @!moves = $!instructions.comb(/\S/).map({ %DIRECTION{$_} }); | |
has $!robot-pos = pos((^$!width X ^$!height).first( | |
-> ($x,$y) { @!grid[$y;$x] eq '@' })); | |
method at(Position:D $pos) is rw { @!grid[$pos.y;$pos.x] } | |
method all-pos { (^$!width X ^$!height).map({ pos($_) }) } | |
method all-pos-of(*@cells) { self.all-pos.grep({ self.at($_) eq any(@cells) }) } | |
# Check if a position is available for a movement in a given direction | |
method can-move($pos, $dir) | |
{ | |
my $new-pos = $pos.step($dir); | |
given self.at($new-pos) { | |
# Walls are unavailable | |
when '#' { return False } | |
# Empty space is available | |
when '.' { return True } | |
# Boxes are available if they can themselves be moved | |
when 'O' { return self.can-move($new-pos, $dir) } | |
# For big boxes, we need to check the other half, when we're | |
# moving vertically. | |
when '[' { | |
if $dir == north|south { | |
return self.can-move($new-pos, $dir) && | |
self.can-move($new-pos.step(east), $dir); | |
} | |
elsif $dir == east { | |
return self.can-move($new-pos.step($dir), $dir); | |
} | |
elsif $dir == west { | |
return self.can-move($new-pos, $dir); | |
} | |
} | |
when ']' { | |
if $dir == north|south { | |
return self.can-move($new-pos, $dir) && | |
self.can-move($new-pos.step(west), $dir); | |
} | |
elsif $dir == east { | |
return self.can-move($new-pos, $dir); | |
} | |
elsif $dir == west { | |
return self.can-move($new-pos.step($dir), $dir); | |
} | |
} | |
default { die "Unexpected '$_' in grid at $pos!" } | |
} | |
} | |
# Move a cell from a given position into the given direction | |
method move($pos, $dir) | |
{ | |
# Nothing to move? We're done. | |
my $cell = self.at($pos); | |
return if $cell eq '.'; | |
# Check what's at the new position, and try to move it if necessary | |
my $new-pos = $pos.step($dir); | |
given self.at($new-pos) { | |
# Walls can't move. (Shouldn't happen, we checked beforehand.) | |
when '#' { die "Can't move into a wall at $pos!" } | |
# Empty cells don't need any action | |
when '.' { } | |
# Boxes need to move | |
when 'O' { self.move($new-pos, $dir) } | |
# Big boxes need to move. When moving vertically, also move the | |
# other half. (Horizontally, that happens automatically.) | |
when '[' { | |
if $dir == north|south { | |
self.move($new-pos, $dir); | |
self.move($new-pos.step(east), $dir); | |
} | |
else { | |
self.move($new-pos, $dir); | |
} | |
} | |
when ']' { | |
if $dir == north|south { | |
self.move($new-pos, $dir); | |
self.move($new-pos.step(west), $dir); | |
} | |
else { | |
self.move($new-pos, $dir); | |
} | |
} | |
default { die "Unexpected '$_' in grid at $pos!" } | |
} | |
# If there's nothing left to move, we're done. | |
return if self.at($pos) eq '.'; | |
# Move the cell | |
self.at($new-pos) = self.at($pos); | |
self.at($pos) = '.'; | |
# If half of a big box, and going vertically, move the other half | |
self.move($pos.step(east), $dir) if $cell eq '[' && $dir == north|south; | |
self.move($pos.step(west), $dir) if $cell eq ']' && $dir == north|south; | |
} | |
method move-robot($dir) | |
{ | |
say "Try to move robot $dir from $!robot-pos ..." if $!verbose; | |
if self.can-move($!robot-pos, $dir) { | |
self.move($!robot-pos, $dir); | |
$!robot-pos .= step($dir); | |
say self if $!verbose; | |
return True; | |
} | |
else { | |
say "Unable to move!" if $!verbose; | |
return False; | |
} | |
} | |
method patrol | |
{ | |
say self if $!verbose; | |
for @!moves -> $dir { | |
self.move-robot($dir); | |
} | |
} | |
method total-gps-coordinates | |
{ | |
return self.all-pos-of('O', '[').map({ .x + 100*.y }).sum; | |
} | |
method Str { @!grid».join.join("\n") } | |
method gist { self.Str } | |
} | |
sub MAIN(IO() $inputfile where *.f = 'aoc15.input', Bool :v(:$verbose) = False) | |
{ | |
my ($map, $instructions) = $inputfile.split(/\n\n/); | |
my $wh1 = Warehouse.new(:$map, :$instructions, :$verbose); | |
$wh1.patrol; | |
say "Part 1: ", $wh1.total-gps-coordinates; | |
$map .= trans(<# O . @> => <## [] .. @.>); | |
my $wh2 = Warehouse.new(:$map, :$instructions, :$verbose); | |
$wh2.patrol; | |
say "Part 2: ", $wh2.total-gps-coordinates; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment