5 use experimental 'multidimensional';
9 my (@map, $xmax, $ymax, @units);
12 @map = map { chomp; [ split // ] } @input;
13 $xmax = $#{ $map[0] };
17 for my $y (0 .. $ymax) {
18 for my $x (0 .. $xmax) {
19 if ($map[$y][$x] eq 'G') {
20 push @units, [$x, $y, 'G', 200];
21 } elsif ($map[$y][$x] eq 'E') {
22 push @units, [$x, $y, 'E', 200];
30 sub enemy_of($unit) { $unit->[2] eq 'G' ? 'E' : 'G' }
32 my @neighbors = ([0, -1], [-1, 0], [1, 0], [0, 1]);
35 return sort { $a->[1] == $b->[1] ? $a->[0] <=> $b->[0] : $a->[1] <=> $b->[1] } @_;
39 for my $y (0 .. $ymax) {
40 for my $x (0 .. $xmax) {
45 for my $unit (grep { $_->[3] } reading_sort(@units)) {
46 say "# $unit->[2] at ($unit->[1]+$unit->[0]j) $unit->[3]"
50 sub neigh_squares($unit, $type) {
52 for my $n (@neighbors) {
56 if ($map[$dy][$dx] eq $type) {
57 push @rv, [ $dx, $dy ];
63 sub enemy_within_range($unit) {
64 return neigh_squares($unit, enemy_of($unit));
68 for my $unit (@units) {
70 if $unit->[0] == $loc->[0] && $unit->[1] == $loc->[1];
72 die "Can't locate unit at ", join(',', @$loc);
75 sub attack($unit, $enemies, $power) {
76 say "Attacking unit: ", join(',', @$unit);
79 for my $enemy (@$enemies) {
80 say "Enemy: ", join(',', @$enemy);
81 my $enemy_unit = loc2unit($enemy);
82 if (!defined $min_hp || $min_hp > $enemy_unit->[3]) {
83 $min_hp = $enemy_unit->[3];
86 if ($min_hp == $enemy_unit->[3]) {
87 push @min_hp, $enemy_unit;
91 @min_hp = reading_sort(@min_hp);
92 my $enemy = shift @min_hp;
94 $enemy->[3] -= $power;
95 if ($enemy->[3] <= 0) {
96 die if $enemy->[2] eq 'E';
98 $map[$enemy->[1]][$enemy->[0]] = '.';
100 $enemy->[3] = 0 if $enemy->[3] < 0;
101 say "attacked ", join(',', @$enemy);
104 sub dump_path($path) {
105 join(' ', map { "[$_->[0],$_->[1]]" } @$path);
110 for my $enemy (grep { $_->[2] ne $unit->[2] && $_->[3] > 0 } @units) {
111 for my $sq (neigh_squares($enemy, '.')) {
112 if (!$target_sq{$sq->[0],$sq->[1]}++) {
113 # say "target square $sq->[0],$sq->[1]";
118 my @q = [ [ $unit->[0], $unit->[1] ] ];
124 # say "walking path ", dump_path($path);
125 last if $found_at_dist && @$path > $found_at_dist;
126 for my $sq (neigh_squares($path->[-1], '.')) {
127 next if $visited{$sq->[0],$sq->[1]}++;
129 if ($target_sq{$sq->[0],$sq->[1]}) {
130 $found_at_dist //= @$path;
131 my $path1 = [ @$path, [ @$sq ] ];
132 # say "Found square $sq->[0],$sq->[1] through ",
134 push @reachable, [ @$sq, @{ $path1->[1] } ];
136 push @q, [ @$path, [ @$sq ] ];
142 # say "no reachable squares";
146 @reachable = reading_sort(@reachable);
147 my $target = $reachable[0];
148 say "moving to $target->[0],$target->[1] via $target->[2],$target->[3]";
149 $map[$unit->[1]][$unit->[0]] = '.';
150 $map[$unit->[1] = $target->[3]][$unit->[0] = $target->[2]] = $unit->[2];
153 sub unit_round($unit, $attack) {
154 return if !$unit->[3]; # are we alive?
155 # say "\nunit_round ", join(',', @$unit);
157 my @enemies = enemy_within_range($unit);
160 @enemies = enemy_within_range($unit);
163 attack($unit, \@enemies, $attack);
167 sub battle($elf_hp) {
170 say "elf_hp: $elf_hp =====================================";
174 say "After $round rounds:";
177 @units = reading_sort(grep { $_->[3] } @units);
179 for my $unit (@units) {
183 $total_hp{$u->[2]} += $u->[3]
186 if (keys %total_hp < 2) {
190 eval { unit_round($unit, $unit->[2] eq 'E' ? $elf_hp : 3); };
195 say "After $round rounds with hp $elf_hp:";
198 say "elf_hp $elf_hp ended after round $round with hp ", join('=>', %total_hp);
199 say $round * (%total_hp)[1];
204 $elf_hp++ while !battle($elf_hp);