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

Day 21: RPG Simulator 20XX

elixir/day_21_rpg_simulator_20xx.livemd

Day 21: RPG Simulator 20XX

Section

defmodule RPGShop do
  @weapons [
    %{name: "Dagger", cost: 8, damage: 4, armor: 0},
    %{name: "Shortsword", cost: 10, damage: 5, armor: 0},
    %{name: "Warhammer", cost: 25, damage: 6, armor: 0},
    %{name: "Longsword", cost: 40, damage: 7, armor: 0},
    %{name: "Greataxe", cost: 74, damage: 8, armor: 0}
  ]

  @armor [
    %{name: "None", cost: 0, damage: 0, armor: 0},
    %{name: "Leather", cost: 13, damage: 0, armor: 1},
    %{name: "Chainmail", cost: 31, damage: 0, armor: 2},
    %{name: "Splintmail", cost: 53, damage: 0, armor: 3},
    %{name: "Bandedmail", cost: 75, damage: 0, armor: 4},
    %{name: "Platemail", cost: 102, damage: 0, armor: 5}
  ]

  @rings [
    %{name: "None", cost: 0, damage: 0, armor: 0},
    %{name: "Damage +1", cost: 25, damage: 1, armor: 0},
    %{name: "Damage +2", cost: 50, damage: 2, armor: 0},
    %{name: "Damage +3", cost: 100, damage: 3, armor: 0},
    %{name: "Defense +1", cost: 20, damage: 0, armor: 1},
    %{name: "Defense +2", cost: 40, damage: 0, armor: 2},
    %{name: "Defense +3", cost: 80, damage: 0, armor: 3}
  ]

  def simulate_combat(player, boss) do
    player_damage = max(player.damage - boss.armor, 1)
    boss_damage = max(boss.damage - player.armor, 1)
    player_turns = div(boss.hp + player_damage - 1, player_damage)
    boss_turns = div(player.hp + boss_damage - 1, boss_damage)
    player_turns <= boss_turns
  end

  def item_combinations do
    for weapon <- @weapons,
        armor <- @armor,
        ring1 <- @rings,
        ring2 <- @rings,
        ring1 != ring2 do
      [weapon, armor, ring1, ring2]
    end
  end

  def calculate_cost_and_stats(items) do
    Enum.reduce(items, %{cost: 0, damage: 0, armor: 0}, fn item, acc ->
      %{
        cost: acc.cost + item.cost,
        damage: acc.damage + item.damage,
        armor: acc.armor + item.armor
      }
    end)
  end

  def find_least_cost_winning_combination(player_hp, boss_hp, boss_damage, boss_armor) do
    boss = %{hp: boss_hp, damage: boss_damage, armor: boss_armor}

    item_combinations()
    |> Enum.map(&amp;calculate_cost_and_stats/1)
    |> Enum.filter(fn stats ->
      player = %{hp: player_hp, damage: stats.damage, armor: stats.armor}
      simulate_combat(player, boss)
    end)
    |> Enum.min_by(&amp; &amp;1.cost)
  end

  def find_most_expensive_losing_combination(player_hp, boss_hp, boss_damage, boss_armor) do
    boss = %{hp: boss_hp, damage: boss_damage, armor: boss_armor}

    item_combinations()
    |> Enum.map(&amp;calculate_cost_and_stats/1)
    |> Enum.filter(fn stats ->
      player = %{hp: player_hp, damage: stats.damage, armor: stats.armor}
      not simulate_combat(player, boss)
    end)
    |> Enum.max_by(&amp; &amp;1.cost, fn -> %{cost: 0, damage: 0, armor: 0} end)
  end
end

player_hp = 100
boss_hp = 109
boss_damage = 8
boss_armor = 2

RPGShop.find_least_cost_winning_combination(player_hp, boss_hp, boss_damage, boss_armor)
%{cost: 111, damage: 7, armor: 4}
RPGShop.find_most_expensive_losing_combination(player_hp, boss_hp, boss_damage, boss_armor)
%{cost: 188, damage: 7, armor: 3}