2019-12-10 20:55:40 +01:00
use std ::collections ::HashMap ;
2019-12-10 19:59:52 +01:00
use std ::io ::{ self , BufRead } ;
2019-12-10 14:59:21 +01:00
2019-12-10 20:55:40 +01:00
#[ derive(Clone, Debug) ]
2019-12-10 19:59:52 +01:00
struct Asteroid {
x : i64 ,
y : i64 ,
2019-12-10 20:55:40 +01:00
visible_asteroids : HashMap < i64 , Vec < Asteroid > > ,
2019-12-10 08:05:30 +01:00
}
2019-12-10 11:45:56 +01:00
2019-12-10 19:59:52 +01:00
impl Asteroid {
pub fn new ( x : usize , y : usize ) -> Self {
Self {
x : x as i64 ,
y : y as i64 ,
2019-12-10 20:55:40 +01:00
visible_asteroids : HashMap ::new ( ) ,
2019-12-10 19:59:52 +01:00
}
}
2019-12-10 20:55:40 +01:00
fn compute_visibles ( mut self , all_asteroids : & [ Asteroid ] ) -> Self {
2019-12-10 19:59:52 +01:00
for ast in all_asteroids {
2019-12-10 20:55:40 +01:00
if ast . x = = self . x & & ast . y = = self . y {
continue ;
}
let angle = calculate_angle ( self . x - ast . x , self . y - ast . y ) ;
self . visible_asteroids . entry ( angle ) . or_insert_with ( Vec ::new ) ;
2019-12-10 19:59:52 +01:00
self . visible_asteroids
2019-12-10 20:55:40 +01:00
. get_mut ( & angle )
. unwrap ( )
. push ( ast . to_owned ( ) ) ;
2019-12-10 19:59:52 +01:00
}
self
}
2019-12-10 20:55:40 +01:00
fn fire_mah_laz0r ( self , limit : usize ) -> Asteroid {
let x = self . x ;
let y = self . y ;
let mut visibles : Vec < ( i64 , Vec < Asteroid > ) > = self
. visible_asteroids
. into_iter ( )
. map ( | ( angle , mut asts ) | {
asts . sort_by_key ( | a | - ( ( a . x - x ) . abs ( ) + ( a . y - y ) . abs ( ) ) ) ;
( angle , asts )
} )
. collect ( ) ;
visibles . sort_by_key ( | & ( angle , _ ) | angle ) ;
let mut num_destroyed = 0 ;
for ( _ , mut asts ) in visibles . into_iter ( ) . cycle ( ) {
match asts . pop ( ) {
None = > ( ) ,
Some ( ast ) = > {
num_destroyed + = 1 ;
if num_destroyed = = limit {
return ast ;
}
}
}
}
unreachable! ( )
}
2019-12-10 11:45:56 +01:00
}
2019-12-10 20:55:40 +01:00
fn read_input ( ) -> Vec < Asteroid > {
io ::stdin ( )
2019-12-10 19:59:52 +01:00
. lock ( )
. lines ( )
. enumerate ( )
. flat_map ( | ( y , l ) | {
l . unwrap ( )
. chars ( )
. enumerate ( )
. filter ( | ( _ , c ) | c = = & '#' )
. map ( move | ( x , _ ) | Asteroid ::new ( x , y ) )
. collect ::< Vec < _ > > ( )
} )
2019-12-10 20:55:40 +01:00
. collect ( )
}
2019-12-10 19:59:52 +01:00
2019-12-10 20:55:40 +01:00
fn main ( ) {
let input = read_input ( ) ;
2019-12-10 19:59:52 +01:00
let part1 = input
. clone ( )
. into_iter ( )
. map ( | ast | ast . compute_visibles ( & input ) )
. max_by_key ( | ast | ast . visible_asteroids . len ( ) )
. unwrap ( ) ;
println! ( " Part 1: {} " , part1 . visible_asteroids . len ( ) ) ;
2019-12-10 20:55:40 +01:00
let part2 = part1 . fire_mah_laz0r ( 200 ) ;
println! ( " Part 2: {} " , part2 . x * 100 + part2 . y ) ;
2019-12-10 14:59:21 +01:00
}
2019-12-10 19:59:52 +01:00
fn calculate_angle ( x_offset : i64 , y_offset : i64 ) -> i64 {
2019-12-10 20:55:40 +01:00
// Angles in the assignment start at 0 degrees when pointing up (negative y), so we have to
// offset them here.
let mut raw_angle =
( ( ( y_offset as f64 ) . atan2 ( x_offset as f64 ) . to_degrees ( ) * 1000.0 ) . round ( ) as i64 ) - 90_000 ;
if raw_angle < 0 {
raw_angle + = 360_000 ;
}
raw_angle
2019-12-10 14:59:21 +01:00
}
2019-12-10 11:45:56 +01:00
#[ cfg(test) ]
mod tests {
use super ::* ;
#[ test ]
fn test_angle_calculation ( ) {
2019-12-10 20:55:40 +01:00
assert_eq! ( calculate_angle ( 0 , - 1 ) , 0 ) ;
assert_eq! ( calculate_angle ( 1 , 0 ) , 90_000 ) ;
assert_eq! ( calculate_angle ( 1 , 1 ) , 135_000 ) ;
assert_eq! ( calculate_angle ( - 1 , - 1 ) , 315_000 ) ;
2019-12-10 11:45:56 +01:00
}
}