TL;DR

On with Advent of Code puzzle XX from 2022: one challenge that needed patience.

This was one of those challenges in which the solution - in particular, the solution for part 2 - was in that bitter spot between you need to do better and optimize this thing and you can get a solution in some minutes, just hold on.

I opted for the waiting part, of course.

The full solution is available; let’s just comment a few passages.

Each “planter elf” is represented as an object of class Planter:

class Planter {
has $.P is built; method pos {$!P.join(',') }

method make-proposal ($team,$direction is copy) {
state @deltas = [-1, -1], [-1, 0], [-1, 1],
[ 0, -1],          [ 0, 1],
[ 1, -1], [ 1, 0], [ 1, 1];
state @tests-for =
[2, 4, 7], # north
[0, 3, 5], # south
[0, 1, 2], # west
[5, 6, 7]; # east
state @move-for = [0, 1], [0, -1], [-1, 0], [1, 0];

my @is-empty = @deltas.map: { $team{($!P «+» $_).join(',')}:!exists }; return if @is-empty.all; for ^4 { return ($!P «+» @move-for[$direction]) if @is-empty[|@tests-for[$direction]].all;
$direction = ($direction + 1) % 4;
}

return;
}

method move-to ($Q) {$!P = $Q } }  The two phases are represented as methods: one to figure out a proposal (make-proposal), another one for actually doing the move. The planter is “controlled” externally, so there is no autonomous, local decision for doing the move. Moving in part 1 is done according to the rules, including tracking the direction of movements as time goes by: my$direction = -1;
for ^10 {
$direction = ($direction + 1) % 4;

# collect proposals, keep moving one separated
my (%moving, %freezing);
for %team.values -> $elf { my$proposal = $elf.make-proposal(%team,$direction) or next;
my $key =$proposal.join(',');
next if %freezing{$key}; if %moving{$key} {
%moving{$key}:delete; %freezing{$key} = 1;
}
else {
%moving{$key} = [$elf, $proposal]; } } # move for %moving.values -> ($elf, $target) { %team{$elf.pos}:delete;
$elf.move-to($target);
%team{$elf.pos} =$elf;
}
}


Depending on the propostals, elves/planters are tracked inside either %moving or %freezing. At the end of the proposals resolution phase, the actual movement phase happens, applied only to the elves/planters remained in %moving.

At this point, calculating the result is simple, keeping in mind that we have to remove the elves themselves because we need the empty locations:

my @Ps = %team.values».P;
my $area = [*] (0, 1).map({ my @Vs = @Ps»[$_]; 1 + @Vs.max - @Vs.min });

return \$area - %team.elems;


Part 2 is considerably less predictable, at least for me. I ended up extending the loop in part 1 and waiting until no movement happened, with the hope it would not take too much time. I was lucky, it only takes about 15 minutes to calculate the solution, so it’s good for me.

Still, I’ll go looking into the solutions megathread because I can’t imagine that there’s no better way of doing this.

Cheers!