Skip to content

Commit f38dbe6

Browse files
committed
Solve day 19 2015 part 2: Medicine for Rudolph
1 parent 6583632 commit f38dbe6

File tree

3 files changed

+275
-26
lines changed

3 files changed

+275
-26
lines changed

src/main/java/com/sbaars/adventofcode/network/DownloadPuzzles.java

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,6 @@ public DownloadPuzzles() {
1717
public static void main(String[] args) {
1818
DownloadPuzzles dlPuzzles = new DownloadPuzzles();
1919
dlPuzzles.runForYear("2015");
20-
dlPuzzles.runForYear("2016");
21-
dlPuzzles.runForYear("2017");
22-
dlPuzzles.runForYear("2018");
23-
dlPuzzles.runForYear("2019");
24-
dlPuzzles.runForYear("2020");
25-
dlPuzzles.runForYear("2021");
26-
dlPuzzles.runForYear("2022");
27-
dlPuzzles.runForYear("2023");
2820
}
2921

3022
public void retrieveTests(String day, String year) {

src/main/java/com/sbaars/adventofcode/year15/days/Day19.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public static void main(String[] args) {
2020
Day19 day = new Day19();
2121
day.printParts();
2222
new com.sbaars.adventofcode.network.Submit().submit(day.part1(), 2015, 19, 1);
23+
new com.sbaars.adventofcode.network.Submit().submit(day.part2(), 2015, 19, 2);
2324
}
2425

2526
private void parseInput() {
@@ -65,6 +66,28 @@ public Object part1() {
6566

6667
@Override
6768
public Object part2() {
68-
return 0; // Implement in next part
69+
// Count total elements (uppercase followed by optional lowercase)
70+
int elements = molecule.replaceAll("[^A-Z]", "").length();
71+
72+
// Count special elements
73+
int rn = countOccurrences(molecule, "Rn");
74+
int ar = countOccurrences(molecule, "Ar");
75+
int y = countOccurrences(molecule, "Y");
76+
77+
// Formula: elements - rn - ar - 2*y - 1
78+
// Each Rn...Ar pair reduces steps by 2
79+
// Each Y reduces steps by 2 (as it's always between Rn and Ar)
80+
// Subtract 1 for the initial 'e'
81+
return elements - rn - ar - 2 * y - 1;
82+
}
83+
84+
private int countOccurrences(String str, String substr) {
85+
int count = 0;
86+
int index = 0;
87+
while ((index = str.indexOf(substr, index)) >= 0) {
88+
count++;
89+
index += substr.length();
90+
}
91+
return count;
6992
}
7093
}
Lines changed: 251 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,257 @@
11
package com.sbaars.adventofcode.year15.days;
22

33
import com.sbaars.adventofcode.year15.Day2015;
4+
import java.util.*;
5+
import java.util.stream.Collectors;
46

57
public class Day22 extends Day2015 {
6-
public Day22() {
7-
super(22);
8-
}
9-
10-
public static void main(String[] args) {
11-
new Day22().printParts();
12-
}
13-
14-
@Override
15-
public Object part1() {
16-
return "";
17-
}
18-
19-
@Override
20-
public Object part2() {
21-
return "";
22-
}
8+
private static final int PLAYER_HP = 50;
9+
private static final int PLAYER_MANA = 500;
10+
11+
private enum Spell {
12+
MAGIC_MISSILE(53, 0, 4, 0, 0, 0),
13+
DRAIN(73, 0, 2, 2, 0, 0),
14+
SHIELD(113, 6, 0, 0, 7, 0),
15+
POISON(173, 6, 0, 0, 0, 3),
16+
RECHARGE(229, 5, 0, 0, 0, 0);
17+
18+
final int cost;
19+
final int duration;
20+
final int damage;
21+
final int heal;
22+
final int armor;
23+
final int manaRegen;
24+
25+
Spell(int cost, int duration, int damage, int heal, int armor, int manaRegen) {
26+
this.cost = cost;
27+
this.duration = duration;
28+
this.damage = damage;
29+
this.heal = heal;
30+
this.armor = armor;
31+
this.manaRegen = manaRegen;
32+
}
33+
}
34+
35+
private record GameState(int playerHp, int playerMana, int bossHp, Map<Spell, Integer> activeEffects) {
36+
public GameState applyEffects() {
37+
int newPlayerHp = playerHp;
38+
int newPlayerMana = playerMana;
39+
int newBossHp = bossHp;
40+
41+
Map<Spell, Integer> newEffects = new HashMap<>();
42+
for (var entry : activeEffects.entrySet()) {
43+
Spell spell = entry.getKey();
44+
int duration = entry.getValue();
45+
46+
if (duration > 0) {
47+
if (spell == Spell.POISON) {
48+
newBossHp -= 3;
49+
} else if (spell == Spell.RECHARGE) {
50+
newPlayerMana += 101;
51+
}
52+
53+
if (duration > 1) {
54+
newEffects.put(spell, duration - 1);
55+
}
56+
}
57+
}
58+
59+
return new GameState(newPlayerHp, newPlayerMana, newBossHp, newEffects);
60+
}
61+
62+
public boolean canCastSpell(Spell spell) {
63+
return playerMana >= spell.cost && !activeEffects.containsKey(spell);
64+
}
65+
66+
public GameState castSpell(Spell spell) {
67+
Map<Spell, Integer> newEffects = new HashMap<>(activeEffects);
68+
int newPlayerHp = playerHp;
69+
int newPlayerMana = playerMana - spell.cost;
70+
int newBossHp = bossHp;
71+
72+
if (spell.duration > 0) {
73+
newEffects.put(spell, spell.duration);
74+
} else {
75+
if (spell == Spell.MAGIC_MISSILE) {
76+
newBossHp -= 4;
77+
} else if (spell == Spell.DRAIN) {
78+
newBossHp -= 2;
79+
newPlayerHp += 2;
80+
}
81+
}
82+
83+
return new GameState(newPlayerHp, newPlayerMana, newBossHp, newEffects);
84+
}
85+
86+
public int getArmor() {
87+
return activeEffects.containsKey(Spell.SHIELD) && activeEffects.get(Spell.SHIELD) > 0 ? 7 : 0;
88+
}
89+
90+
@Override
91+
public boolean equals(Object o) {
92+
if (this == o) return true;
93+
if (o == null || getClass() != o.getClass()) return false;
94+
GameState that = (GameState) o;
95+
return playerHp == that.playerHp &&
96+
playerMana == that.playerMana &&
97+
bossHp == that.bossHp &&
98+
activeEffects.equals(that.activeEffects);
99+
}
100+
101+
@Override
102+
public int hashCode() {
103+
return Objects.hash(playerHp, playerMana, bossHp, activeEffects);
104+
}
105+
}
106+
107+
public Day22() {
108+
super(22);
109+
}
110+
111+
public static void main(String[] args) {
112+
Day22 day = new Day22();
113+
day.printParts();
114+
new com.sbaars.adventofcode.network.Submit().submit(day.part2(), 2015, 22, 2);
115+
}
116+
117+
@Override
118+
public Object part1() {
119+
int[] bossStats = parseBoss();
120+
return findMinManaToWin(bossStats[0], bossStats[1]);
121+
}
122+
123+
@Override
124+
public Object part2() {
125+
int[] bossStats = parseBoss();
126+
return findMinManaToWinHardMode(bossStats[0], bossStats[1]);
127+
}
128+
129+
private int[] parseBoss() {
130+
List<String> lines = dayStream().collect(Collectors.toList());
131+
int hp = Integer.parseInt(lines.get(0).split(": ")[1]);
132+
int damage = Integer.parseInt(lines.get(1).split(": ")[1]);
133+
return new int[]{hp, damage};
134+
}
135+
136+
private int findMinManaToWin(int bossHp, int bossDamage) {
137+
PriorityQueue<Map.Entry<GameState, Integer>> queue = new PriorityQueue<>(Map.Entry.comparingByValue());
138+
Set<GameState> visited = new HashSet<>();
139+
140+
GameState initialState = new GameState(PLAYER_HP, PLAYER_MANA, bossHp, new HashMap<>());
141+
queue.offer(Map.entry(initialState, 0));
142+
143+
while (!queue.isEmpty()) {
144+
var entry = queue.poll();
145+
GameState state = entry.getKey();
146+
int totalMana = entry.getValue();
147+
148+
if (visited.contains(state)) {
149+
continue;
150+
}
151+
visited.add(state);
152+
153+
// Player's turn - Apply effects first
154+
GameState afterEffects = state.applyEffects();
155+
if (afterEffects.bossHp <= 0) {
156+
return totalMana;
157+
}
158+
159+
// Try each spell
160+
for (Spell spell : Spell.values()) {
161+
if (!afterEffects.canCastSpell(spell)) {
162+
continue;
163+
}
164+
165+
GameState afterSpell = afterEffects.castSpell(spell);
166+
if (afterSpell.bossHp <= 0) {
167+
return totalMana + spell.cost;
168+
}
169+
170+
// Boss's turn - Apply effects first
171+
GameState afterBossEffects = afterSpell.applyEffects();
172+
if (afterBossEffects.bossHp <= 0) {
173+
return totalMana + spell.cost;
174+
}
175+
176+
// Boss attacks
177+
int damage = Math.max(1, bossDamage - afterBossEffects.getArmor());
178+
GameState afterBossTurn = new GameState(
179+
afterBossEffects.playerHp - damage,
180+
afterBossEffects.playerMana,
181+
afterBossEffects.bossHp,
182+
afterBossEffects.activeEffects
183+
);
184+
185+
if (afterBossTurn.playerHp > 0) {
186+
queue.offer(Map.entry(afterBossTurn, totalMana + spell.cost));
187+
}
188+
}
189+
}
190+
191+
return -1; // No solution found
192+
}
193+
194+
private int findMinManaToWinHardMode(int bossHp, int bossDamage) {
195+
PriorityQueue<Map.Entry<GameState, Integer>> queue = new PriorityQueue<>(Map.Entry.comparingByValue());
196+
Set<GameState> visited = new HashSet<>();
197+
198+
GameState initialState = new GameState(PLAYER_HP, PLAYER_MANA, bossHp, new HashMap<>());
199+
queue.offer(Map.entry(initialState, 0));
200+
201+
while (!queue.isEmpty()) {
202+
var entry = queue.poll();
203+
GameState state = entry.getKey();
204+
int totalMana = entry.getValue();
205+
206+
if (visited.contains(state)) {
207+
continue;
208+
}
209+
visited.add(state);
210+
211+
// Hard mode: Player loses 1 HP at start of turn
212+
GameState afterHardMode = new GameState(state.playerHp() - 1, state.playerMana(), state.bossHp(), state.activeEffects());
213+
if (afterHardMode.playerHp() <= 0) {
214+
continue;
215+
}
216+
217+
// Player's turn - Apply effects first
218+
GameState afterEffects = afterHardMode.applyEffects();
219+
if (afterEffects.bossHp() <= 0) {
220+
return totalMana;
221+
}
222+
223+
// Try each spell
224+
for (Spell spell : Spell.values()) {
225+
if (!afterEffects.canCastSpell(spell)) {
226+
continue;
227+
}
228+
229+
GameState afterSpell = afterEffects.castSpell(spell);
230+
if (afterSpell.bossHp() <= 0) {
231+
return totalMana + spell.cost;
232+
}
233+
234+
// Boss's turn - Apply effects first
235+
GameState afterBossEffects = afterSpell.applyEffects();
236+
if (afterBossEffects.bossHp() <= 0) {
237+
return totalMana + spell.cost;
238+
}
239+
240+
// Boss attacks
241+
int damage = Math.max(1, bossDamage - afterBossEffects.getArmor());
242+
GameState afterBossTurn = new GameState(
243+
afterBossEffects.playerHp() - damage,
244+
afterBossEffects.playerMana(),
245+
afterBossEffects.bossHp(),
246+
afterBossEffects.activeEffects()
247+
);
248+
249+
if (afterBossTurn.playerHp() > 0) {
250+
queue.offer(Map.entry(afterBossTurn, totalMana + spell.cost));
251+
}
252+
}
253+
}
254+
255+
return -1; // No solution found
256+
}
23257
}

0 commit comments

Comments
 (0)