]> www.fi.muni.cz Git - aoc.git/blob - 2018/29.pl
Day 25: examining the input
[aoc.git] / 2018 / 29.pl
1 #!/usr/bin/perl -w
2
3 use v5.36;
4 use strict;
5 use experimental 'multidimensional';
6
7 my @map = map { chomp; [ split // ] } <>;
8 my $xmax = $#{ $map[0] };
9 my $ymax = $#map;
10
11 my @units;
12 for my $y (0 .. $ymax) {
13         for my $x (0 .. $xmax) {
14                 if ($map[$y][$x] eq 'G') {
15                         push @units, [$x, $y, 'G', 200];
16                 } elsif ($map[$y][$x] eq 'E') {
17                         push @units, [$x, $y, 'E', 200];
18                 }
19         }
20 }
21
22 sub enemy_of($unit) { $unit->[2] eq 'G' ? 'E' : 'G' }
23
24 my @neighbors = ([0, -1], [-1, 0], [1, 0], [0, 1]);
25
26 sub reading_sort {
27         return sort { $a->[1] == $b->[1] ? $a->[0] <=> $b->[0] : $a->[1] <=> $b->[1] } @_;
28 }
29
30 sub print_map {
31         for my $y (0 .. $ymax) {
32                 for my $x (0 .. $xmax) {
33                         print $map[$y][$x];
34                 }
35                 print "\n";
36         }
37         for my $unit (grep { $_->[3] } reading_sort(@units)) {
38                 say "$unit->[2] at $unit->[0],$unit->[1] ($unit->[3])"
39         }
40 }
41
42 sub neigh_squares($unit, $type) {
43         my @rv;
44         for my $n (@neighbors) {
45                 my ($dx, $dy) = @$n;
46                 $dx += $unit->[0];
47                 $dy += $unit->[1];
48                 if ($map[$dy][$dx] eq $type) {
49                         push @rv, [ $dx, $dy ];
50                 }
51         }
52         return @rv;
53 }
54
55 sub enemy_within_range($unit) {
56         return neigh_squares($unit, enemy_of($unit));
57 }
58
59 sub loc2unit($loc) {
60         for my $unit (@units) {
61                 return $unit
62                         if $unit->[0] == $loc->[0] && $unit->[1] == $loc->[1];
63         }
64         die "Can't locate unit at ", join(',', @$loc);
65 }
66
67 sub attack($unit, $enemies) {
68         say "Attacking unit: ", join(',', @$unit);
69         my $min_hp;
70         my @min_hp;
71         for my $enemy (@$enemies) {
72                 say "Enemy: ", join(',', @$enemy);
73                 my $enemy_unit = loc2unit($enemy);
74                 if (!defined $min_hp || $min_hp > $enemy_unit->[3]) {
75                         $min_hp = $enemy_unit->[3];
76                         @min_hp = ();
77                 }
78                 if ($min_hp == $enemy_unit->[3]) {
79                         push @min_hp, $enemy_unit;
80                 }
81         }
82
83         @min_hp = reading_sort(@min_hp);
84         my $enemy = shift @min_hp;
85         say "attacking";
86         $enemy->[3] -= 3;
87         if ($enemy->[3] <= 0) {
88                 $enemy->[3] = 0;
89                 $map[$enemy->[1]][$enemy->[0]] = '.';
90         }
91         $enemy->[3] = 0 if $enemy->[3] < 0;
92         say "attacked ", join(',', @$enemy);
93 }
94
95 sub dump_path($path) {
96         join(' ', map { "[$_->[0],$_->[1]]" } @$path);
97 }
98
99 sub move($unit) {
100         my %target_sq;
101         for my $enemy (grep { $_->[2] ne $unit->[2] } @units) {
102                 for my $sq (neigh_squares($enemy, '.')) {
103                         if (!$target_sq{$sq->[0],$sq->[1]}++) {
104                                 # say "target square $sq->[0],$sq->[1]";
105                         }
106                 }
107         }
108
109         my @q = [ [ $unit->[0], $unit->[1] ] ];
110         my %visited;
111         my $found_at_dist;
112         my @reachable;
113         while (@q) {
114                 my $path = shift @q;
115                 # say "walking path ", dump_path($path);
116                 last if $found_at_dist && @$path > $found_at_dist;
117                 for my $sq (neigh_squares($path->[-1], '.')) {
118                         next if $visited{$sq->[0],$sq->[1]}++;
119
120                         if ($target_sq{$sq->[0],$sq->[1]}) {
121                                 $found_at_dist //= @$path;
122                                 my $path1 = [ @$path, [ @$sq ] ];
123                                 # say "Found square $sq->[0],$sq->[1] through ",
124                                 #       dump_path($path1);
125                                 push @reachable, [ @$sq, @{ $path1->[1] } ];
126                         } else {
127                                 push @q, [ @$path, [ @$sq ] ];
128                         }
129                 }
130         }
131
132         if (!@reachable) {
133                 # say "no reachable squares";
134                 return;
135         }
136
137         @reachable = reading_sort(@reachable);
138         my $target = $reachable[0];
139         # say "moving to $target->[0],$target->[1] via $target->[2],$target->[3]";
140         $map[$unit->[1]][$unit->[0]] = '.';
141         $map[$unit->[1] = $target->[3]][$unit->[0] = $target->[2]] = $unit->[2];
142 }
143
144 sub unit_round($unit) {
145         return if !$unit->[3]; # are we alive?
146         # say "\nunit_round ", join(',', @$unit);
147
148         my @enemies = enemy_within_range($unit);
149         if (!@enemies) {
150                 move($unit);
151                 @enemies = enemy_within_range($unit);
152         }
153         if (@enemies) {
154                 attack($unit, \@enemies);
155         }
156 }
157
158 my $round = 0;
159 my %total_hp;
160 ROUND:
161 while (1) {
162         say "After $round rounds:";
163         print_map;
164         $round++;
165         @units = reading_sort(grep { $_->[3] } @units);
166
167         for my $unit (@units) {
168                 next if !$unit->[3];
169                 %total_hp = ();
170                 for my $u (@units) {
171                         $total_hp{$u->[2]} += $u->[3]
172                                 if $u->[3];
173                 }
174                 if (keys %total_hp < 2) {
175                         $round--;
176                         last ROUND;
177                 }
178                 unit_round($unit);
179         }
180
181 }
182
183 say "After $round rounds:";
184 print_map;
185
186 say "ended after round $round with hp ", join('=>', %total_hp);
187 say $round * (%total_hp)[1];