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

Day 19 - take 2

2022/day19-take2.livemd

Day 19 - take 2

Mix.install([:kino])

Setup

input = Kino.Input.textarea("input file")
text =
  input
  |> Kino.Input.read()

Common

defmodule Parser do
  def parse(text) do
    text
    |> 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
        )

      %{
        index: String.to_integer(blueprint_id),
        cost: %{
          clay: %{ore: String.to_integer(clay_robot_cost)},
          geode: %{
            obsidian: String.to_integer(geode_robot_obsidian_cost),
            ore: String.to_integer(geode_robot_ore_cost)
          },
          obsidian: %{
            clay: String.to_integer(obsidian_robot_clay_cost),
            ore: String.to_integer(obsidian_robot_ore_cost)
          },
          ore: %{ore: String.to_integer(ore_robot_cost)}
        }
      }
    end)
  end
end
defmodule Day19 do
  def start(costs, t) do
    bots = {1, 0, 0, 0}
    reserve = {0, 0, 0, 0}
    work(0, bots, reserve, costs, t)
  end

  def work(mx, bots, reserve, costs, 0) do
    do_work(mx, nil, bots, reserve, costs, 0)
  end

  def work(mx, bots, reserve, costs, t) do
    mx
    |> do_work(:geode, bots, reserve, costs, t)
    |> do_work(:obsidian, bots, reserve, costs, t)
    |> do_work(:clay, bots, reserve, costs, t)
    |> do_work(:ore, bots, reserve, costs, t)
    |> do_work(nil, bots, reserve, costs, t)
  end

  # do nothing
  def do_work(mx, nil, {_, _, _, bots}, {_, _, _, reserve}, _, t), do: max(mx, reserve + bots * t)

  # not enough obsidian 
  def do_work(mx, :geode, {_, _, b, _}, {_, _, r, _}, %{geode: %{obsidian: c}}, t)
      when b * (t - 2) + r < c,
      do: mx

  # not enough ore 
  def do_work(mx, :geode, {b, _, _, _}, {r, _, _, _}, %{geode: %{ore: c}}, t)
      when b * (t - 2) + r < c,
      do: mx

  # not enough clay 
  def do_work(mx, :obsidian, {_, b, _, _}, {_, r, _, _}, %{obsidian: %{clay: c}}, t)
      when b * (t - 2) + r < c,
      do: mx

  # not enough ore production to get an obsidian bot
  def do_work(mx, :obsidian, {b, _, _, _}, {r, _, _, _}, %{obsidian: %{ore: c}}, t)
      when b * (t - 2) + r < c,
      do: mx

  # not enough ore production to get a clay bot
  def do_work(mx, :clay, {b, _, _, _}, {r, _, _, _}, %{clay: %{ore: c}}, t)
      when b * (t - 2) + r < c,
      do: mx

  # not enough ore production to get an ore bot
  def do_work(mx, :ore, {b, _, _, _}, {r, _, _, _}, %{ore: %{ore: c}}, t)
      when b * (t - 2) + r < c,
      do: mx

  # no more obsidian bots needed
  def do_work(mx, :obsidian, {_, _, b, _}, _, %{geode: %{obsidian: c}}, _)
      when b >= c,
      do: mx

  # no more clay bots needed
  def do_work(mx, :clay, {_, b, _, _}, _, %{obsidian: %{clay: c}}, _)
      when b >= c,
      do: mx

  # no more ore bots needed
  def do_work(mx, :ore, {b, _, _, _}, _, c, _)
      when b >= c.clay.ore and b >= c.obsidian.ore and b >= c.geode.ore,
      do: mx

  def do_work(mx, :geode, bots, reserve, costs, t) do
    {ore_bots, clay_bots, obsidian_bots, geode_bots} = bots
    {ore_reserve, clay_reserve, obsidian_reserve, geode_reserve} = reserve
    %{geode: %{ore: ore_cost, obsidian: obsidian_cost}} = costs

    t_needed =
      0
      |> max(div(ore_cost - ore_reserve + ore_bots - 1, ore_bots))
      |> max(div(obsidian_cost - obsidian_reserve + obsidian_bots - 1, obsidian_bots))
      |> then(fn val -> val + 1 end)

    reserve = {
      ore_reserve + t_needed * ore_bots - ore_cost,
      clay_reserve + t_needed * clay_bots,
      obsidian_reserve + t_needed * obsidian_bots - obsidian_cost,
      geode_reserve + t_needed * geode_bots
    }

    bots = {ore_bots, clay_bots, obsidian_bots, geode_bots + 1}

    work(mx, bots, reserve, costs, t - t_needed)
  end

  def do_work(mx, :obsidian, bots, reserve, costs, t) do
    {ore_bots, clay_bots, obsidian_bots, geode_bots} = bots
    {ore_reserve, clay_reserve, obsidian_reserve, geode_reserve} = reserve
    %{obsidian: %{ore: ore_cost, clay: clay_cost}} = costs

    t_needed =
      0
      |> max(div(ore_cost - ore_reserve + ore_bots - 1, ore_bots))
      |> max(div(clay_cost - clay_reserve + clay_bots - 1, clay_bots))
      |> then(fn val -> val + 1 end)

    reserve = {
      ore_reserve + t_needed * ore_bots - ore_cost,
      clay_reserve + t_needed * clay_bots - clay_cost,
      obsidian_reserve + t_needed * obsidian_bots,
      geode_reserve + t_needed * geode_bots
    }

    bots = {ore_bots, clay_bots, obsidian_bots + 1, geode_bots}

    work(mx, bots, reserve, costs, t - t_needed)
  end

  def do_work(mx, :clay, bots, reserve, costs, t) do
    {ore_bots, clay_bots, obsidian_bots, geode_bots} = bots
    {ore_reserve, clay_reserve, obsidian_reserve, geode_reserve} = reserve
    %{clay: %{ore: ore_cost}} = costs

    t_needed =
      0
      |> max(div(ore_cost - ore_reserve + ore_bots - 1, ore_bots))
      |> then(fn val -> val + 1 end)

    reserve = {
      ore_reserve + t_needed * ore_bots - ore_cost,
      clay_reserve + t_needed * clay_bots,
      obsidian_reserve + t_needed * obsidian_bots,
      geode_reserve + t_needed * geode_bots
    }

    bots = {ore_bots, clay_bots + 1, obsidian_bots, geode_bots}

    work(mx, bots, reserve, costs, t - t_needed)
  end

  def do_work(mx, :ore, bots, reserve, costs, t) do
    {ore_bots, clay_bots, obsidian_bots, geode_bots} = bots
    {ore_reserve, clay_reserve, obsidian_reserve, geode_reserve} = reserve
    %{ore: %{ore: ore_cost}} = costs

    t_needed =
      0
      |> max(div(ore_cost - ore_reserve + ore_bots - 1, ore_bots))
      |> then(fn val -> val + 1 end)

    reserve = {
      ore_reserve + t_needed * ore_bots - ore_cost,
      clay_reserve + t_needed * clay_bots,
      obsidian_reserve + t_needed * obsidian_bots,
      geode_reserve + t_needed * geode_bots
    }

    bots = {ore_bots + 1, clay_bots, obsidian_bots, geode_bots}

    work(mx, bots, reserve, costs, t - t_needed)
  end
end
data = Parser.parse(text)
Enum.sum(for %{index: i, cost: c} <- data, do: Day19.start(c, 24) * i)
data = Parser.parse(text)

for %{index: i, cost: c} <- data, i < 4 do
  Task.async(fn -> Day19.start(c, 32) end)
end
|> Task.await_many(:infinity)
|> Enum.reduce(fn val, acc -> val * acc end)