diff --git a/README.md b/README.md index 054ac1c..8846293 100644 --- a/README.md +++ b/README.md @@ -249,6 +249,7 @@ Performance is reasonable even on older hardware, for example a 2011 MacBook Pro | 10 | [The Stars Align](https://adventofcode.com/2018/day/10) | [Source](src/year2018/day10.rs) | 12 | | 11 | [Chronal Charge](https://adventofcode.com/2018/day/11) | [Source](src/year2018/day11.rs) | 1552 | | 12 | [Subterranean Sustainability](https://adventofcode.com/2018/day/12) | [Source](src/year2018/day12.rs) | 75 | +| 13 | [Mine Cart Madness](https://adventofcode.com/2018/day/13) | [Source](src/year2018/day13.rs) | 391 | ## 2017 diff --git a/benches/benchmark.rs b/benches/benchmark.rs index e76426a..d6be348 100644 --- a/benches/benchmark.rs +++ b/benches/benchmark.rs @@ -141,6 +141,7 @@ mod year2018 { benchmark!(year2018, day10); benchmark!(year2018, day11); benchmark!(year2018, day12); + benchmark!(year2018, day13); } mod year2019 { diff --git a/src/lib.rs b/src/lib.rs index 3cdaee8..8cb11f2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -124,6 +124,7 @@ pub mod year2018 { pub mod day10; pub mod day11; pub mod day12; + pub mod day13; } /// # Rescue Santa from deep space with a solar system adventure. diff --git a/src/main.rs b/src/main.rs index f20ffe4..9f3d714 100644 --- a/src/main.rs +++ b/src/main.rs @@ -191,6 +191,7 @@ fn year2018() -> Vec { solution!(year2018, day10), solution!(year2018, day11), solution!(year2018, day12), + solution!(year2018, day13), ] } diff --git a/src/year2018/day13.rs b/src/year2018/day13.rs new file mode 100644 index 0000000..f99efdc --- /dev/null +++ b/src/year2018/day13.rs @@ -0,0 +1,142 @@ +//! # Mine Cart Madness +//! +//! Simulates the mine carts for both parts. When checking the grid we only care about `\`, `/` +//! and `+` characters, all other characters can be ignored. Carts are sorted by `y` and then by +//! `x` before each tick as the movement order is important to resolve collisions or near misses +//! correctly. +use crate::util::grid::*; +use crate::util::point::*; + +pub struct Input { + grid: Grid, + carts: Vec, +} + +#[derive(Clone, Copy)] +pub struct Cart { + position: Point, + direction: Point, + turns: u32, + active: bool, +} + +impl Cart { + fn new(position: Point, direction: Point) -> Cart { + Cart { position, direction, turns: 0, active: true } + } + + fn tick(&mut self, grid: &Grid) { + self.position += self.direction; + + match grid[self.position] { + b'\\' => { + self.direction = match self.direction { + UP => LEFT, + DOWN => RIGHT, + LEFT => UP, + RIGHT => DOWN, + _ => unreachable!(), + } + } + b'/' => { + self.direction = match self.direction { + UP => RIGHT, + DOWN => LEFT, + LEFT => DOWN, + RIGHT => UP, + _ => unreachable!(), + } + } + b'+' => { + self.direction = match self.turns { + 0 => self.direction.counter_clockwise(), + 1 => self.direction, + 2 => self.direction.clockwise(), + _ => unreachable!(), + }; + self.turns = (self.turns + 1) % 3; + } + _ => (), + } + } +} + +pub fn parse(input: &str) -> Input { + let grid = Grid::parse(input); + let mut carts = Vec::new(); + + for (i, b) in grid.bytes.iter().enumerate() { + let result = match b { + b'^' => Some(UP), + b'v' => Some(DOWN), + b'<' => Some(LEFT), + b'>' => Some(RIGHT), + _ => None, + }; + + if let Some(direction) = result { + let x = i as i32 % grid.width; + let y = i as i32 / grid.width; + carts.push(Cart::new(Point::new(x, y), direction)); + } + } + + Input { grid, carts } +} + +pub fn part1(input: &Input) -> String { + let mut carts = input.carts.clone(); + let mut occupied = input.grid.default_copy(); + + loop { + // Turn order is important. + carts.sort_unstable_by_key(|c| input.grid.width * c.position.y + c.position.x); + + for cart in &mut carts { + // Follow tracks to next position. + occupied[cart.position] = false; + cart.tick(&input.grid); + let next = cart.position; + + if occupied[next] { + return format!("{},{}", next.x, next.y); + } + + occupied[next] = true; + } + } +} + +pub fn part2(input: &Input) -> String { + let mut carts = input.carts.clone(); + let mut occupied = input.grid.default_copy(); + + while carts.len() > 1 { + // Turn order is important. + carts.sort_unstable_by_key(|c| input.grid.width * c.position.y + c.position.x); + + for i in 0..carts.len() { + // Crashed carts may not have been removed yet. + if carts[i].active { + // Follow tracks to next position. + occupied[carts[i].position] = false; + carts[i].tick(&input.grid); + let next = carts[i].position; + + if occupied[next] { + // Mark both carts as crashed. + carts.iter_mut().filter(|c| c.position == next).for_each(|c| c.active = false); + occupied[next] = false; + } else { + occupied[next] = true; + } + } + } + + // Removed crashed carts to speed up future ticks. + carts.retain(|c| c.active); + } + + let last = carts[0].position; + format!("{},{}", last.x, last.y) +} diff --git a/tests/test.rs b/tests/test.rs index 0583c3a..786a8c2 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -125,6 +125,7 @@ mod year2018 { mod day10_test; mod day11_test; mod day12_test; + mod day13_test; } mod year2019 { diff --git a/tests/year2018/day13_test.rs b/tests/year2018/day13_test.rs new file mode 100644 index 0000000..f034740 --- /dev/null +++ b/tests/year2018/day13_test.rs @@ -0,0 +1,28 @@ +use aoc::year2018::day13::*; + +const FIRST_EXAMPLE: &str = r"/->-\ . +| | /----\ . +| /-+--+-\ | . +| | | | v | . +\-+-/ \-+--/ . + \------/ ."; + +const SECOND_EXAMPLE: &str = r"/>-<\ . +| | . +| /<+-\ . +| | | v . +\>+/ ."; + +#[test] +fn part1_test() { + let input = parse(FIRST_EXAMPLE); + assert_eq!(part1(&input), "7,3"); +} + +#[test] +fn part2_test() { + let input = parse(SECOND_EXAMPLE); + assert_eq!(part2(&input), "6,4"); +}