solve_day19 :: (test: bool) {
contents := read_entire_file(ifx test then "inputs/day19_test.txt" else "inputs/day19.txt");
lines := split(contents, cast(u8) #char "\n");
part1 := 0;
part2 := 1;
counter := 0;
for lines {
blueprint := parse_blueprint(it);
res, count := run_blueprint(blueprint, 24) * (it_index + 1);
part1 += res;
counter += count;
if it_index < 3 {
res,count = run_blueprint(blueprint, 32);
counter += count;
part2 *= res;
}
}
print("Part 1: %\n", part1);
print("Part 2: %\n", part2);
print("Counter: %\n", counter);
}
run_blueprint :: (blueprint: Blueprint, time: s64) -> s64, s64{
queue: Deque(Permutation);
init(*queue);
resize(*queue, 2_000_000);
defer deinit(*queue);
deque_add_last(*queue, .{
to_build = .ORE,
time = time,
});
deque_add_last(*queue, .{
to_build = .CLAY,
time = time,
});
result := 0;
counter := 0;
max_deque := 0;
while queue.count > 0 {
max_deque = max(max_deque, queue.count);
permutation := deque_remove_first(*queue);
counter += 1;
if permutation.time <= 0 || calc_max(permutation) <= result {
continue;
}
while permutation.time > 0 {
building := Robot_Type.NONE;
if permutation.to_build == {
case .ORE;
if has_enough(permutation, blueprint.cost[0]) {
building = .ORE;
subtract_cost(*permutation, blueprint.cost[0]);
}
case .CLAY;
if has_enough(permutation, blueprint.cost[1]) {
building = .CLAY;
subtract_cost(*permutation, blueprint.cost[1]);
}
case .OBSIDIAN;
if has_enough(permutation, blueprint.cost[2]) {
building = .OBSIDIAN;
subtract_cost(*permutation, blueprint.cost[2]);
}
case .GEODE;
if has_enough(permutation, blueprint.cost[3]) {
building = .GEODE;
subtract_cost(*permutation, blueprint.cost[3]);
}
}
mine(*permutation);
result = max(result, permutation.geode);
if building == {
case .ORE; permutation.ore_robots += 1;
case .CLAY; permutation.clay_robots += 1;
case .OBSIDIAN; permutation.obsidian_robots += 1;
case .GEODE; permutation.geode_robots += 1;
}
if building != .NONE {
if can_build(permutation, blueprint.cost[3]) {
new_perm := permutation;
new_perm.to_build = .GEODE;
deque_add_last(*queue, new_perm);
}
if permutation.ore_robots < blueprint.max_ore_robots {
new_perm := permutation;
new_perm.to_build = .ORE;
deque_add_last(*queue, new_perm);
}
if permutation.clay_robots < blueprint.max_clay_robots {
new_perm := permutation;
new_perm.to_build = .CLAY;
deque_add_last(*queue, new_perm);
}
if permutation.obsidian_robots < blueprint.max_obsidian_robots && can_build(permutation, blueprint.cost[2]) {
new_perm := permutation;
new_perm.to_build = .OBSIDIAN;
deque_add_last(*queue, new_perm);
}
break;
}
}
}
return result, max_deque;
}
can_build :: (permutation: Permutation, robot: Robot) -> bool {
if robot.clay_cost > 0 && permutation.clay_robots == 0 return false;
if robot.obsidian_cost > 0 && permutation.obsidian_robots == 0 return false;
return true;
}
mine :: (permutation: *Permutation) {
permutation.time -= 1;
permutation.ore += permutation.ore_robots;
permutation.clay += permutation.clay_robots;
permutation.obsidian += permutation.obsidian_robots;
permutation.geode += permutation.geode_robots;
}
has_enough :: (permutation: Permutation, robot: Robot) -> bool {
result := permutation.ore >= robot.ore_cost &&
permutation.clay >= robot.clay_cost &&
permutation.obsidian >= robot.obsidian_cost;
return result;
}
subtract_cost :: (permutation: *Permutation, robot: Robot) {
permutation.ore -= robot.ore_cost;
permutation.clay -= robot.clay_cost;
permutation.obsidian -= robot.obsidian_cost;
}
parse_blueprint :: (line: string) -> Blueprint {
colon_index := find_index_from_left(line, cast(u8) #char ":");
robots := split(slice(line, colon_index + 2, line.count - colon_index - 3), ". ");
blueprint: Blueprint = .{
cost = Robot.[
parse_robot(robots[0]),
parse_robot(robots[1]),
parse_robot(robots[2]),
parse_robot(robots[3]),
],
};
for blueprint.cost {
blueprint.max_ore_robots = max(blueprint.max_ore_robots, it.ore_cost);
blueprint.max_clay_robots = max(blueprint.max_clay_robots, it.clay_cost);
blueprint.max_obsidian_robots = max(blueprint.max_obsidian_robots, it.obsidian_cost);
}
return blueprint;
}
calc_max :: (permutation: Permutation) -> s64 {
return permutation.geode + (permutation.time + 2 * permutation.geode_robots) * permutation.time / 2;
}
parse_robot :: (line: string) -> Robot {
parts := split(line, cast(u8) #char " ");
cost1 := string_to_int(parts[4], 10, s64);
cost2 := -1;
robot: Robot;
if parts[5] == {
case "ore";
robot.ore_cost = cost1;
case "clay";
robot.clay_cost = cost1;
case "obsidian";
robot.obsidian_cost = cost1;
case;
assert(false, "Bad cost category '%'", parts[5]);
}
if parts.count == 9 {
cost2 = string_to_int(parts[7], 10, s64);
if parts[8] == {
case "ore";
robot.ore_cost = cost2;
case "clay";
robot.clay_cost = cost2;
case "obsidian";
robot.obsidian_cost = cost2;
case;
assert(false, "Bad cost category '%'", parts[8]);
}
}
return robot;
}
Permutation :: struct {
time: s64 = 0;
ore_robots: s64 = 1;
clay_robots: s64 = 0;
obsidian_robots: s64 = 0;
geode_robots: s64 = 0;
ore: s64;
clay: s64;
obsidian: s64;
geode: s64;
to_build: Robot_Type = .NONE;
}
Blueprint :: struct {
max_ore_robots: s64;
max_clay_robots: s64;
max_obsidian_robots: s64;
cost: [4]Robot;
}
Robot :: struct {
ore_cost: s64;
clay_cost: s64;
obsidian_cost: s64;
}
Robot_Type :: enum {
ORE :: 0;
CLAY :: 1;
OBSIDIAN :: 2;
GEODE :: 3;
NONE :: 4;
}