2021-12-21 09:38:17 +01:00
#![ feature(test) ]
2021-12-21 23:24:40 +01:00
use fnv ::FnvHashMap ;
2021-12-21 09:38:17 +01:00
extern crate test ;
2021-12-21 15:45:38 +01:00
type Parsed = ( u16 , u16 ) ;
const INPUT : Parsed = ( 7 , 3 ) ;
fn part1 ( ( p1 , p2 ) : Parsed ) -> usize {
( 1 ..= 100 )
. cycle ( )
2021-12-21 16:02:44 +01:00
. skip ( 1 )
. step_by ( 3 )
2021-12-22 00:41:17 +01:00
. scan ( GameState { odd_round : false , scores : [ ( p1 , 0 ) , ( p2 , 0 ) ] } , | game , die | {
advance_game ( game , die * 3 ) ;
2021-12-22 00:29:21 +01:00
Some ( ( game . scores [ 0 ] . 1 , game . scores [ 1 ] . 1 ) )
2021-12-21 15:45:38 +01:00
} )
2021-12-22 00:29:21 +01:00
. zip ( 1 .. )
. find_map ( | ( ( s1 , s2 ) , r ) | ( s1 > = 1000 | | s2 > = 1000 ) . then ( | | r * 3 * ( s1 . min ( s2 ) as usize ) ) )
2021-12-21 15:45:38 +01:00
. unwrap ( )
2021-12-21 09:38:17 +01:00
}
2021-12-21 23:24:40 +01:00
#[ derive(Debug, Hash, PartialEq, Eq, Clone, Copy) ]
struct GameState {
odd_round : bool ,
// Tuples are (position, total score)
scores : [ ( u16 , usize ) ; 2 ] ,
}
// Just this, but cached and grouped because I don’t want to calc it in the loop
// iproduct!(1..=3, 1..=3, 1..=3).map(|(a, b, c)| a + b + c)
const POSSIBLE_ROLLS : [ ( u16 , usize ) ; 7 ] = [ ( 6 , 7 ) , ( 5 , 6 ) , ( 7 , 6 ) , ( 4 , 3 ) , ( 8 , 3 ) , ( 3 , 1 ) , ( 9 , 1 ) ] ;
2021-12-21 15:45:38 +01:00
fn part2 ( ( p1 , p2 ) : Parsed ) -> usize {
2021-12-21 23:24:40 +01:00
let mut games = FnvHashMap ::default ( ) ;
2021-12-22 00:41:17 +01:00
games . insert ( GameState { odd_round : false , scores : [ ( p1 , 0 ) , ( p2 , 0 ) ] } , 1 ) ;
2021-12-22 00:29:21 +01:00
let mut wins = [ 0 , 0 ] ;
2021-12-22 00:41:17 +01:00
let mut storage = Vec ::with_capacity ( 100_000 ) ;
2021-12-21 23:24:40 +01:00
while ! games . is_empty ( ) {
2021-12-22 00:41:17 +01:00
for ( start , count ) in games . drain ( ) {
2021-12-22 00:29:21 +01:00
for & ( die , count2 ) in & POSSIBLE_ROLLS {
let mut new_state = start ;
advance_game ( & mut new_state , die ) ;
if new_state . scores [ start . odd_round as usize ] . 1 > = 21 {
wins [ new_state . odd_round as usize ] + = count * count2 ;
2021-12-21 23:24:40 +01:00
} else {
2021-12-22 00:41:17 +01:00
storage . push ( ( new_state , count * count2 ) ) ;
2021-12-21 23:24:40 +01:00
}
2021-12-22 00:29:21 +01:00
}
}
2021-12-22 00:41:17 +01:00
// Of all the versions I’ve tried, temporarily storing in a vector and then sorting back
// into the map was by far the fastest.
for ( k , v ) in storage . drain ( .. ) {
* games . entry ( k ) . or_insert ( 0 ) + = v ;
}
2021-12-21 23:24:40 +01:00
}
2021-12-22 00:29:21 +01:00
wins [ 0 ] . max ( wins [ 1 ] )
}
fn advance_game ( game : & mut GameState , die : u16 ) {
let index = game . odd_round as usize ;
let mut points = game . scores [ index ] . 0 + die ;
points - = ( points - 1 ) / 10 * 10 ;
game . scores [ index ] . 0 = points ;
game . scores [ index ] . 1 + = points as usize ;
game . odd_round = ! game . odd_round ;
2021-12-21 09:38:17 +01:00
}
fn main ( ) {
2021-12-21 15:45:38 +01:00
println! ( " Part 1: {} " , part1 ( INPUT ) ) ;
println! ( " Part 2: {} " , part2 ( INPUT ) ) ;
2021-12-21 09:38:17 +01:00
}
#[ cfg(test) ]
mod tests {
use super ::* ;
use aoc2021 ::* ;
2021-12-21 15:45:38 +01:00
#[ test ]
fn part1_test ( ) {
assert_eq! ( part1 ( ( 4 , 8 ) ) , 739785 ) ;
}
2021-12-21 16:02:44 +01:00
2021-12-21 23:24:40 +01:00
#[ test ]
fn part2_test ( ) {
assert_eq! ( part2 ( ( 4 , 8 ) ) , 444356092776315 ) ;
}
2021-12-21 16:02:44 +01:00
#[ bench ]
fn part1_bench ( b : & mut test ::Bencher ) {
b . iter ( | | assert_eq! ( part1 ( test ::black_box ( INPUT ) ) , 551901 ) )
}
2021-12-21 23:24:40 +01:00
#[ bench ]
fn part2_bench ( b : & mut test ::Bencher ) {
b . iter ( | | assert_eq! ( part2 ( test ::black_box ( INPUT ) ) , 272847859601291 ) )
}
2021-12-21 09:38:17 +01:00
}