use super::template::{DayTrait, ResultType}; use crate::common::{ direction::Direction, parser::{eol_terminated, extract_result, ignore, usize}, pos::Pos2D, }; use nom::{ branch::alt, character::complete::{char, multispace0}, combinator::value, error::Error, multi::many1, Err, IResult, Parser, }; use std::str::FromStr; use thiserror::Error; const DAY_NUMBER: usize = 22; pub struct Day; impl DayTrait for Day { fn get_day_number(&self) -> usize { DAY_NUMBER } fn part1(&self, lines: &str) -> anyhow::Result { let (world_map, instructions) = Day::parse(lines)?; let world = WrappingWorld::new(world_map); let walker = Walker::walk_all(&world, instructions)?; Ok(ResultType::Integer(walker.value())) } fn part2(&self, lines: &str) -> anyhow::Result { let (world_map, instructions) = Day::parse(lines)?; let world = CubeWorld::new(world_map); let walker = Walker::walk_all(&world, instructions)?; Ok(ResultType::Integer(walker.value())) } } impl Day { fn parse(input: &str) -> Result<(WorldMap, Vec), WorldError> { let (input, world) = WorldMap::parse(input)?; let input = ignore(multispace0)(input)?; let instructions = extract_result(Instruction::parse)(input)?; Ok((world, instructions)) } } #[derive(Debug, Error)] enum WorldError { #[error("Not a valid description: {0}")] ParsingError(String), #[error("No Starting Point found")] NoStartingPoint, } impl From>> for WorldError { fn from(error: Err>) -> Self { WorldError::ParsingError(error.to_string()) } } struct WorldMap { tiles: Vec>>, } impl FromStr for WorldMap { type Err = WorldError; fn from_str(input: &str) -> Result { Ok(extract_result(WorldMap::parse)(input)?) } } impl WorldMap { pub fn new(tiles: Vec>>) -> Self { Self { tiles } } fn parse(input: &str) -> IResult<&str, WorldMap> { let tile = alt(( value(Some(false), char('#')), value(Some(true), char('.')), value(None, char(' ')), )); let line = eol_terminated(many1(tile)); let mut lines = many1(line).map(|tiles| WorldMap::new(tiles)); lines.parse(input) } fn get_first(&self, pos: Pos2D, facing: Direction) -> Option> { match facing { Direction::East => self.tiles.get(pos.y()).and_then(|row| { row.iter() .position(|t| t.is_some()) .map(|x| Pos2D::new(x, pos.y())) }), Direction::West => self.tiles.get(pos.y()).and_then(|row| { row.iter() .rposition(|t| t.is_some()) .map(|x| Pos2D::new(x, pos.y())) }), Direction::South => self .tiles .iter() .position(|row| pos.x() < row.len() && row[pos.x()].is_some()) .map(|y| Pos2D::new(pos.x(), y)), Direction::North => self .tiles .iter() .rposition(|row| pos.x() < row.len() && row[pos.x()].is_some()) .map(|y| Pos2D::new(pos.x(), y)), } } fn get_tile(&self, pos: Pos2D) -> Option { self.tiles .get(pos.y()) .and_then(|row| row.get(pos.x())) .copied() .flatten() } } trait World { fn get_map(&self) -> &WorldMap; fn wrap(&self, pos: Pos2D, facing: Direction) -> Option<(Pos2D, Direction)>; fn get_start(&self, pos: Pos2D, facing: Direction) -> Option> { self.get_map().get_first(pos, facing) } fn step(&self, pos: Pos2D, facing: Direction) -> Option<(Pos2D, Direction)> { let Some(pos) = pos.check_add(facing) else { return self.wrap(pos, facing); }; match self.get_map().get_tile(pos) { Some(true) => Some((pos, facing)), Some(false) => None, None => self.wrap(pos, facing), } } } struct WrappingWorld { map: WorldMap, } impl WrappingWorld { fn new(world_map: WorldMap) -> Self { WrappingWorld { map: world_map } } } impl World for WrappingWorld { fn get_map(&self) -> &WorldMap { &self.map } fn wrap(&self, pos: Pos2D, facing: Direction) -> Option<(Pos2D, Direction)> { if let Some(pos) = self.map.get_first(pos, facing) { match self.map.get_tile(pos) { Some(true) => Some((pos, facing)), Some(false) => None, None => unreachable!(), } } else { unreachable!(); } } } struct CubeWorld { map: WorldMap, } impl CubeWorld { fn new(world_map: WorldMap) -> Self { CubeWorld { map: world_map } } } impl World for CubeWorld { fn get_map(&self) -> &WorldMap { &self.map } fn wrap(&self, pos: Pos2D, facing: Direction) -> Option<(Pos2D, Direction)> { if let Some(pos) = self.map.get_first(pos, facing) { match self.map.get_tile(pos) { Some(true) => Some((pos, facing)), Some(false) => None, None => unreachable!(), } } else { unreachable!(); } } } #[derive(Debug, Clone)] enum Instruction { Walk(usize), Right, Left, } impl Instruction { fn parse(input: &str) -> IResult<&str, Vec> { many1(alt(( usize.map(|v| Instruction::Walk(v)), value(Instruction::Right, char('R')), value(Instruction::Left, char('L')), ))) .parse(input) } } struct Walker { pos: Pos2D, facing: Direction, } impl Walker { fn new(world: &W) -> Result { let facing = Direction::East; if let Some(pos) = world.get_start(Pos2D::new(0, 0), facing) { Ok(Walker { pos, facing }) } else { Err(WorldError::NoStartingPoint) } } fn value(&self) -> i64 { (self.pos.y() + 1) as i64 * 1000 + (self.pos.x() + 1) as i64 * 4 + Walker::face_value(self.facing) } fn face_value(facing: Direction) -> i64 { match facing { Direction::East => 0, Direction::North => 3, Direction::West => 2, Direction::South => 1, } } fn act(mut self, world: &W, instruction: Instruction) -> Self { match instruction { Instruction::Right => self.facing = self.facing.turn_right(), Instruction::Left => self.facing = self.facing.turn_left(), Instruction::Walk(steps) => { for _ in 0..steps { let Some((next_pos, next_facing)) = world.step(self.pos, self.facing) else { break; }; self.pos = next_pos; self.facing = next_facing; } } } self } pub fn walk_all( world: &W, instructions: Vec, ) -> Result { let mut walker = Walker::new(world)?; for instruction in instructions { walker = walker.act(world, instruction); } Ok(walker) } } #[cfg(test)] mod test { use super::*; use crate::common::file::read_string; use anyhow::Result; #[test] fn test_part1() -> Result<()> { let day = Day {}; let lines = read_string(day.get_day_number(), "example01.txt")?; let expected = ResultType::Integer(6032); let result = day.part1(&lines)?; assert_eq!(result, expected); Ok(()) } #[test] fn test_part2() -> Result<()> { let day = Day {}; let lines = read_string(day.get_day_number(), "example01.txt")?; let expected = ResultType::Nothing; let result = day.part2(&lines)?; assert_eq!(result, expected); Ok(()) } #[test] fn parse() -> Result<()> { let day = Day {}; let lines = read_string(day.get_day_number(), "example01.txt")?; let (world, instructions) = Day::parse(&lines)?; assert_eq!(world.tiles.len(), 12); assert_eq!(instructions.len(), 13); Ok(()) } #[test] fn walk() -> Result<()> { let day = Day {}; let lines = read_string(day.get_day_number(), "example01.txt")?; let (world_map, _) = Day::parse(&lines)?; let world = WrappingWorld::new(world_map); let mut walker = Walker::new(&world)?; assert_eq!(walker.pos, Pos2D::new(8, 0)); walker = walker.act(&world, Instruction::Walk(10)); assert_eq!(walker.pos, Pos2D::new(10, 0)); walker = walker.act(&world, Instruction::Left); walker = walker.act(&world, Instruction::Walk(2)); assert_eq!(walker.pos, Pos2D::new(10, 10)); Ok(()) } #[test] fn walk_all() -> Result<()> { let day = Day {}; let lines = read_string(day.get_day_number(), "example01.txt")?; let (world_map, instructions) = Day::parse(&lines)?; let world = WrappingWorld::new(world_map); let walker = Walker::walk_all(&world, instructions)?; assert_eq!(walker.pos, Pos2D::new(7, 5)); assert_eq!(walker.facing, Direction::East); assert_eq!(walker.value(), 6032); Ok(()) } }