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

Day 19

day19.livemd

Day 19

Input

input = """
Blueprint 1: Each ore robot costs 4 ore. Each clay robot costs 2 ore. Each obsidian robot costs 3 ore and 14 clay. Each geode robot costs 2 ore and 7 obsidian.
Blueprint 2: Each ore robot costs 2 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 8 clay. Each geode robot costs 3 ore and 12 obsidian.
"""

Common

kind of ugly recursion with lot’s of repition and potential for off-by-one errors, but it seems to work

defmodule Miner do
  def just_mine(minerals, robots, time) do
    Enum.zip(minerals, robots)
    |> Enum.map(fn {mineral, robot} ->
      mineral + robot * time
    end)
  end

  def better?(first, second) do
    if Enum.at(first, 3) > Enum.at(second, 3) do
      first
    else
      second
    end
  end

  def mine(_, minerals, robots, 0) do
    # IO.inspect({robots, minerals, 0})
    minerals
  end

  def mine(_, _, _, time) when time < 0, do: IO.puts("bad time!")

  def mine(
        [ore_ore, clay_ore, obs_ore, obs_clay, geode_ore, geode_obs] = blueprint,
        [ore, clay, obs, geode] = minerals,
        [ore_robots, clay_robots, obs_robots, geode_robots] = robots,
        time
      ) do
    # IO.inspect({robots, minerals, time})
    best = just_mine(minerals, robots, time)

    build_time =
      cond do
        ore < ore_ore ->
          1 + div(ore_ore - ore - 1, ore_robots) + 1

        # 2* heuristic might not be correct but works on input
        ore >= ore_ore * 2 ->
          999_999

        true ->
          1
      end

    best =
      if time - build_time >= 0 do
        better?(
          mine(
            blueprint,
            [
              ore + ore_robots * build_time - ore_ore,
              clay + clay_robots * build_time,
              obs + obs_robots * build_time,
              geode + geode_robots * build_time
            ],
            [ore_robots + 1, clay_robots, obs_robots, geode_robots],
            time - build_time
          ),
          best
        )
      else
        best
      end

    build_time =
      cond do
        ore < clay_ore ->
          1 + div(clay_ore - ore - 1, ore_robots) + 1

        ore >= clay_ore * 2 ->
          999_999

        true ->
          1
      end

    best =
      if time - build_time >= 0 do
        better?(
          mine(
            blueprint,
            [
              ore + ore_robots * build_time - clay_ore,
              clay + clay_robots * build_time,
              obs + obs_robots * build_time,
              geode + geode_robots * build_time
            ],
            [ore_robots, clay_robots + 1, obs_robots, geode_robots],
            time - build_time
          ),
          best
        )
      else
        best
      end

    build_time =
      cond do
        ore >= 2 * obs_ore and clay >= 2 * obs_clay ->
          999_999

        ore >= obs_ore and clay >= obs_clay ->
          1

        true ->
          if clay_robots > 0 do
            max(
              1 + div(obs_ore - ore - 1, ore_robots) + 1,
              1 + div(obs_clay - clay - 1, clay_robots) + 1
            )
          else
            999_999
          end
      end

    best =
      if time - build_time >= 0 do
        better?(
          mine(
            blueprint,
            [
              ore + ore_robots * build_time - obs_ore,
              clay + clay_robots * build_time - obs_clay,
              obs + obs_robots * build_time,
              geode + geode_robots * build_time
            ],
            [ore_robots, clay_robots, obs_robots + 1, geode_robots],
            time - build_time
          ),
          best
        )
      else
        best
      end

    build_time =
      cond do
        ore >= 2 * geode_ore and obs >= 2 * geode_obs ->
          999_999

        ore >= geode_ore and obs >= geode_obs ->
          1

        true ->
          if obs_robots > 0 do
            max(
              1 + div(geode_ore - ore - 1, ore_robots) + 1,
              1 + div(geode_obs - obs - 1, obs_robots) + 1
            )
          else
            999_999
          end
      end

    if time - build_time >= 0 do
      better?(
        mine(
          blueprint,
          [
            ore + ore_robots * build_time - geode_ore,
            clay + clay_robots * build_time,
            obs + obs_robots * build_time - geode_obs,
            geode + geode_robots * build_time
          ],
          [ore_robots, clay_robots, obs_robots, geode_robots + 1],
          time - build_time
        ),
        best
      )
    else
      best
    end
  end
end
blueprints =
  input
  |> String.split("\n", trim: true)
  |> Enum.map(fn line ->
    Regex.scan(~r/\d+/, line)
    |> tl()
    |> List.flatten()
    |> Enum.map(&amp;String.to_integer/1)
  end)

Part 1

blueprints
# |> Enum.take(10)
|> Enum.map(&amp;Miner.mine(&amp;1, [0, 0, 0, 0], [1, 0, 0, 0], 24))
|> Enum.with_index(fn [_, _, _, geode], index -> geode * (index + 1) end)
|> Enum.sum()

Part 2

blueprints
|> Enum.take(3)
|> Enum.map(&amp;Miner.mine(&amp;1, [0, 0, 0, 0], [1, 0, 0, 0], 32))
|> Enum.map(&amp;Enum.at(&amp;1, 3))
|> Enum.product()