2022-12-19 12:29:55 +01:00
#![ feature(test) ]
extern crate test ;
use std ::ops ::Sub ;
use aoc2022 ::{ boilerplate , common ::* } ;
use scanf ::sscanf ;
const DAY : usize = 19 ;
type Parsed = Vec < Blueprint > ;
#[ derive(Copy, Clone, PartialEq, Eq, Debug, Default) ]
struct Factory {
minute : u32 ,
2022-12-20 00:48:11 +01:00
waited : u32 ,
2022-12-19 12:29:55 +01:00
ore : u32 ,
ore_robot : u32 ,
clay : u32 ,
clay_robot : u32 ,
obsidian : u32 ,
obsidian_robot : u32 ,
geode : u32 ,
geode_robot : u32 ,
}
impl Factory {
fn pass_minute ( self ) -> Self {
Factory {
ore : self . ore + self . ore_robot ,
clay : self . clay + self . clay_robot ,
obsidian : self . obsidian + self . obsidian_robot ,
geode : self . geode + self . geode_robot ,
minute : self . minute + 1 ,
2022-12-20 00:48:11 +01:00
waited : self . waited + 1 ,
2022-12-19 12:29:55 +01:00
.. self
}
}
fn can_afford ( & self , price : & Price ) -> bool {
self . ore > = price . ore & & self . clay > = price . clay & & self . obsidian > = price . obsidian
}
}
impl Sub < Price > for Factory {
type Output = Self ;
fn sub ( self , price : Price ) -> Self ::Output {
Factory { ore : self . ore - price . ore , clay : self . clay - price . clay , obsidian : self . obsidian - price . obsidian , .. self }
}
}
#[ derive(Copy, Clone, PartialEq, Eq, Debug, Default) ]
struct Blueprint {
number : u32 ,
ore_robot : Price ,
clay_robot : Price ,
obsidian_robot : Price ,
geode_robot : Price ,
max_ore : u32 ,
}
#[ derive(Copy, Clone, PartialEq, Eq, Debug, Default) ]
struct Price {
ore : u32 ,
clay : u32 ,
obsidian : u32 ,
}
fn parse_input ( raw : & str ) -> Parsed {
raw . lines ( ) . map ( | line | {
let mut bp = Blueprint ::default ( ) ;
let mut number = 0 ;
let mut ore_robot_ore = 0 ;
let mut clay_robot_ore = 0 ;
let mut obsidian_robot_ore = 0 ;
let mut obsidian_robot_clay = 0 ;
let mut geode_robot_ore = 0 ;
let mut geode_robot_obsidian = 0 ;
// Unfortunately, this macro only takes `ident`, not `expr`, so I have to use temporary
// variables that are assigned to fields of the blueprint later
sscanf! ( line , " Blueprint {}: Each ore robot costs {} ore. Each clay robot costs {} ore. Each obsidian robot costs {} ore and {} clay. Each geode robot costs {} ore and {} obsidian. " , number ,
ore_robot_ore , clay_robot_ore , obsidian_robot_ore , obsidian_robot_clay , geode_robot_ore , geode_robot_obsidian ) . unwrap ( ) ;
bp . number = number ;
bp . ore_robot . ore = ore_robot_ore ;
bp . clay_robot . ore = clay_robot_ore ;
bp . obsidian_robot . ore = obsidian_robot_ore ;
bp . obsidian_robot . clay = obsidian_robot_clay ;
bp . geode_robot . ore = geode_robot_ore ;
bp . geode_robot . obsidian = geode_robot_obsidian ;
bp . max_ore = clay_robot_ore . max ( obsidian_robot_ore ) . max ( geode_robot_ore ) ;
bp
} ) . collect ( )
}
fn part1 ( parsed : & Parsed ) -> u32 {
2022-12-20 00:48:11 +01:00
parsed . iter ( ) . map ( | blueprint | max_geodes ( Factory { ore_robot : 1 , .. Default ::default ( ) } , blueprint , 3 , 24 ) * blueprint . number ) . sum ( )
2022-12-19 12:29:55 +01:00
}
fn part2 ( parsed : & Parsed ) -> u32 {
2022-12-20 00:48:11 +01:00
parsed . iter ( ) . take ( 3 ) . map ( | blueprint | max_geodes ( Factory { ore_robot : 1 , .. Default ::default ( ) } , blueprint , 4 , 32 ) ) . product ( )
2022-12-19 12:29:55 +01:00
}
2022-12-20 00:48:11 +01:00
fn max_geodes ( factory : Factory , bp : & Blueprint , wait_limit : u32 , limit : u32 ) -> u32 {
2022-12-19 12:29:55 +01:00
if factory . minute = = limit {
return factory . geode ;
}
if factory . can_afford ( & bp . geode_robot ) {
2022-12-20 00:48:11 +01:00
return max_geodes (
Factory { geode_robot : factory . geode_robot + 1 , waited : 0 , .. factory . pass_minute ( ) } - bp . geode_robot ,
bp ,
wait_limit ,
limit ,
) ;
2022-12-19 12:29:55 +01:00
}
2022-12-20 00:48:11 +01:00
// This assumption (“if we can build an obsidian robot, we should”) holds for my input but not the test input.
// Not entirely fair, but good enough until (or if at all) I figure out a better way.
2022-12-19 12:29:55 +01:00
if factory . obsidian_robot < bp . geode_robot . obsidian & & factory . can_afford ( & bp . obsidian_robot ) {
2022-12-20 00:48:11 +01:00
return max_geodes (
Factory { obsidian_robot : factory . obsidian_robot + 1 , waited : 0 , .. factory . pass_minute ( ) } - bp . obsidian_robot ,
bp ,
wait_limit ,
limit ,
) ;
2022-12-19 12:29:55 +01:00
}
2022-12-20 00:48:11 +01:00
2022-12-19 12:29:55 +01:00
let mut outcomes = Vec ::with_capacity ( 3 ) ;
if factory . ore_robot < bp . max_ore & & factory . can_afford ( & bp . ore_robot ) {
2022-12-20 00:48:11 +01:00
outcomes . push ( Factory { ore_robot : factory . ore_robot + 1 , waited : 0 , .. factory . pass_minute ( ) } - bp . ore_robot ) ;
2022-12-19 12:29:55 +01:00
}
if factory . clay_robot < bp . obsidian_robot . clay & & factory . can_afford ( & bp . clay_robot ) {
2022-12-20 00:48:11 +01:00
outcomes . push ( Factory { clay_robot : factory . clay_robot + 1 , waited : 0 , .. factory . pass_minute ( ) } - bp . clay_robot ) ;
}
if outcomes . is_empty ( ) | | factory . waited < wait_limit {
outcomes . push ( factory . pass_minute ( ) ) ;
2022-12-19 12:29:55 +01:00
}
2022-12-20 00:48:11 +01:00
outcomes . into_iter ( ) . take ( 2 ) . map ( | f | max_geodes ( f , bp , wait_limit , limit ) ) . max ( ) . unwrap ( )
2022-12-19 12:29:55 +01:00
}
boilerplate! {
TEST_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 . " ,
2022-12-20 00:48:11 +01:00
tests : { part1 : { TEST_INPUT = > 33 } } ,
2022-12-19 12:29:55 +01:00
bench1 = = 1150 ,
bench2 = = 37367 ,
bench_parse : Vec ::len = > 30 ,
}