Skip to content

Instantly share code, notes, and snippets.

@latkin
Created December 22, 2015 16:59
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save latkin/45d8e009f471b4b5d609 to your computer and use it in GitHub Desktop.
Save latkin/45d8e009f471b4b5d609 to your computer and use it in GitHub Desktop.
Advent of Code Day 22 updated solution
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) (71, 10))
printfn "Part 2 - min to win: %d" (cheapestWin 2 (50, 500) (71, 10))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment