Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Advent of Code 2015 Day 21 Part 2

2015_day21_part2.livemd

Advent of Code 2015 Day 21 Part 2

Mix.install([
  {:kino_aoc, "~> 0.1"}
])

Get Inputs

{:ok, puzzle_input} =
  KinoAOC.download_puzzle("2015", "21", System.fetch_env!("LB_SESSION"))

My answer

load_shop_menu = fn menu ->
  menu
  |> String.trim()
  |> String.split("\n")
  |> Enum.map(fn row ->
    row
    |> String.split("  ", trim: true)
    |> then(fn [name, cost, damage, armor] ->
      %{
        name: name,
        cost: cost |> String.trim() |> String.to_integer(),
        damage: damage |> String.trim() |> String.to_integer(),
        armor: armor |> String.trim() |> String.to_integer(),
      }
    end)
  end)
end
weapons =
  """
  Dagger        8     4       0
  Shortsword   10     5       0
  Warhammer    25     6       0
  Longsword    40     7       0
  Greataxe     74     8       0
  """
  |> load_shop_menu.()

armors =
  """
  Leather      13     0       1
  Chainmail    31     0       2
  Splintmail   53     0       3
  Bandedmail   75     0       4
  Platemail   102     0       5
  """
  |> load_shop_menu.()

rings =
  """
  Damage +1    25     1       0
  Damage +2    50     2       0
  Damage +3   100     3       0
  Defense +1   20     0       1
  Defense +2   40     0       2
  Defense +3   80     0       3
  """
  |> load_shop_menu.()

{weapons, armors, rings}
defmodule Game do
  def battle(player, boss) do
    boss_hp = boss.hp - max(1, player.damage - boss.armor)
    if boss_hp < 1 do
      :win
    else
      player_hp = player.hp - max(1, boss.damage - player.armor)
      if player_hp < 1 do
        :lose
      else
        battle(
          Map.put(player, :hp, player_hp),
          Map.put(boss, :hp, boss_hp)
        )
      end
    end
  end
end
player = %{
  hp: 8,
  damage: 5,
  armor: 5
}

boss = %{
  hp: 12,
  damage: 7,
  armor: 2
}

{player, boss}
Game.battle(player, boss)
defmodule Combinations do
  def all(_, 0), do: [[]]
  def all([], _), do: []
  def all(list, n) when length(list) == n, do: [list]

  def all([head | tail], n) do
    with_head = for combo <- all(tail, n - 1), do: [head | combo]
    without_head = all(tail, n)
    with_head ++ without_head
  end
end
weapon_combinations = Combinations.all(weapons, 1)
armor_combinations = Combinations.all(armors, 0) ++ Combinations.all(armors, 1)
ring_combinations =
  Combinations.all(rings, 0) ++ Combinations.all(rings, 1) ++ Combinations.all(rings, 2)

equipment_combinations =
  for weapon <- weapon_combinations,
      armor <- armor_combinations,
      ring <- ring_combinations do
    (weapon ++ armor ++ ring)
    |> Enum.reduce(%{cost: 0, damage: 0, armor: 0}, fn equipment, acc_equipments ->
      acc_equipments
      |> Map.put(:cost, acc_equipments.cost + equipment.cost)
      |> Map.put(:damage, acc_equipments.damage + equipment.damage)
      |> Map.put(:armor, acc_equipments.armor + equipment.armor)
    end)
  end
boss =
  puzzle_input
  |> String.split("\n")
  |> Enum.map(fn row ->
    row
    |> String.split(": ")
    |> Enum.at(1)
    |> String.to_integer()
  end)
  |> then(fn [hp, damage, armor] ->
      %{hp: hp, damage: damage, armor: armor}
  end)
equipment_combinations
|> Enum.sort_by(&amp;(&amp;1.cost), :desc)
|> Enum.find(fn equipments ->
  Game.battle(Map.put(equipments, :hp, 100), boss) == :lose
end)