Skip to content

Instantly share code, notes, and snippets.

@mscha
Last active December 15, 2024 18:29
Show Gist options
  • Save mscha/6326e187635af590891a62723dd9cf09 to your computer and use it in GitHub Desktop.
Save mscha/6326e187635af590891a62723dd9cf09 to your computer and use it in GitHub Desktop.
Advent of Code 2024 day 15
#!/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