Skip to content

Commit e1ee84d

Browse files
committed
feat: Day 24: Immune System Simulator 20XX (part 2)
1 parent 64cd8d8 commit e1ee84d

File tree

5 files changed

+394
-3
lines changed

5 files changed

+394
-3
lines changed

.github/badges/completion.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"schemaVersion": 1,
33
"label": "completion",
4-
"message": "37/50",
4+
"message": "41/50",
55
"color": "yellow"
66
}

day-24-immune-system-simulator-20xx/README.md

+166
Original file line numberDiff line numberDiff line change
@@ -187,5 +187,171 @@ In the example above, the winning army ends up with `782 + 4434 = 5216 units`.
187187

188188
You scan the reindeer's condition (your puzzle input); the white-bearded man looks nervous. As it stands now, **how many units would the winning army have**?
189189

190+
## Part Two
191+
192+
Things aren't looking good for the reindeer. The man asks whether more milk and cookies would help you think.
193+
194+
If only you could give the reindeer's immune system a boost, you might be able to change the outcome of the combat.
195+
196+
A **boost** is an integer increase in immune system units' attack damage. For example, if you were to boost the above example's immune system's units by `1570`, the armies would instead look like this:
197+
198+
```
199+
Immune System:
200+
17 units each with 5390 hit points (weak to radiation, bludgeoning) with
201+
an attack that does 6077 fire damage at initiative 2
202+
989 units each with 1274 hit points (immune to fire; weak to bludgeoning,
203+
slashing) with an attack that does 1595 slashing damage at initiative 3
204+
205+
Infection:
206+
801 units each with 4706 hit points (weak to radiation) with an attack
207+
that does 116 bludgeoning damage at initiative 1
208+
4485 units each with 2961 hit points (immune to radiation; weak to fire,
209+
cold) with an attack that does 12 slashing damage at initiative 4
210+
With this boost, the combat proceeds differently:
211+
```
212+
213+
With this boost, the combat proceeds differently:
214+
215+
```
216+
Immune System:
217+
Group 2 contains 989 units
218+
Group 1 contains 17 units
219+
Infection:
220+
Group 1 contains 801 units
221+
Group 2 contains 4485 units
222+
223+
Infection group 1 would deal defending group 2 185832 damage
224+
Infection group 1 would deal defending group 1 185832 damage
225+
Infection group 2 would deal defending group 1 53820 damage
226+
Immune System group 2 would deal defending group 1 1577455 damage
227+
Immune System group 2 would deal defending group 2 1577455 damage
228+
Immune System group 1 would deal defending group 2 206618 damage
229+
230+
Infection group 2 attacks defending group 1, killing 9 units
231+
Immune System group 2 attacks defending group 1, killing 335 units
232+
Immune System group 1 attacks defending group 2, killing 32 units
233+
Infection group 1 attacks defending group 2, killing 84 units
234+
```
235+
236+
```
237+
Immune System:
238+
Group 2 contains 905 units
239+
Group 1 contains 8 units
240+
Infection:
241+
Group 1 contains 466 units
242+
Group 2 contains 4453 units
243+
244+
Infection group 1 would deal defending group 2 108112 damage
245+
Infection group 1 would deal defending group 1 108112 damage
246+
Infection group 2 would deal defending group 1 53436 damage
247+
Immune System group 2 would deal defending group 1 1443475 damage
248+
Immune System group 2 would deal defending group 2 1443475 damage
249+
Immune System group 1 would deal defending group 2 97232 damage
250+
251+
Infection group 2 attacks defending group 1, killing 8 units
252+
Immune System group 2 attacks defending group 1, killing 306 units
253+
Infection group 1 attacks defending group 2, killing 29 units
254+
```
255+
256+
```
257+
Immune System:
258+
Group 2 contains 876 units
259+
Infection:
260+
Group 2 contains 4453 units
261+
Group 1 contains 160 units
262+
263+
Infection group 2 would deal defending group 2 106872 damage
264+
Immune System group 2 would deal defending group 2 1397220 damage
265+
Immune System group 2 would deal defending group 1 1397220 damage
266+
267+
Infection group 2 attacks defending group 2, killing 83 units
268+
Immune System group 2 attacks defending group 2, killing 427 units
269+
```
270+
271+
After a few fights...
272+
273+
```
274+
Immune System:
275+
Group 2 contains 64 units
276+
Infection:
277+
Group 2 contains 214 units
278+
Group 1 contains 19 units
279+
280+
Infection group 2 would deal defending group 2 5136 damage
281+
Immune System group 2 would deal defending group 2 102080 damage
282+
Immune System group 2 would deal defending group 1 102080 damage
283+
284+
Infection group 2 attacks defending group 2, killing 4 units
285+
Immune System group 2 attacks defending group 2, killing 32 units
286+
```
287+
288+
```
289+
Immune System:
290+
Group 2 contains 60 units
291+
Infection:
292+
Group 1 contains 19 units
293+
Group 2 contains 182 units
294+
295+
Infection group 1 would deal defending group 2 4408 damage
296+
Immune System group 2 would deal defending group 1 95700 damage
297+
Immune System group 2 would deal defending group 2 95700 damage
298+
299+
Immune System group 2 attacks defending group 1, killing 19 units
300+
```
301+
302+
```
303+
Immune System:
304+
Group 2 contains 60 units
305+
Infection:
306+
Group 2 contains 182 units
307+
308+
Infection group 2 would deal defending group 2 4368 damage
309+
Immune System group 2 would deal defending group 2 95700 damage
310+
311+
Infection group 2 attacks defending group 2, killing 3 units
312+
Immune System group 2 attacks defending group 2, killing 30 units
313+
```
314+
315+
After a few more fights...
316+
317+
```
318+
Immune System:
319+
Group 2 contains 51 units
320+
Infection:
321+
Group 2 contains 40 units
322+
323+
Infection group 2 would deal defending group 2 960 damage
324+
Immune System group 2 would deal defending group 2 81345 damage
325+
326+
Infection group 2 attacks defending group 2, killing 0 units
327+
Immune System group 2 attacks defending group 2, killing 27 units
328+
```
329+
330+
```
331+
Immune System:
332+
Group 2 contains 51 units
333+
Infection:
334+
Group 2 contains 13 units
335+
336+
Infection group 2 would deal defending group 2 312 damage
337+
Immune System group 2 would deal defending group 2 81345 damage
338+
339+
Infection group 2 attacks defending group 2, killing 0 units
340+
Immune System group 2 attacks defending group 2, killing 13 units
341+
```
342+
343+
```
344+
Immune System:
345+
Group 2 contains 51 units
346+
Infection:
347+
No groups remain.
348+
```
349+
350+
This boost would allow the immune system's armies to win! It would be left with **`51`** units.
351+
352+
You don't even know **how** you could boost the reindeer's immune system or what effect it might have, so you need to be cautious and find the **smallest boost** that would allow the immune system to win.
353+
354+
**How many units does the immune system have left** after getting the smallest boost it needs to win?
355+
190356
## References
191357
- https://adventofcode.com/2018/day/24

day-24-immune-system-simulator-20xx/immune-system.js

-2
Original file line numberDiff line numberDiff line change
@@ -175,5 +175,3 @@ module.exports = (input) => {
175175
.find((army) => !army.isDead).groups
176176
.reduce((units, group) => units + group.units, 0);
177177
};
178-
179-
// 18341 too high
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
const PRINT_COMBAT_TEXT = false;
2+
3+
function print () {
4+
if (PRINT_COMBAT_TEXT) {
5+
const args = Array.from(arguments);
6+
7+
console.log(...args);
8+
}
9+
}
10+
11+
const parseArmyInformation = (input) => {
12+
return input
13+
.split('\n\n')
14+
.map((army, armyIndex) => {
15+
const armyName = armyIndex === 0 ? 'Immune System' : 'Infection';
16+
const groups = army
17+
.split('\n')
18+
.slice(1)
19+
.map((line, groupIndex) => {
20+
const parts = /(\d+) units each with (\d+) hit points .*with an attack that does (\d+) (\w+) damage at initiative (\d+)/.exec(line.trim());
21+
const modifiers = /\((.*)\)/.exec(line.trim());
22+
23+
const tags = modifiers
24+
? modifiers[1].split(';').reduce((map, definition) => {
25+
/weak to/.test(definition)
26+
? map.set('weaknesses', new Set(/weak to (.*)/.exec(definition)[1].split(', ')))
27+
: map.set('immunities', new Set(/immune to (.*)/.exec(definition)[1].split(', ')));
28+
29+
return map;
30+
}, new Map())
31+
: new Map();
32+
33+
return {
34+
armyName,
35+
number: groupIndex + 1,
36+
units: +parts[1],
37+
hitPoints: +parts[2],
38+
weaknesses: tags.get('weaknesses') || new Set(),
39+
immunities: tags.get('immunities') || new Set(),
40+
attackDamage: +parts[3],
41+
attackType: parts[4],
42+
initiative: +parts[5],
43+
get effectivePower() {
44+
return this.units * this.attackDamage;
45+
},
46+
get isDead() {
47+
return this.units <= 0;
48+
},
49+
};
50+
});
51+
52+
return {
53+
name: armyName,
54+
groups,
55+
get isDead() {
56+
return this.groups.every((group) => group.isDead);
57+
},
58+
};
59+
});
60+
};
61+
62+
const printArmyStatus = (army) => {
63+
print(`${army.name}:`);
64+
army.groups.every((group) => group.isDead)
65+
? print('No groups remain.')
66+
: army.groups.forEach((group) => !group.isDead && print(`Group ${group.number} contains ${group.units} units`));
67+
};
68+
69+
const attackerSelectionSort = (a, b) => {
70+
return b.effectivePower - a.effectivePower === 0
71+
? b.initiative - a.initiative
72+
: b.effectivePower - a.effectivePower;
73+
};
74+
75+
const effectiveDamage = (attacker, defender) => {
76+
const { effectivePower, attackType } = attacker;
77+
78+
return (defender.weaknesses.has(attackType) ? 2 : (defender.immunities.has(attackType) ? 0 : 1)) * effectivePower;
79+
};
80+
81+
const targetSelectionSort = (a, b) => {
82+
return b.damage - a.damage === 0
83+
? b.group.effectivePower - a.group.effectivePower === 0
84+
? b.group.initiative - a.group.initiative
85+
: b.group.effectivePower - a.group.effectivePower
86+
: b.damage - a.damage;
87+
};
88+
89+
const targetSelectionPhase = (attackers, defenders) => {
90+
const sortedAttackers = [...attackers.groups].filter((group) => !group.isDead).sort(attackerSelectionSort);
91+
const availableDefenders = [...defenders.groups].filter((group) => !group.isDead);
92+
const plannedAttacks = [];
93+
94+
sortedAttackers.forEach((attackingGroup) => {
95+
const effectiveDamageOnDefenders = availableDefenders.map((defendingGroup) => {
96+
return {
97+
damage: effectiveDamage(attackingGroup, defendingGroup),
98+
group: defendingGroup,
99+
};
100+
});
101+
102+
// print target selection phase
103+
effectiveDamageOnDefenders.forEach(({ damage, group }) => {
104+
print(`${attackers.name} group ${attackingGroup.number} would deal defending group ${group.number} ${damage} damage`);
105+
});
106+
107+
if (!effectiveDamageOnDefenders.length) {
108+
return;
109+
}
110+
111+
const selectedTarget = effectiveDamageOnDefenders.sort(targetSelectionSort)[0];
112+
113+
if (selectedTarget.damage === 0) {
114+
return;
115+
}
116+
117+
plannedAttacks.push({
118+
attackingGroup,
119+
defendingGroup: selectedTarget.group,
120+
});
121+
122+
const selectedDefenderIndex = availableDefenders.findIndex(({ number }) => selectedTarget.group.number === number);
123+
124+
availableDefenders.splice(selectedDefenderIndex, 1);
125+
});
126+
127+
return plannedAttacks;
128+
};
129+
130+
const attackingPhaseSort = (a, b) => {
131+
return b.attackingGroup.initiative - a.attackingGroup.initiative;
132+
};
133+
134+
const attackingPhase = (plannedAttacks) => {
135+
const sortedAttackingOrder = plannedAttacks.sort(attackingPhaseSort);
136+
137+
for (let i = 0; i < sortedAttackingOrder.length; i++) {
138+
const { attackingGroup, defendingGroup } = sortedAttackingOrder[i];
139+
const damage = effectiveDamage(attackingGroup, defendingGroup);
140+
const killedUnits = Math.min(Math.floor(damage / defendingGroup.hitPoints), defendingGroup.units);
141+
142+
print(`${attackingGroup.armyName} group ${attackingGroup.number} attacks defending group ${defendingGroup.number}, killing ${killedUnits} units`);
143+
144+
defendingGroup.units -= killedUnits;
145+
}
146+
};
147+
148+
const simulate = (armies) => {
149+
while (!armies[0].isDead && !armies[1].isDead) {
150+
printArmyStatus(armies[0]);
151+
printArmyStatus(armies[1]);
152+
153+
print('');
154+
155+
// target selection phase
156+
const plannedAttacks = [
157+
...targetSelectionPhase(armies[1], armies[0]),
158+
...targetSelectionPhase(armies[0], armies[1]),
159+
];
160+
161+
// halt if opposing groups are immune to each other
162+
if (!plannedAttacks.length) {
163+
return [false, 0];
164+
}
165+
166+
print('');
167+
168+
// attacking phase
169+
attackingPhase(plannedAttacks);
170+
171+
print('');
172+
}
173+
174+
printArmyStatus(armies[0]);
175+
printArmyStatus(armies[1]);
176+
177+
return [
178+
!armies[0].isDead,
179+
armies[0].groups.reduce((units, group) => units + group.units, 0),
180+
];
181+
};
182+
183+
const boost = (army, amount) => {
184+
army.groups.forEach((group) => group.attackDamage += amount);
185+
};
186+
187+
module.exports = (input) => {
188+
let appliedBoost = 1;
189+
let minimumBoost = appliedBoost;
190+
let maximumBoost = Infinity;
191+
192+
while (appliedBoost <= maximumBoost) {
193+
const armies = parseArmyInformation(input);
194+
195+
boost(armies[0], appliedBoost);
196+
197+
const [success, immuneSystemUnitsLeft] = simulate(armies);
198+
199+
if (!success) {
200+
minimumBoost = appliedBoost;
201+
appliedBoost = maximumBoost === Infinity
202+
? appliedBoost *= 2
203+
: Math.floor((maximumBoost - minimumBoost) / 2 + 1) + minimumBoost;
204+
} else if (appliedBoost < maximumBoost) {
205+
maximumBoost = appliedBoost;
206+
appliedBoost = Math.floor((maximumBoost - minimumBoost) / 2 + 1) + minimumBoost;
207+
} else {
208+
return immuneSystemUnitsLeft;
209+
}
210+
}
211+
};

0 commit comments

Comments
 (0)