Part 1
Solution
This puzzle is a game with many choices on every turn, so I did it in two steps. First I built a simulator in Excel where I type in the spells and see what happens. Then I used VBA to try every possible sequence.
The layout
- The boss’s hit points and damage are in B4:B5, my hit points are in D4 and my mana is in A6.
- The spell table is in L1:S6 (cost, damage, heal, poison, shield, recharge, turns).
- From row 11 down there is one row per turn. Column A says whose turn it is, “P” for me and “E” for the boss, with one formula:
A11: =LET(a, SEQUENCE(100), IF(MOD(a,2)=1,"P","E"))
- On my rows I type the spell number (1 to 5) in column L.
- Row 10 labels the columns B (before) and A (after). The columns are boss hit points (B, C), boss damage (D), my hit points (E, F), my mana (G, H), armor (J) and mana spent (K).
- Rows 11, D10 and the starting cells B11, E11 and G11 are linked to the input:
=B4, =D4, =A6 and =B5.
Effects without timers
I didn’t track timers. Instead each row asks “was this spell cast in the last few rows?” with COUNTIF over column L. Poison and shield last 6 turns, so they look back 6 rows. Recharge lasts 5, so it looks back 5:
B12: =MAX(0,C11-(COUNTIF(L6:L11,4)*$P$5)) ← poison damage
J12: =COUNTIF(L6:L11,3)*$Q$4 ← shield armor
G12: =H11+COUNTIF(L7:L11,5)*$R$6 ← recharge mana
The spell I cast this turn is handled in the same row:
C11: =MAX(0,B11-(COUNTIF(L11,1)*$N$2+COUNTIF(L11,2)*$N$3)) ← boss hit points after my spell
F11: =MAX(0,E11-IF(A11="E",(D11-J11),0))+COUNTIF(L11,2)*$O$3 ← boss hits me on "E" rows, Drain heals me
H11: =MAX(0,IF(A11="P",G11-IF(L11>0,INDEX($M$2:$M$6,L11),0),G11)) ← mana after the spell
K11: =IF(A11="P",IF(L11>0,INDEX($M$2:$M$6,L11),0),"") ← mana spent
Reading the result
C8 is the first row where the boss has 0 hit points, and F8 is the first row where I do. A10 is the earlier of the two. If the boss dies first, K8 adds up the mana spent up to that row:
C8: =IFNA(XMATCH(0,C11:C54,0),999)
F8: =IFNA(XMATCH(0,F11:F54,0),999)
K8: =IF(C8<=F8,SUM(TAKE(K11:K54,A10)))
The <= matters. If poison kills the boss at the start of his turn, the sheet still lets him hit me in that row, so both hit 0 on the same row.
The simulator doesn’t check that a sequence is legal. It won’t stop me from casting a spell I can’t afford, or one that’s still running. So I used it to look at a sequence, not to find one.
Trying every sequence
For the search I wrote a macro. It plays the game turn by turn and tries all five spells each turn. It skips spells I can’t afford or that are still active, and stops following any path whose mana already reaches the best total found. When it finishes, it writes the best sequence into column L, so K8 confirms the total:
Option Explicit
Private best As Long, bestLen As Long, bossDmg As Long, hardMode As Boolean
Private bestPath(1 To 200) As Long, curPath(1 To 200) As Long
Private spellCost As Variant
Public Sub Day22Part1()
Solve False
End Sub
Public Sub Day22Part2()
Solve True
End Sub
Private Sub Solve(ByVal hard As Boolean)
Dim ws As Worksheet, i As Long
Set ws = ActiveSheet
spellCost = ws.Range("M2:M6").Value
bossDmg = ws.Range("B5").Value
hardMode = hard
best = &H7FFFFFFF
bestLen = 0
Search ws.Range("D4").Value, ws.Range("A6").Value, ws.Range("B4").Value, 0, 0, 0, 0, 0
ws.Range("L11:L110").ClearContents
For i = 1 To bestLen
ws.Cells(9 + 2 * i, 12).Value = bestPath(i) ' L11, L13, L15, ...
Next i
MsgBox "Least mana: " & best
End Sub
Private Sub Record(ByVal total As Long, ByVal n As Long)
Dim i As Long
best = total: bestLen = n
For i = 1 To n: bestPath(i) = curPath(i): Next i
End Sub
Private Sub Search(ByVal php As Long, ByVal mana As Long, ByVal bhp As Long, _
ByVal sh As Long, ByVal po As Long, ByVal re As Long, _
ByVal spent As Long, ByVal depth As Long)
Dim sp As Long, c As Long, s As Long, m As Long, b As Long, ph As Long
Dim sh2 As Long, po2 As Long, re2 As Long, armor As Long, dmg As Long
If spent >= best Then Exit Sub
' my turn: hard mode, then effects
If hardMode Then
php = php - 1
If php <= 0 Then Exit Sub
End If
If sh > 0 Then sh = sh - 1
If po > 0 Then
bhp = bhp - 3: po = po - 1
End If
If re > 0 Then
mana = mana + 101: re = re - 1
End If
If bhp <= 0 Then
Record spent, depth
Exit Sub
End If
For sp = 1 To 5
c = spellCost(sp, 1)
If c <= mana Then
If Not ((sp = 3 And sh > 0) Or (sp = 4 And po > 0) Or (sp = 5 And re > 0)) Then
s = spent + c
If s < best Then
curPath(depth + 1) = sp
m = mana - c: b = bhp: ph = php
sh2 = sh: po2 = po: re2 = re
Select Case sp
Case 1: b = b - 4
Case 2: b = b - 2: ph = ph + 2
Case 3: sh2 = 6
Case 4: po2 = 6
Case 5: re2 = 5
End Select
If b <= 0 Then
Record s, depth + 1
Else
' boss turn: effects, then he attacks
armor = 0
If sh2 > 0 Then
armor = 7: sh2 = sh2 - 1
End If
If po2 > 0 Then
b = b - 3: po2 = po2 - 1
End If
If re2 > 0 Then
m = m + 101: re2 = re2 - 1
End If
If b <= 0 Then
Record s, depth + 1
Else
dmg = bossDmg - armor
If dmg < 1 Then dmg = 1
ph = ph - dmg
If ph > 0 Then Search ph, m, b, sh2, po2, re2, s, depth + 1
End If
End If
End If
End If
End If
Next sp
End Sub
On the puzzle’s two examples the same logic gives 226 and 641. Part 1 can take a little while to run, because it looks at about 700,000 positions.
Part 2
Solution
On hard difficulty I lose 1 hit point at the start of each of my turns. The macro just turns that on: Day22Part2 runs the same search with hardMode = True. It gives a higher total, since some of the cheapest wins no longer survive.
To let the simulator show this too, I put a 1 in D2 as a switch and added one term to the “after” hit points formula in column F:
F11: =MAX(0,E11-IF(A11="E",(D11-J11),0)-IF(AND(A11="P",$D$2=1),1,0))+COUNTIF(L11,2)*$O$3
Then I typed the sequence from the macro into column L and K8 showed the same total.