7
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Advent of code 2015 Day 21 Part 1 & Part 2 を Livebook で楽しむ

Last updated at Posted at 2024-12-05

はじめに

Advent of code 2024 の準備として、過去回の Advent of code 2015 を Livebook で楽しみます

本記事では Day 21 の Part 1 と Part 2 を解きます

問題文はこちら

実装したノートブックはこちら

セットアップ

Kino AOC をインストールします

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

Kino AOC の使い方はこちらを参照

入力の取得

"Advent of Code Helper" スマートセルを追加し、 Day 21 の入力を取得します

スクリーンショット 2024-12-04 19.58.08.png

私の答え

私の答えです。
折りたたんでおきます。
▶を押して開いてください。

Part 1

回答

まずはお店のメニューを読み込みます

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}

実行結果

{[
   %{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}
 ],
 [
   %{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}
 ],
 [
   %{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}
 ]}

決着が着くまで再帰的に戦うモジュールを用意します

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
}

Game.battle(player, boss)

実行結果

:win

プレイヤーが勝利しました

全ての装備の組み合わせを作ります

まず、配列から n 個の要素を取り出す組み合わせを全て作るモジュールを用意します

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

武器は必ず 1 個、鎧は 0 か 1 個、指輪は 0 から 2 個の範囲で装備できます

各個数についてそれぞれ全ての組み合わせを作り、さらに武器と鎧と指輪の組み合わせを全て組み合わせます

このとき、コスト、攻撃力、守備力の合計を装備の値として計算しておきます

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

実行結果

[
  %{cost: 8, damage: 4, armor: 0},
  %{cost: 33, damage: 5, armor: 0},
  %{cost: 58, damage: 6, armor: 0},
  ...
]

ボスのパラメーターを入力から読み込みます

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)

実行結果

%{damage: 9, armor: 2, hp: 103}

装備の組み合わせをコストの低い順に並べ、ボスに勝てる組み合わせを探索します

equipment_combinations
|> Enum.sort_by(&(&1.cost))
|> Enum.find(fn equipments ->
  Game.battle(Map.put(equipments, :hp, 100), boss) == :win
end)

Part 2

回答

Part 1 の逆です

装備をコストの高い順に並べ、ボスに負ける組み合わせを探索します

equipment_combinations
|> Enum.sort_by(&(&1.cost), :desc)
|> Enum.find(fn equipments ->
  Game.battle(Map.put(equipments, :hp, 100), boss) == :lose
end)

まとめ

問題文から ChatGPT に画像を生成してもらいました

aoc2015_day21.png

RPG を作っているようでワクワクする問題ですね

7
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?