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

Day 19

2022/day19.livemd

Day 19

Mix.install([{:kino, "~>0.8.0"}])

Setup

input = Kino.Input.textarea("input file")

Part 1

defmodule Day19 do
  def build_robots(state) do
    state
    |> Enum.map(fn {id, blueprint_strategy} ->
      materials = %{
        ore: blueprint_strategy[:ore],
        clay: blueprint_strategy[:clay],
        obsidian: blueprint_strategy[:obsidian],
        geode: blueprint_strategy[:geode]
      }

      costs = %{
        ore_robot: %{cost: blueprint_strategy[:ore_robot][:cost]},
        clay_robot: %{cost: blueprint_strategy[:clay_robot][:cost]},
        obsidian_robot: %{
          ore_cost: blueprint_strategy[:obsidian_robot][:ore_cost],
          clay_cost: blueprint_strategy[:obsidian_robot][:clay_cost]
        },
        geode_robot: %{
          ore_cost: blueprint_strategy[:geode_robot][:ore_cost],
          obsidian_cost: blueprint_strategy[:geode_robot][:obsidian_cost]
        }
      }

      {new_robots, remaining_materials} =
        {[], materials}
        |> build_geode_robots(
          {costs[:geode_robot][:ore_cost], costs[:geode_robot][:obsidian_cost]}
        )
        |> build_obsidian_robots(
          {costs[:obsidian_robot][:ore_cost], costs[:obsidian_robot][:clay_cost]}
        )
        |> build_clay_robots(costs[:clay_robot][:cost])
        |> build_ore_robots(costs[:ore_robot][:cost])

      {id, new_robots, remaining_materials}
    end)
  end

  def pick_resources(state) do
    state
    |> Enum.map(fn {id, blueprint_strategy} ->
      ore_robots = blueprint_strategy[:ore_robot][:amount]
      clay_robots = blueprint_strategy[:clay_robot][:amount]
      obsidian_robots = blueprint_strategy[:obsidian_robot][:amount]
      geode_robots = blueprint_strategy[:geode_robot][:amount]

      {id,
       %{
         ore: ore_robots,
         clay: clay_robots,
         obsidian: obsidian_robots || 0,
         geode: geode_robots || 0
       }}
    end)
  end

  def merge(state, new_robots, new_resources) do
    state
    |> Enum.map(fn {blueprint_id, blueprint} ->
      {_, robots_to_add_for_blueprint, remaining_materials_for_blueprint} =
        new_robots |> Enum.find(fn {id, _, _} -> id == blueprint_id end)

      new_ore_robots =
        robots_to_add_for_blueprint |> Enum.count(fn robot -> robot == {:ore_robot} end)

      new_clay_robots =
        robots_to_add_for_blueprint |> Enum.count(fn robot -> robot == {:clay_robot} end)

      new_obsidian_robots =
        robots_to_add_for_blueprint |> Enum.count(fn robot -> robot == {:obsidian_robot} end)

      new_geode_robots =
        robots_to_add_for_blueprint |> Enum.count(fn robot -> robot == {:geode_robot} end)

      {_, materials_to_add_for_blueprint} =
        new_resources |> Enum.find(fn {id, _} -> id == blueprint_id end)

      {
        blueprint_id,
        %{
          blueprint
          | clay:
              remaining_materials_for_blueprint[:clay] + materials_to_add_for_blueprint[:clay],
            ore: remaining_materials_for_blueprint[:ore] + materials_to_add_for_blueprint[:ore],
            obsidian:
              remaining_materials_for_blueprint[:obsidian] +
                materials_to_add_for_blueprint[:obsidian],
            geode:
              remaining_materials_for_blueprint[:geode] + materials_to_add_for_blueprint[:geode],
            clay_robot: %{
              blueprint[:clay_robot]
              | amount: blueprint[:clay_robot][:amount] + new_clay_robots
            },
            ore_robot: %{
              blueprint[:ore_robot]
              | amount: blueprint[:ore_robot][:amount] + new_ore_robots
            },
            obsidian_robot: %{
              blueprint[:obsidian_robot]
              | amount: blueprint[:obsidian_robot][:amount] + new_obsidian_robots
            },
            geode_robot: %{
              blueprint[:geode_robot]
              | amount: blueprint[:geode_robot][:amount] + new_geode_robots
            }
        }
      }
    end)
  end

  # Private methods
  defp build_geode_robots({new_robots, materials}, {ore_cost, obsidian_cost}) do
    if materials[:ore] >= ore_cost and materials[:obsidian] >= obsidian_cost do
      # reduce materials and add new robot
      {new_robots ++ [{:geode_robot}],
       %{
         materials
         | ore: materials[:ore] - ore_cost,
           obsidian: materials[:obsidian] - obsidian_cost
       }}
    else
      # return same materials and no new robot
      {new_robots, materials}
    end
  end

  defp build_obsidian_robots({new_robots, materials}, {ore_cost, clay_cost}) do
    if materials[:ore] >= ore_cost and materials[:clay] >= clay_cost do
      # reduce materials and add new robot
      {new_robots ++ [{:obsidian_robot}],
       %{materials | ore: materials[:ore] - ore_cost, clay: materials[:clay] - clay_cost}}
    else
      # return same materials and no new robot
      {new_robots, materials}
    end
  end

  defp build_clay_robots({new_robots, materials}, ore_cost) do
    if materials[:ore] >= ore_cost do
      {new_robots ++ [{:clay_robot}], %{materials | ore: materials[:ore] - ore_cost}}
    else
      # return same materials and no new robot
      {new_robots, materials}
    end
  end

  defp build_ore_robots({new_robots, materials}, ore_cost) do
    if materials[:ore] >= ore_cost do
      {new_robots ++ [{:ore_robot}], %{materials | ore: materials[:ore] - ore_cost}}
    else
      # return same materials and no new robot
      {new_robots, materials}
    end
  end
