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)