AoC2020/src/day24.rs

183 lines
5.2 KiB
Rust

use std::str::FromStr;
use std::collections::{HashMap, VecDeque, HashSet};
use std::io::BufRead;
#[derive(Debug, PartialEq)]
enum TileState {
Black,
White,
}
#[derive(Debug, PartialEq)]
enum HexDirection {
West,
NorthWest,
NorthEast,
East,
SouthEast,
SouthWest,
}
impl HexDirection {
const ALL: &'static [HexDirection] = &[
HexDirection::West,
HexDirection::NorthWest,
HexDirection::NorthEast,
HexDirection::East,
HexDirection::SouthEast,
HexDirection::SouthWest,
];
}
impl FromStr for HexDirection {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"w" => Ok(HexDirection::West),
"nw" => Ok(HexDirection::NorthWest),
"ne" => Ok(HexDirection::NorthEast),
"e" => Ok(HexDirection::East),
"se" => Ok(HexDirection::SouthEast),
"sw" => Ok(HexDirection::SouthWest),
_ => Err(())
}
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
struct HexCoord {
q: isize,
r: isize,
}
// More info: https://www.redblobgames.com/grids/hexagons/#coordinates-axial
impl HexCoord {
fn origin() -> HexCoord {
HexCoord {
r: 0,
q: 0,
}
}
fn move_in_direction(&self, direction: &HexDirection) -> HexCoord {
match direction {
HexDirection::West => HexCoord { r: self.r, q: self.q - 1 },
HexDirection::NorthWest => HexCoord { r: self.r - 1, q: self.q },
HexDirection::NorthEast => HexCoord { r: self.r - 1, q: self.q + 1},
HexDirection::East => HexCoord { r: self.r, q: self.q + 1},
HexDirection::SouthEast => HexCoord { r: self.r + 1, q: self.q },
HexDirection::SouthWest => HexCoord { r: self.r + 1, q: self.q -1 },
}
}
fn follow_path(self, mut directions: VecDeque<HexDirection>) -> HexCoord {
if directions.len() == 0 {
self
} else {
let next = self.move_in_direction(&directions.pop_front().unwrap());
HexCoord::follow_path(next, directions)
}
}
}
fn parse_line(s: String) -> VecDeque<HexDirection> {
let mut vertical = String::new();
let mut directions = VecDeque::new();
for c in s.chars() {
match c {
'n' | 's' => vertical = c.to_string(),
'w' | 'e' => {
directions.push_back(HexDirection::from_str(&format!("{}{}", vertical, c)).unwrap());
vertical = String::new();
},
_ => unreachable!()
}
}
directions
}
fn tick(state: HashSet<HexCoord>) -> HashSet<HexCoord> {
let mut neighbours: HashMap<HexCoord, usize> = HashMap::new();
for coord in &state {
for direction in HexDirection::ALL {
let nb = neighbours.entry(coord.move_in_direction(direction)).or_insert(0);
*nb += 1;
}
}
neighbours.iter()
.filter(|(coord, nb_count)| (state.contains(coord) && **nb_count <= 2) || (!state.contains(coord) && **nb_count == 2))
.map(|(coord, _)| coord.clone())
.collect()
}
fn place_tiles(mut directions_lines: Vec<VecDeque<HexDirection>>) -> HashSet<HexCoord> {
let mut tiles: HashMap<HexCoord, TileState> = HashMap::new();
for line in directions_lines.drain(..) {
let end = HexCoord::origin().follow_path(line);
let tile = tiles.get(&end).unwrap_or(&TileState::White);
if tile == &TileState::White {
tiles.insert(end, TileState::Black);
} else {
tiles.insert(end, TileState::White);
}
}
tiles.iter().filter(|(_, t)| **t == TileState::Black).map(|(c, _)| c.clone()).collect()
}
pub fn part1<F: BufRead> (input: F) {
let lines = input.lines().map(|line| parse_line(line.unwrap())).collect::<Vec<_>>();
println!("{}", place_tiles(lines).len())
}
pub fn part2<F: BufRead> (input: F) {
let lines = input.lines().map(|line| parse_line(line.unwrap())).collect::<Vec<_>>();
let mut state = place_tiles(lines);
for _ in 0..100 {
state = tick(state);
}
println!("{}", state.len())
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_parse_line() {
let line = "nwwswee".to_string();
let expected: VecDeque<_> = vec![
HexDirection::NorthWest,
HexDirection::West,
HexDirection::SouthWest,
HexDirection::East,
HexDirection::East
].into();
assert_eq!(expected, parse_line(line))
}
#[test]
fn test_follow_path() {
let mut test_cases: Vec<(HexCoord, VecDeque<_>)> = vec![
(HexCoord { r: 0, q: 0 }, vec![
HexDirection::NorthWest,
HexDirection::West,
HexDirection::SouthWest,
HexDirection::East,
HexDirection::East
].into()),
(HexCoord { r: 1, q: 0}, vec![
HexDirection::East,
HexDirection::SouthEast,
HexDirection::West,
].into())
];
for (expected, path) in test_cases.drain(..) {
assert_eq!(expected, HexCoord::origin().follow_path(path));
}
}
}