diff --git a/2018/Cargo.toml b/2018/Cargo.toml index 891f18c0..fd9b379f 100644 --- a/2018/Cargo.toml +++ b/2018/Cargo.toml @@ -1,6 +1,6 @@ # https://adventofcode.com/2018 [workspace] -members = ["day*", "toto"] +members = ["day*"] resolver = "2" diff --git a/2018/README.md b/2018/README.md index 444c8d8f..dd595248 100644 --- a/2018/README.md +++ b/2018/README.md @@ -1,11 +1,11 @@ # Advent of Code in Rust 🦀 ![AoC2018](https://img.shields.io/badge/Advent_of_Code-2018-8A2BE2) -![Stars: 45](https://img.shields.io/badge/Stars-45⭐-blue) -![Rust: 23](https://img.shields.io/badge/Rust-23-cyan?logo=Rust) +![Stars: 50](https://img.shields.io/badge/Stars-50⭐-blue) +![Rust: 25](https://img.shields.io/badge/Rust-25-cyan?logo=Rust) ![Python: 4](https://img.shields.io/badge/Python-4-cyan?logo=Python) -## 2018 ([Calendar](https://adventofcode.com/2018)) ([Solutions](../2018/)) : 45⭐ +## 2018 ([Calendar](https://adventofcode.com/2018)) ([Solutions](../2018/)) : 50⭐ Puzzle | Stars | Languages ------------------------------------------------------------------------------------ | ----- | ----------- @@ -23,6 +23,7 @@ Puzzle [Day 12: Subterranean Sustainability](https://adventofcode.com/2018/day/12) | ⭐⭐ | [Rust](../2018/day12/day12.rs) [Day 13: Mine Cart Madness](https://adventofcode.com/2018/day/13) | ⭐⭐ | [Rust](../2018/day13/day13.rs) [Day 14: Chocolate Charts](https://adventofcode.com/2018/day/14) | ⭐⭐ | [Rust](../2018/day14/day14.rs) +[Day 15: Beverage Bandits](https://adventofcode.com/2018/day/15) | ⭐⭐ | [Rust](../2018/day15/day15.rs) [Day 16: Chronal Classification](https://adventofcode.com/2018/day/16) | ⭐⭐ | [Rust](../2018/day16/day16.rs) [Python](../2018/day16/day16.py) [Day 17: Reservoir Research](https://adventofcode.com/2018/day/17) | ⭐⭐ | [Rust](../2018/day17/day17.rs) [Day 18: Settlers of The North Pole](https://adventofcode.com/2018/day/18) | ⭐⭐ | [Rust](../2018/day18/day18.rs) [Python](../2018/day18/day18.py) @@ -31,4 +32,5 @@ Puzzle [Day 21: Chronal Conversion](https://adventofcode.com/2018/day/21) | ⭐⭐ | [Rust](../2018/day21/day21.rs) [Day 22: Mode Maze](https://adventofcode.com/2018/day/22) | ⭐⭐ | [Rust](../2018/day22/day22.rs) [Day 23: Experimental Emergency Teleportation](https://adventofcode.com/2018/day/23) | ⭐⭐ | [Rust](../2018/day23/day23.rs) -[Day 25: Four-Dimensional Adventure](https://adventofcode.com/2018/day/25) | ⭐ | [Rust](../2018/day25/day25.rs) +[Day 24: Immune System Simulator 20XX](https://adventofcode.com/2018/day/24) | ⭐⭐ | [Rust](../2018/day24/src/main.rs) +[Day 25: Four-Dimensional Adventure](https://adventofcode.com/2018/day/25) | ⭐⭐ | [Rust](../2018/day25/day25.rs) diff --git a/2018/day15/Cargo.toml b/2018/day15/Cargo.toml new file mode 100644 index 00000000..46c03c2c --- /dev/null +++ b/2018/day15/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "day15" +version = "0.1.0" +edition = "2021" + +[dependencies] +aoc = { path = "../../aoc" } + +[[bin]] +name = "day15" +path = "day15.rs" diff --git a/2018/day15/day15.rs b/2018/day15/day15.rs new file mode 100644 index 00000000..f40c98a4 --- /dev/null +++ b/2018/day15/day15.rs @@ -0,0 +1,359 @@ +//! [Day 15: Beverage Bandits](https://adventofcode.com/2018/day/15) + +use std::collections::{HashMap, HashSet}; +use std::{cmp::Ordering, collections::VecDeque}; + +type Grid = aoc::grid::Grid; + +const ELF: char = 'E'; +const GOBLIN: char = 'G'; +const WALL: char = '#'; + +#[derive(Clone)] +struct Unit { + x: usize, + y: usize, + hit_points: u32, + attack_power: u32, + race: char, +} + +impl Unit { + fn attack_order(&self, other: &Self) -> Ordering { + self.y.cmp(&other.y).then(self.x.cmp(&other.x)) + } + + fn min_hp_order(&self, other: &Self) -> Ordering { + self.hit_points + .cmp(&other.hit_points) + .then(Self::attack_order(self, other)) + } +} +struct Puzzle { + wall: HashSet<(usize, usize)>, + units: Vec, +} + +fn adjacent(x: usize, y: usize) -> Vec<(usize, usize)> { + vec![(x, y - 1), (x - 1, y), (x + 1, y), (x, y + 1)] +} + +fn parse_target(targets: &[Unit], race: char) -> HashMap<(usize, usize), Vec> { + let mut target_mapping: HashMap<(usize, usize), Vec> = HashMap::new(); + + for (i, t) in targets.iter().enumerate() { + if t.race != race && t.hit_points != 0 { + for adj in adjacent(t.x, t.y) { + target_mapping.entry(adj).or_default().push(i); + } + } + } + + target_mapping +} + +fn next_pos( + u: usize, + units: &[Unit], + target_adj: &HashSet<(usize, usize)>, + wall: &HashSet<(usize, usize)>, +) -> Option<(usize, usize)> { + let others: HashSet<(usize, usize)> = units + .iter() + .enumerate() + .filter_map(|(i, unit)| { + if i != u && unit.hit_points != 0 { + Some((unit.x, unit.y)) + } else { + None + } + }) + .collect(); + + let mut pos = vec![]; + + let mut q = VecDeque::new(); + let mut visited = HashSet::new(); + + q.extend( + adjacent(units[u].x, units[u].y) + .iter() + .map(|(x, y)| (*x, *y, 1, (*x, *y))), + ); + + visited.insert((units[u].x, units[u].y)); + + let mut min_path = u32::MAX; + + while let Some(e) = q.pop_front() { + // + let (x, y, steps, start) = e; + + if min_path != u32::MAX && steps > min_path { + continue; + } + + if wall.contains(&(x, y)) || others.contains(&(x, y)) { + continue; + } + + if target_adj.contains(&(x, y)) { + min_path = min_path.min(steps); + pos.push(e); + } else if !visited.contains(&(x, y)) { + visited.insert((x, y)); + + q.extend( + adjacent(x, y) + .iter() + .map(|(nx, ny)| (*nx, *ny, steps + 1, start)), + ); + } + } + + pos.iter() + .min_by_key(|(x, y, steps, (sx, sy))| (steps, y, x, sy, sx)) + .map(|(_, _, _, start)| *start) +} + +fn has_elves_and_goblins(units: &[Unit]) -> bool { + let mut goblins = false; + let mut elves = false; + + for unit in units { + if unit.hit_points != 0 { + match unit.race { + GOBLIN => goblins = true, + ELF => elves = true, + _ => (), + } + + if goblins && elves { + return true; + } + } + } + + false +} + +impl Puzzle { + fn new() -> Puzzle { + Puzzle { + wall: HashSet::new(), + units: vec![], + } + } + + /// Get the puzzle input. + fn configure(&mut self, path: &str) { + let data = std::fs::read_to_string(path).unwrap(); + + let grid = Grid::parse(&data); + + for (x, y, u) in grid.iter() { + match u { + &GOBLIN | &ELF => { + self.units.push(Unit { + x, + y, + hit_points: 200, + attack_power: 3, + race: *u, + }); + } + &WALL => { + self.wall.insert((x, y)); + } + _ => (), + } + } + } + + fn fight(&self, elf_attack_power: u32, elves_must_win: bool) -> Option { + let wall = &self.wall; + let mut units = self.units.clone(); + + for unit in &mut units { + if unit.race == ELF { + unit.attack_power = elf_attack_power; + } + } + + let mut round = 0; + + loop { + units.retain(|unit| unit.hit_points != 0); + + units.sort_unstable_by(Unit::attack_order); + + for u in 0..units.len() { + if units[u].hit_points == 0 { + continue; + } + + let target_mapping = parse_target(&units, units[u].race); + + if !target_mapping.contains_key(&(units[u].x, units[u].y)) { + // not in target range + + let target_adj: HashSet<(usize, usize)> = + target_mapping.keys().copied().collect(); + + if let Some(xy) = next_pos(u, &units, &target_adj, wall) { + units[u].x = xy.0; + units[u].y = xy.1; + } + } + + if target_mapping.contains_key(&(units[u].x, units[u].y)) { + // attack + let damage = units[u].attack_power; + + let target_indices = target_mapping.get(&(units[u].x, units[u].y)).unwrap(); + + let &target_index = target_indices + .iter() + .min_by(|&&a, &&b| Unit::min_hp_order(&units[a], &units[b])) + .unwrap(); + + let target = &mut units[target_index]; + + target.hit_points = target.hit_points.saturating_sub(damage); + + if elves_must_win && target.hit_points == 0 && target.race == ELF { + return None; + } + + if target.hit_points == 0 && !has_elves_and_goblins(&units) { + if u == units.len() - 1 { + round += 1; + } + + let sum_of_hp: u32 = units.iter().map(|unit| unit.hit_points).sum(); + + return Some(round * sum_of_hp); + } + } + } + + round += 1; + } + } + + /// Solve part one. + fn part1(&self) -> u32 { + self.fight(3, false).unwrap() + } + + /// Solve part two. + fn part2(&self) -> u32 { + for elf_attack_power in 4..100 { + if let Some(outcome) = self.fight(elf_attack_power, true) { + return outcome; + } + } + 0 + } +} + +fn main() { + let args = aoc::parse_args(); + let mut puzzle = Puzzle::new(); + puzzle.configure(args.path.as_str()); + + // print!("\x1b[H\x1b[2J\x1b]1337;ClearScrollback\x07"); + + println!("{}", puzzle.part1()); + println!("{}", puzzle.part2()); +} + +/// Test from puzzle input +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_6_1() { + let mut puzzle = Puzzle::new(); + puzzle.configure("sample_6.txt"); + assert_eq!(puzzle.part1(), 27730); + } + + #[test] + fn test_6_2() { + let mut puzzle = Puzzle::new(); + puzzle.configure("sample_6.txt"); + assert_eq!(puzzle.part2(), 4988); + } + + #[test] + fn test_7_1() { + let mut puzzle = Puzzle::new(); + puzzle.configure("sample_7.txt"); + assert_eq!(puzzle.part1(), 36334); + } + + // #[test] + // fn test_7_2() { + // let mut puzzle = Puzzle::new(); + // puzzle.configure("sample_7.txt"); + // assert_eq!(puzzle.part2(), 0); + // } + + #[test] + fn test_8_1() { + let mut puzzle = Puzzle::new(); + puzzle.configure("sample_8.txt"); + assert_eq!(puzzle.part1(), 39514); + } + + #[test] + fn test_8_2() { + let mut puzzle = Puzzle::new(); + puzzle.configure("sample_8.txt"); + assert_eq!(puzzle.part2(), 31284); + } + + #[test] + fn test_9_1() { + let mut puzzle = Puzzle::new(); + puzzle.configure("sample_9.txt"); + assert_eq!(puzzle.part1(), 27755); + } + + #[test] + fn test_9_2() { + let mut puzzle = Puzzle::new(); + puzzle.configure("sample_9.txt"); + assert_eq!(puzzle.part2(), 3478); + } + + #[test] + fn test_10_1() { + let mut puzzle = Puzzle::new(); + puzzle.configure("sample_10.txt"); + assert_eq!(puzzle.part1(), 28944); + } + + #[test] + fn test_10_2() { + let mut puzzle = Puzzle::new(); + puzzle.configure("sample_10.txt"); + assert_eq!(puzzle.part2(), 6474); + } + + #[test] + fn test_11_1() { + let mut puzzle = Puzzle::new(); + puzzle.configure("sample_11.txt"); + assert_eq!(puzzle.part1(), 18740); + } + + #[test] + fn test_11_2() { + let mut puzzle = Puzzle::new(); + puzzle.configure("sample_11.txt"); + assert_eq!(puzzle.part2(), 1140); + } +} diff --git a/2018/day15/sample_10.txt b/2018/day15/sample_10.txt new file mode 100644 index 00000000..2343d7bd --- /dev/null +++ b/2018/day15/sample_10.txt @@ -0,0 +1,7 @@ +####### +#.E...# +#.#..G# +#.###.# +#E#G#G# +#...#G# +####### diff --git a/2018/day15/sample_11.txt b/2018/day15/sample_11.txt new file mode 100644 index 00000000..95882b26 --- /dev/null +++ b/2018/day15/sample_11.txt @@ -0,0 +1,9 @@ +######### +#G......# +#.E.#...# +#..##..G# +#...##..# +#...#...# +#.G...G.# +#.....G.# +######### diff --git a/2018/day15/sample_6.txt b/2018/day15/sample_6.txt new file mode 100644 index 00000000..291d351e --- /dev/null +++ b/2018/day15/sample_6.txt @@ -0,0 +1,7 @@ +####### +#.G...# +#...EG# +#.#.#G# +#..G#E# +#.....# +####### diff --git a/2018/day15/sample_7.txt b/2018/day15/sample_7.txt new file mode 100644 index 00000000..ac399d60 --- /dev/null +++ b/2018/day15/sample_7.txt @@ -0,0 +1,7 @@ +####### +#G..#E# +#E#E.E# +#G.##.# +#...#E# +#...E.# +####### diff --git a/2018/day15/sample_8.txt b/2018/day15/sample_8.txt new file mode 100644 index 00000000..58f778d0 --- /dev/null +++ b/2018/day15/sample_8.txt @@ -0,0 +1,7 @@ +####### +#E..EG# +#.#G.E# +#E.##E# +#G..#.# +#..E#.# +####### diff --git a/2018/day15/sample_9.txt b/2018/day15/sample_9.txt new file mode 100644 index 00000000..6dc1c083 --- /dev/null +++ b/2018/day15/sample_9.txt @@ -0,0 +1,7 @@ +####### +#E.G#.# +#.#G..# +#G.#.G# +#G..#.# +#...E.# +####### diff --git a/README.md b/README.md index cbb50bed..227aacb2 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Year | Solutions [Advent of Code 2021](https://adventofcode.com/2021) | [Solutions](2021/README.md) | 50⭐ [Advent of Code 2020](https://adventofcode.com/2020) | [Solutions](2020/README.md) | 50⭐ [Advent of Code 2019](https://adventofcode.com/2019) | [Solutions](2019/README.md) | 50⭐ -[Advent of Code 2018](https://adventofcode.com/2018) | [Solutions](2018/README.md) | 16⭐ +[Advent of Code 2018](https://adventofcode.com/2018) | [Solutions](2018/README.md) | 50⭐ [Advent of Code 2017](https://adventofcode.com/2017) | [Solutions](2017/README.md) | 50⭐ [Advent of Code 2016](https://adventofcode.com/2016) | [Solutions](2016/README.md) | 50⭐ [Advent of Code 2015](https://adventofcode.com/2015) | [Solutions](2015/README.md) | 50⭐