From 132bd5dcf33697dae9063ff51d369495f0aad142 Mon Sep 17 00:00:00 2001 From: sloane Date: Wed, 27 Nov 2024 14:18:00 -0500 Subject: [PATCH] solve 2015 day 21 --- 2015/README.md | 3 +- 2015/lib/2015/21.ex | 192 ++++++++++++++++++++++++++++++++++++++++++++ README.md | 2 +- 3 files changed, 195 insertions(+), 2 deletions(-) create mode 100644 2015/lib/2015/21.ex diff --git a/2015/README.md b/2015/README.md index 1bbb4bf..a0ee495 100644 --- a/2015/README.md +++ b/2015/README.md @@ -17,7 +17,7 @@ | | | [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 | | +| [20] | [21] | 22 | 23 | 24 | 25 | | [1]: ./lib/2015/1.ex [2]: ./lib/2015/2.ex @@ -39,3 +39,4 @@ [18]: ./lib/2015/18.ex [19]: ./lib/2015/19.ex [20]: ./lib/2015/20.ex +[21]: ./lib/2015/21.ex diff --git a/2015/lib/2015/21.ex b/2015/lib/2015/21.ex new file mode 100644 index 0000000..5af6938 --- /dev/null +++ b/2015/lib/2015/21.ex @@ -0,0 +1,192 @@ +import AOC + +aoc 2015, 21 do + defmodule Weapon do + defstruct [:damage, :cost] + def damage(%Weapon{damage: damage}), do: damage + def damage(nil), do: 0 + def cost(%Weapon{cost: cost}), do: cost + def cost(nil), do: 0 + end + + defmodule Armor do + defstruct [:defence, :cost] + def defence(%Armor{defence: defence}), do: defence + def defence(nil), do: 0 + def cost(%Armor{cost: cost}), do: cost + def cost(nil), do: 0 + end + + defmodule Ring do + defstruct damage: 0, defence: 0, cost: 0 + def defence(%Ring{defence: defence}), do: defence || 0 + def defence(nil), do: 0 + def damage(%Ring{damage: damage}), do: damage || 0 + def damage(nil), do: 0 + def cost(%Ring{cost: cost}), do: cost + def cost(nil), do: 0 + end + + defmodule Character do + defstruct [:hp, :weapon, :armor, rings: []] + + def cost(%Character{} = char) do + Weapon.cost(char.weapon) + Armor.cost(char.armor) + + (Enum.map(char.rings, &Ring.cost/1) |> Enum.sum()) + end + + def damage(%Character{rings: rings} = char) do + Weapon.damage(char.weapon) + (Enum.map(rings, &Ring.damage/1) |> Enum.sum()) + end + + def defence(%Character{rings: rings} = char) do + Armor.defence(char.armor) + (Enum.map(rings, &Ring.defence/1) |> Enum.sum()) + end + + def equip(%Character{weapon: nil} = char, %Weapon{} = weapon) do + %Character{char | weapon: weapon} + end + + def equip(%Character{armor: nil} = char, %Armor{} = armor) do + %Character{char | armor: armor} + end + + def equip(%Character{rings: []} = char, %Ring{} = ring) do + %Character{char | rings: [ring]} + end + + def equip(%Character{rings: [ring_1]} = char, %Ring{} = ring_2) do + %Character{char | rings: [ring_1, ring_2]} + end + + def equip(%Character{} = char, nil), do: char + + def attack(%Character{} = attacker, %Character{} = defender) do + damage = damage(attacker) + defence = defence(defender) + %Character{defender | hp: defender.hp - max(1, damage - defence)} + end + + def defeated?(%Character{hp: hp}), do: hp <= 0 + end + + def p1 do + boss = boss() + + for weapon <- weapons(), + armor <- [nil | armor()], + ring_1 <- [nil | rings()], + ring_2 <- [nil | rings() -- [ring_1]], + reduce: :infinity do + min_cost -> + player = + %Character{hp: 100} + |> Character.equip(weapon) + |> Character.equip(armor) + |> Character.equip(ring_1) + |> Character.equip(ring_2) + + curr_cost = Character.cost(player) + + if curr_cost < min_cost do + case battle(player, boss) do + {:win, _} -> curr_cost + {:lose, _} -> min_cost + end + else + min_cost + end + end + end + + def p2 do + boss = boss() + + for weapon <- weapons(), + armor <- [nil | armor()], + ring_1 <- [nil | rings()], + ring_2 <- [nil | rings() -- [ring_1]], + reduce: 0 do + max_cost -> + player = + %Character{hp: 100} + |> Character.equip(weapon) + |> Character.equip(armor) + |> Character.equip(ring_1) + |> Character.equip(ring_2) + + curr_cost = Character.cost(player) + + if max_cost < curr_cost do + case battle(player, boss) do + {:win, _} -> max_cost + {:lose, _} -> curr_cost + end + else + max_cost + end + end + end + + defp battle(player, boss) do + player_attack = fn {player, boss} -> + boss = Character.attack(player, boss) + + if Character.defeated?(boss) do + {:halt, {:win, player}} + else + {:cont, {player, boss}} + end + end + + boss_attack = fn {player, boss} -> + player = Character.attack(boss, player) + + if Character.defeated?(player) do + {:halt, {:lose, player}} + else + {:cont, {player, boss}} + end + end + + Stream.cycle([player_attack, boss_attack]) + |> Enum.reduce_while({player, boss}, fn turn, characters -> + turn.(characters) + end) + end + + defp boss do + %Character{hp: 103, weapon: %Weapon{damage: 9}, armor: %Armor{defence: 2}} + end + + defp weapons do + [ + %Weapon{cost: 8, damage: 4}, + %Weapon{cost: 10, damage: 5}, + %Weapon{cost: 25, damage: 6}, + %Weapon{cost: 40, damage: 7}, + %Weapon{cost: 74, damage: 8} + ] + end + + defp armor do + [ + %Armor{cost: 13, defence: 1}, + %Armor{cost: 31, defence: 2}, + %Armor{cost: 53, defence: 3}, + %Armor{cost: 75, defence: 4}, + %Armor{cost: 102, defence: 5} + ] + end + + defp rings do + [ + %Ring{cost: 25, damage: 1}, + %Ring{cost: 50, damage: 2}, + %Ring{cost: 100, damage: 3}, + %Ring{cost: 20, defence: 1}, + %Ring{cost: 40, defence: 2}, + %Ring{cost: 80, defence: 3} + ] + end +end diff --git a/README.md b/README.md index 8b652f5..cced555 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ | Year | Stars | Languages | | - | - | - | -| [2015] | **40/50** 🌟 | Elixir | +| [2015] | **42/50** 🌟 | Elixir | | 2016 | | | | [2017] | **18/50** 🌟 | Haskell | | 2018 | | |