Skip to content

Commit 64cd8d8

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

File tree

5 files changed

+390
-1
lines changed

5 files changed

+390
-1
lines changed

Diff for: .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": "36/50",
4+
"message": "37/50",
55
"color": "yellow"
66
}

Diff for: README.md

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ Collect stars by solving puzzles. Two puzzles will be made available on each day
3434
- [Day 18: Settlers of The North Pole](day-18-settlers-of-the-north-pole/)
3535
- [Day 22: Mode Maze](day-22-mode-maze/)
3636
- [Day 23: Experimental Emergency Teleportation](day-23-experimental-emergency-teleportation/)
37+
- [Day 24: Immune System Simulator 20XX](day-24-immune-system-simulator-20xx/)
3738
- [Day 25: Four-Dimensional Adventure](day-25-four-dimensional-adventure/)
3839

3940
## Running Tests

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

+191
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
## Day 24: Immune System Simulator 20XX
2+
3+
After [a weird buzzing noise](https://www.youtube.com/watch?v=NDVjLt_QHL8&t=7), you appear back at the man's cottage. He seems relieved to see his friend, but quickly notices that the little reindeer caught some kind of cold while out exploring.
4+
5+
The portly man explains that this reindeer's immune system isn't similar to regular reindeer immune systems:
6+
7+
The **immune system** and the **infection** each have an army made up of several **groups**; each **group** consists of one or more identical **units**. The armies repeatedly **fight** until only one army has units remaining.
8+
9+
**Units** within a group all have the same **hit points** (amount of damage a unit can take before it is destroyed), **attack damage** (the amount of damage each unit deals), an **attack type**, an **initiative** (higher initiative units attack first and win ties), and sometimes **weaknesses** or **immunities**. Here is an example group:
10+
11+
```
12+
18 units each with 729 hit points (weak to fire; immune to cold, slashing)
13+
with an attack that does 8 radiation damage at initiative 10
14+
```
15+
16+
Each group also has an **effective power**: the number of units in that group multiplied by their attack damage. The above group has an effective power of `18 * 8 = 144`. Groups never have zero or negative units; instead, the group is removed from combat.
17+
18+
Each **fight** consists of two phases: **target selection** and **attacking**.
19+
20+
During the **target selection** phase, each group attempts to choose one target. In decreasing order of effective power, groups choose their targets; in a tie, the group with the higher initiative chooses first. The attacking group chooses to target the group in the enemy army to which it would deal the most damage (after accounting for weaknesses and immunities, but not accounting for whether the defending group has enough units to actually receive all of that damage).
21+
22+
If an attacking group is considering two defending groups to which it would deal equal damage, it chooses to target the defending group with the largest effective power; if there is still a tie, it chooses the defending group with the highest initiative. If it cannot deal any defending groups damage, it does not choose a target. Defending groups can only be chosen as a target by one attacking group.
23+
24+
At the end of the target selection phase, each group has selected zero or one groups to attack, and each group is being attacked by zero or one groups.
25+
26+
During the **attacking** phase, each group deals damage to the target it selected, if any. Groups attack in decreasing order of initiative, regardless of whether they are part of the infection or the immune system. (If a group contains no units, it cannot attack.)
27+
28+
The damage an attacking group deals to a defending group depends on the attacking group's attack type and the defending group's immunities and weaknesses. By default, an attacking group would deal damage equal to its **effective power** to the defending group. However, if the defending group is **immune** to the attacking group's attack type, the defending group instead takes **no damage**; if the defending group is **weak** to the attacking group's attack type, the defending group instead takes **double damage**.
29+
30+
The defending group only loses **whole units** from damage; damage is always dealt in such a way that it kills the most units possible, and any remaining damage to a unit that does not immediately kill it is ignored. For example, if a defending group contains **10** units with **10** hit points each and receives **75** damage, it loses exactly **7** units and is left with **3** units at full health.
31+
32+
After the fight is over, if both armies still contain units, a new fight begins; combat only ends once one army has lost all of its units.
33+
34+
For example, consider the following armies:
35+
36+
```
37+
Immune System:
38+
17 units each with 5390 hit points (weak to radiation, bludgeoning) with
39+
an attack that does 4507 fire damage at initiative 2
40+
989 units each with 1274 hit points (immune to fire; weak to bludgeoning,
41+
slashing) with an attack that does 25 slashing damage at initiative 3
42+
43+
Infection:
44+
801 units each with 4706 hit points (weak to radiation) with an attack
45+
that does 116 bludgeoning damage at initiative 1
46+
4485 units each with 2961 hit points (immune to radiation; weak to fire,
47+
cold) with an attack that does 12 slashing damage at initiative 4
48+
```
49+
50+
If these armies were to enter combat, the following fights, including details during the target selection and attacking phases, would take place:
51+
52+
```
53+
Immune System:
54+
Group 1 contains 17 units
55+
Group 2 contains 989 units
56+
Infection:
57+
Group 1 contains 801 units
58+
Group 2 contains 4485 units
59+
60+
Infection group 1 would deal defending group 1 185832 damage
61+
Infection group 1 would deal defending group 2 185832 damage
62+
Infection group 2 would deal defending group 2 107640 damage
63+
Immune System group 1 would deal defending group 1 76619 damage
64+
Immune System group 1 would deal defending group 2 153238 damage
65+
Immune System group 2 would deal defending group 1 24725 damage
66+
67+
Infection group 2 attacks defending group 2, killing 84 units
68+
Immune System group 2 attacks defending group 1, killing 4 units
69+
Immune System group 1 attacks defending group 2, killing 51 units
70+
Infection group 1 attacks defending group 1, killing 17 units
71+
```
72+
73+
```
74+
Immune System:
75+
Group 2 contains 905 units
76+
Infection:
77+
Group 1 contains 797 units
78+
Group 2 contains 4434 units
79+
80+
Infection group 1 would deal defending group 2 184904 damage
81+
Immune System group 2 would deal defending group 1 22625 damage
82+
Immune System group 2 would deal defending group 2 22625 damage
83+
84+
Immune System group 2 attacks defending group 1, killing 4 units
85+
Infection group 1 attacks defending group 2, killing 144 units
86+
```
87+
88+
```
89+
Immune System:
90+
Group 2 contains 761 units
91+
Infection:
92+
Group 1 contains 793 units
93+
Group 2 contains 4434 units
94+
95+
Infection group 1 would deal defending group 2 183976 damage
96+
Immune System group 2 would deal defending group 1 19025 damage
97+
Immune System group 2 would deal defending group 2 19025 damage
98+
99+
Immune System group 2 attacks defending group 1, killing 4 units
100+
Infection group 1 attacks defending group 2, killing 143 units
101+
```
102+
103+
```
104+
Immune System:
105+
Group 2 contains 618 units
106+
Infection:
107+
Group 1 contains 789 units
108+
Group 2 contains 4434 units
109+
110+
Infection group 1 would deal defending group 2 183048 damage
111+
Immune System group 2 would deal defending group 1 15450 damage
112+
Immune System group 2 would deal defending group 2 15450 damage
113+
114+
Immune System group 2 attacks defending group 1, killing 3 units
115+
Infection group 1 attacks defending group 2, killing 143 units
116+
```
117+
118+
```
119+
Immune System:
120+
Group 2 contains 475 units
121+
Infection:
122+
Group 1 contains 786 units
123+
Group 2 contains 4434 units
124+
125+
Infection group 1 would deal defending group 2 182352 damage
126+
Immune System group 2 would deal defending group 1 11875 damage
127+
Immune System group 2 would deal defending group 2 11875 damage
128+
129+
Immune System group 2 attacks defending group 1, killing 2 units
130+
Infection group 1 attacks defending group 2, killing 142 units
131+
```
132+
133+
```
134+
Immune System:
135+
Group 2 contains 333 units
136+
Infection:
137+
Group 1 contains 784 units
138+
Group 2 contains 4434 units
139+
140+
Infection group 1 would deal defending group 2 181888 damage
141+
Immune System group 2 would deal defending group 1 8325 damage
142+
Immune System group 2 would deal defending group 2 8325 damage
143+
144+
Immune System group 2 attacks defending group 1, killing 1 unit
145+
Infection group 1 attacks defending group 2, killing 142 units
146+
```
147+
148+
```
149+
Immune System:
150+
Group 2 contains 191 units
151+
Infection:
152+
Group 1 contains 783 units
153+
Group 2 contains 4434 units
154+
155+
Infection group 1 would deal defending group 2 181656 damage
156+
Immune System group 2 would deal defending group 1 4775 damage
157+
Immune System group 2 would deal defending group 2 4775 damage
158+
159+
Immune System group 2 attacks defending group 1, killing 1 unit
160+
Infection group 1 attacks defending group 2, killing 142 units
161+
```
162+
163+
```
164+
Immune System:
165+
Group 2 contains 49 units
166+
Infection:
167+
Group 1 contains 782 units
168+
Group 2 contains 4434 units
169+
170+
Infection group 1 would deal defending group 2 181424 damage
171+
Immune System group 2 would deal defending group 1 1225 damage
172+
Immune System group 2 would deal defending group 2 1225 damage
173+
174+
Immune System group 2 attacks defending group 1, killing 0 units
175+
Infection group 1 attacks defending group 2, killing 49 units
176+
```
177+
178+
```
179+
Immune System:
180+
No groups remain.
181+
Infection:
182+
Group 1 contains 782 units
183+
Group 2 contains 4434 units
184+
```
185+
186+
In the example above, the winning army ends up with `782 + 4434 = 5216 units`.
187+
188+
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**?
189+
190+
## References
191+
- https://adventofcode.com/2018/day/24

Diff for: day-24-immune-system-simulator-20xx/immune-system.js

+179
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
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+
module.exports = (input) => {
149+
const armies = parseArmyInformation(input);
150+
151+
while (!armies[0].isDead && !armies[1].isDead) {
152+
printArmyStatus(armies[0]);
153+
printArmyStatus(armies[1]);
154+
155+
print('');
156+
157+
// target selection phase
158+
const plannedAttacks = [
159+
...targetSelectionPhase(armies[1], armies[0]),
160+
...targetSelectionPhase(armies[0], armies[1]),
161+
];
162+
163+
print('');
164+
165+
// attacking phase
166+
attackingPhase(plannedAttacks);
167+
168+
print('');
169+
}
170+
171+
printArmyStatus(armies[0]);
172+
printArmyStatus(armies[1]);
173+
174+
return armies
175+
.find((army) => !army.isDead).groups
176+
.reduce((units, group) => units + group.units, 0);
177+
};
178+
179+
// 18341 too high

0 commit comments

Comments
 (0)