-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathDay22.cs
177 lines (151 loc) · 5.82 KB
/
Day22.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
using AdventOfCode.CSharp.Common;
using System;
namespace AdventOfCode.CSharp.Y2015.Solvers;
public class Day22 : ISolver
{
public record GameState(int Mana, int PlayerHP, int BossHP, int Shield, int Poison, int Recharge);
public static void Solve(ReadOnlySpan<byte> input, Solution solution)
{
ParseInput(input, out int bossHp, out int bossDamage);
int part1 = Solve(bossHp, bossDamage, false);
int part2 = Solve(bossHp, bossDamage, true);
solution.SubmitPart1(part1);
solution.SubmitPart2(part2);
}
private static int Solve(int bossHp, int bossDamage, bool hasExtraDamage)
{
const int startingMana = 500;
const int startingHp = 50;
var pq = new PrioritySet<GameState, int>();
var firstState = new GameState(startingMana, startingHp, bossHp, 0, 0, 0);
pq.Enqueue(firstState, 0);
while (pq.TryDequeue(out GameState state, out int usedMana))
{
// check if boss is dead
if (state.BossHP == 0)
{
return usedMana;
}
// If it is part 2, apply damage to the player
if (hasExtraDamage)
{
int newHp = state.PlayerHP - 1;
if (newHp == 0)
{
continue;
}
state = state with { PlayerHP = newHp };
}
// Apply all active effects
if (state.Shield > 0)
{
state = state with { Shield = state.Shield - 1 };
}
if (state.Poison > 0)
{
state = state with { BossHP = Math.Max(0, state.BossHP - 3), Poison = state.Poison - 1 };
if (state.BossHP == 0)
{
return usedMana;
}
}
if (state.Recharge > 0)
{
state = state with { Mana = state.Mana + 101, Recharge = state.Recharge - 1 };
}
// Try use magic missile
if (state.Mana >= 53)
{
GameState stateAfterPlayerTurn = state with { BossHP = Math.Max(state.BossHP - 4, 0), Mana = state.Mana - 53 };
if (SimulateBossTurn(stateAfterPlayerTurn, bossDamage, out GameState stateAfterBossTurn))
{
pq.EnqueueOrUpdate(stateAfterBossTurn, usedMana + 53);
}
}
// Try use drain
if (state.Mana >= 73)
{
GameState stateAfterPlayerTurn = state with
{
BossHP = Math.Max(state.BossHP - 2, 0),
PlayerHP = state.PlayerHP + 2,
Mana = state.Mana - 73
};
if (SimulateBossTurn(stateAfterPlayerTurn, bossDamage, out GameState stateAfterBossTurn))
{
pq.EnqueueOrUpdate(stateAfterBossTurn, usedMana + 73);
}
}
// Try use shield
if (state.Mana >= 113 & state.Shield == 0)
{
GameState stateAfterPlayerTurn = state with { Shield = 6, Mana = state.Mana - 113 };
if (SimulateBossTurn(stateAfterPlayerTurn, bossDamage, out GameState stateAfterBossTurn))
{
pq.EnqueueOrUpdate(stateAfterBossTurn, usedMana + 113);
}
}
// Try use poison
if (state.Mana >= 173 & state.Poison == 0)
{
GameState stateAfterPlayerTurn = state with { Poison = 6, Mana = state.Mana - 173 };
if (SimulateBossTurn(stateAfterPlayerTurn, bossDamage, out GameState stateAfterBossTurn))
{
pq.EnqueueOrUpdate(stateAfterBossTurn, usedMana + 173);
}
}
// Try use recharge
if (state.Mana >= 229 & state.Recharge == 0)
{
GameState stateAfterPlayerTurn = state with { Recharge = 5, Mana = state.Mana - 229 };
if (SimulateBossTurn(stateAfterPlayerTurn, bossDamage, out GameState stateAfterBossTurn))
{
pq.EnqueueOrUpdate(stateAfterBossTurn, usedMana + 229);
}
}
}
ThrowHelper.ThrowException("Unable to beat the boss");
return default;
}
private static void ParseInput(ReadOnlySpan<byte> input, out int bossHp, out int bossDamage)
{
var reader = new SpanReader(input);
reader.SkipLength("Hit Points: ".Length);
bossHp = reader.ReadPosIntUntil('\n');
reader.SkipLength("Damage: ".Length);
bossDamage = reader.ReadPosIntUntil('\n');
}
private static bool SimulateBossTurn(GameState state, int bossDamage, out GameState newState)
{
// if the boss is already dead, don't try simulate.
if (state.BossHP == 0)
{
newState = state;
return true;
}
// apply effects
bool hasArmor = state.Shield > 0;
if (hasArmor)
{
state = state with { Shield = state.Shield - 1 };
}
if (state.Poison > 0)
{
state = state with { BossHP = Math.Max(0, state.BossHP - 3), Poison = state.Poison - 1 };
// if the boss has died, end simulating the bosses turn.
if (state.BossHP == 0)
{
newState = state;
return true;
}
}
if (state.Recharge > 0)
{
state = state with { Mana = state.Mana + 101, Recharge = state.Recharge - 1 };
}
// make attack
int damage = Math.Max(1, bossDamage - (hasArmor ? 7 : 0));
newState = state with { PlayerHP = Math.Max(0, state.PlayerHP - damage) };
return newState.PlayerHP > 0;
}
}