--- /dev/null
+#!/usr/bin/perl -w
+
+use v5.36;
+use strict;
+
+my @armies_txt;
+{ local $/ = "\n\n"; chomp (@armies_txt = <>); }
+
+my @units = map { parse_army($_) } @armies_txt;
+use Data::Dumper;
+print Dumper \@units;
+
+sub parse_army($text) {
+ my ($name, @rest) = split /\n/, $text;
+ $name =~ s/:\z//;
+ my $i = 0;
+ return ( map { parse_group($name, ++$i, $_) }
+ grep { length $_ } @rest );
+}
+
+sub parse_group($army, $id, $text) {
+ my %rv = ( army => $army, name => "$army group $id", text => $text );
+ ($rv{units}) = $text =~ /\A(\d+) unit/;
+ ($rv{hp}) = $text =~ / (\d+) hit point/;
+ ($rv{damage}, $rv{damage_type})
+ = $text =~ /that does (\d+) (\w+) damage /;
+ ($rv{init}) = $text =~ /at initiative (\d+)\z/;
+ my ($l) = $text =~ /weak to ([^;\)]+)[\);]/;
+
+ $rv{weak} = { map { $_ => 1 } split(/, /, $l) }
+ if length $l;
+ ($l) = $text =~ /immune to ([^;\)]+)[\);]/;
+ $rv{immune} = { map { $_ => 1 } split(/, /, $l) }
+ if length $l;
+ $rv{orig_units} = $rv{units};
+ $rv{orig_damage} = $rv{damage};
+ return \%rv;
+}
+
+sub e_pwr($unit) { return $unit->{units} * $unit->{damage} };
+
+sub damage_to($attacker, $defender) {
+ return 0 if $defender->{immune}->{$attacker->{damage_type}};
+ my $power = e_pwr($attacker);
+ $power *= 2 if $defender->{weak}->{$attacker->{damage_type}};
+ return $power;
+}
+
+sub selection {
+ say "";
+ my @rv;
+ my %attacked;
+ for my $unit (sort {
+ $b->{units}*$b->{damage} <=> $a->{units}*$a->{damage}
+ || $b->{init} <=> $a->{init}
+ } @units) {
+ next if !$unit->{units};
+ for my $def (sort {
+ damage_to($unit, $b) <=> damage_to($unit, $a)
+ || e_pwr($b) <=> e_pwr($a)
+ || $b->{init} <=> $a->{init}
+ }
+ grep {
+ $_->{army} ne $unit->{army}
+ && $_->{units}
+ && !$attacked{$_->{name}}
+ } @units) {
+ my $d = damage_to($unit, $def);
+ next if !$d;
+ say $unit->{name}, ' => ', $def->{name}, ': ', $d;
+ $attacked{$def->{name}} = $unit->{name};
+ push @rv, [ $unit, $def ];
+ last;
+ }
+ }
+ return \@rv;
+}
+
+sub attack($items) {
+ say "";
+ my $total_killed = 0;
+ for my $item (sort { $b->[0]->{init} <=> $a->[0]->{init} } @$items) {
+ my ($att, $def) = @$item;
+ next if !$att->{units};
+ my $killed = int(damage_to($att, $def) / $def->{hp});
+ $killed = $def->{units} if $killed > $def->{units};
+ $total_killed += $killed;
+
+ say "$att->{name} attacks $def->{name} killing $killed units";
+ $def->{units} -= $killed;
+ $def->{units} = 0 if $def->{units} < 0;
+ }
+ return $total_killed;
+}
+
+sub battle($boost) {
+ my $round = 0;
+
+ for my $unit (@units) {
+ $unit->{units} = $unit->{orig_units};
+ $unit->{damage} = $unit->{orig_damage};
+ $unit->{damage} += $boost
+ if $unit->{army} eq 'Immune System';
+ }
+
+ say " ################## boost $boost ##################### ";
+ while (1) {
+ say "round ", $round++;
+ my %alive;
+ my $sum;
+ for my $unit (@units) {
+ next if !$unit->{units};
+ say "$unit->{name} contains $unit->{units} units";
+ $sum += $unit->{units};
+ $alive{$unit->{army}}++;
+ }
+ if (keys(%alive) < 2) {
+ say "sum=$sum";
+ return $alive{"Immune System"} ? 1 : 0;
+ }
+ return 0 if !attack(selection());
+ }
+}
+
+my $boost = 0;
+1 while !battle($boost++);
+
+__END__
+Immune System:
+889 units each with 3275 hit points (weak to bludgeoning, radiation; immune to cold) with an attack that does 36 bludgeoning damage at initiative 12
+94 units each with 1336 hit points (weak to radiation, cold) with an attack that does 127 bludgeoning damage at initiative 7
+1990 units each with 5438 hit points with an attack that does 25 slashing damage at initiative 20
+
+Infection group 4 attacks Immune System group 9 killing 0 units
+6523