end

init_state =
  input
  |> Kino.Input.read()
  |> String.split("\n", trim: true)
  |> Enum.map(fn blueprint_str ->
    [
      [
        _,
        blueprint_id,
        ore_robot_cost,
        clay_robot_cost,
        obsidian_robot_ore_cost,
        obsidian_robot_clay_cost,
        geode_robot_ore_cost,
        geode_robot_obsidian_cost
      ]
    ] =
      Regex.scan(
        ~r/Blueprint (?\d+): Each ore robot costs (?\d+) ore. Each clay robot costs (?\d+).+ Each obsidian robot costs (?\d+) ore and (?\d+) clay. Each geode robot costs (?\d+) ore and (?\d+) obsidian./,
        blueprint_str
      )

    [
      String.to_integer(blueprint_id),
      %{
        id: String.to_integer(blueprint_id),
        ore_robot: %{cost: String.to_integer(ore_robot_cost), amount: 1},
        clay_robot: %{cost: String.to_integer(clay_robot_cost), amount: 0},
        obsidian_robot: %{
          ore_cost: String.to_integer(obsidian_robot_ore_cost),
          clay_cost: String.to_integer(obsidian_robot_clay_cost),
          amount: 0
        },
        geode_robot: %{
          ore_cost: String.to_integer(geode_robot_ore_cost),
          obsidian_cost: String.to_integer(geode_robot_obsidian_cost),
          amount: 0
        },
        ore: 0,
        clay: 0,
        obsidian: 0,
        geode: 0
      }
    ]
    |> List.to_tuple()
  end)

final_state =
  1..24
  |> Enum.reduce(init_state, fn minute, acc ->
    IO.inspect("===== Minute " <> Integer.to_string(minute) <> "=========")
    # Build
    new_robots = Day19.build_robots(acc)
    # Collect
    new_resources = Day19.pick_resources(acc)

    # IO.inspect(new_robots)
    # IO.inspect(new_resources)

    res_res = Day19.merge(acc, new_robots, new_resources)

    [{_, res}] = res_res
    IO.inspect("geode: " <> Integer.to_string(res[:geode]))
    IO.inspect("obsidian: " <> Integer.to_string(res[:obsidian]))
    IO.inspect("clay: " <> Integer.to_string(res[:clay]))
    IO.inspect("ore: " <> Integer.to_string(res[:ore]))
    IO.inspect("..")
    IO.inspect("geode robots: " <> Integer.to_string(res[:geode_robot][:amount]))
    IO.inspect("obsidian robots: " <> Integer.to_string(res[:obsidian_robot][:amount]))
    IO.inspect("clay robots: " <> Integer.to_string(res[:clay_robot][:amount]))
    IO.inspect("ore robots: " <> Integer.to_string(res[:ore_robot][:amount]))
    res_res
  end)

# final_state
# |> Enum.map(fn {blueprint_id, blueprint} -> 
#   blueprint_id * blueprint[:geode]
# end)
# |> Enum.sum()