summary refs log tree commit diff stats
path: root/day22.fsx
diff options
context:
space:
mode:
Diffstat (limited to 'day22.fsx')
-rw-r--r--day22.fsx72
1 files changed, 72 insertions, 0 deletions
diff --git a/day22.fsx b/day22.fsx
new file mode 100644
index 0000000..7944d25
--- /dev/null
+++ b/day22.fsx
@@ -0,0 +1,72 @@
+type Spell = 
+    { Name : string
+      Cost : int
+      Damage : int
+      Heal : int
+      Armor : int
+      Mana : int
+      Duration : int }
+
+let allSpells = 
+    [ { Name = "Magic Missile"; Cost = 53;  Damage = 4; Heal = 0; Armor = 0; Mana = 0;   Duration = 1}
+      { Name = "Drain";         Cost = 73;  Damage = 2; Heal = 2; Armor = 0; Mana = 0;   Duration = 1}
+      { Name = "Shield";        Cost = 113; Damage = 0; Heal = 0; Armor = 7; Mana = 0;   Duration = 6}
+      { Name = "Poison";        Cost = 173; Damage = 3; Heal = 0; Armor = 0; Mana = 0;   Duration = 6}
+      { Name = "Recharge";      Cost = 229; Damage = 0; Heal = 0; Armor = 0; Mana = 101; Duration = 5} ]
+
+let cheapestWin part (hp, mana) (bossHp, bossDamage) =
+    let rec play part myTurn best spent (hp, mana, spells) (bossHp, bossDamage) : int =
+        // if we have already spent more than the known cheapest win, bail
+        if spent >= best then best else
+
+        // part 2, check if I die before any effects play out
+        if part = 2 && myTurn && hp = 1 then best else
+
+        // apply effects
+        let mana  = (spells |> List.sumBy (fun s -> s.Mana)) + mana
+        let damage = spells |> List.sumBy (fun s -> s.Damage)
+        let armor  = spells |> List.sumBy (fun s -> s.Armor)
+
+        // does the boss die from effects?
+        let bossHp = bossHp - damage
+        if bossHp <= 0 then spent else
+
+        // decrement duration of all effects, and groom expired ones
+        let spells =
+            spells
+            |> List.map (fun s -> {s with Duration = s.Duration - 1})
+            |> List.filter (fun s -> s.Duration > 0)
+
+        if myTurn then
+            // part 2, I lose 1 HP on my turn
+            let hp = if part = 2 then hp - 1 else hp
+
+            // what spells can I afford and don't already have running?
+            match allSpells |> List.filter (fun s -> (s.Cost <= mana) && not (spells |> List.exists (fun s' -> s.Name = s'.Name))) with
+            | [] -> best
+            | buyableSpells ->
+                // play out the rest of the game with each possible purchase
+                let mutable newBest = best
+                for s in buyableSpells do
+                    let extraDamage, heal, spells = 
+                        if s.Duration = 1 then s.Damage, s.Heal, spells
+                        else 0, 0, s :: spells
+                    let spent = spent + s.Cost
+                    let mana = mana - s.Cost
+                    let hp = hp + heal
+                
+                    let bossHp = bossHp - extraDamage
+                    if bossHp <= 0 then newBest <- min newBest spent
+                    else newBest <- min newBest (play part false newBest spent (hp, mana, spells) (bossHp, bossDamage))
+                newBest
+        // boss's turn
+        else
+            let damage = max (bossDamage - armor) 1
+            let hp = hp - damage
+            if hp <= 0 then best else
+            play part true best spent (hp, mana, spells) (bossHp, bossDamage)
+
+    play part true 999999 0 (hp, mana, []) (bossHp, bossDamage)
+
+printfn "Part 1 - min to win: %d" (cheapestWin 1 (50, 500) (55, 8))
+printfn "Part 2 - min to win: %d" (cheapestWin 2 (50, 500) (55, 8))