use itertools::Itertools; use nom::branch::alt; use nom::bytes::complete::tag; use nom::character::complete::{digit1, newline, one_of}; use nom::combinator::map; use nom::multi::{many1, separated_list1}; use nom::sequence::separated_pair; use nom::IResult; pub fn process_part_1(input: &str) -> usize { let (grid, instructions) = parse_input(input).unwrap().1; let mut player = Player { x: grid[0] .iter() .position(|t| matches!(t, Tile::Free)) .unwrap(), y: 0, dir: Dir::R, }; for i in instructions { player.walk1(&i, &grid); } 1000 * (player.y + 1) + (4 * (player.x + 1)) + isize::from(&player.dir) as usize } pub fn process_part_2(input: &str, cube_size: usize) -> usize { let (grid, instructions) = parse_input(input).unwrap().1; let mut player = Player { x: grid[0] .iter() .position(|t| matches!(t, Tile::Free)) .unwrap(), y: 0, dir: Dir::R, }; for i in instructions { player.walk2(&i, &grid, cube_size); } 1000 * (player.y + 1) + (4 * (player.x + 1)) + isize::from(&player.dir) as usize } #[derive(Debug)] struct Player { x: usize, y: usize, dir: Dir, } impl Player { fn walk1(&mut self, instr: &Instruction, grid: &[Vec]) { match instr { Instruction::Left => { self.dir = (isize::from(&self.dir) - 1).rem_euclid(4).into(); } Instruction::Right => { self.dir = (isize::from(&self.dir) + 1).rem_euclid(4).into(); } Instruction::Walk(dist) => { for _ in 0..*dist { let (next_x, next_y, next_tile) = match &self.dir { Dir::R => { let row = &grid[self.y]; let (next_x, next_tile) = row .iter() .enumerate() .cycle() .skip(self.x + 1) .find(|(_, t)| !matches!(t, Tile::None)) .unwrap(); (next_x, self.y, next_tile) } Dir::D => { let col = grid.iter().map_while(|row| row.get(self.x)); let (next_y, next_tile) = col .enumerate() .cycle() .skip(self.y + 1) .find(|(_, t)| !matches!(t, Tile::None)) .unwrap(); (self.x, next_y, next_tile) } Dir::L => { let row = &grid[self.y]; let (next_x, next_tile) = row .iter() .enumerate() .rev() .cycle() .skip(row.len() - self.x) .find(|(_, t)| !matches!(t, Tile::None)) .unwrap(); (next_x, self.y, next_tile) } Dir::U => { let col = grid.iter().map_while(|row| row.get(self.x)).collect_vec(); let (next_y, next_tile) = col .iter() .enumerate() .rev() .cycle() .skip(col.len() - self.y) .find(|(_, t)| !matches!(t, Tile::None)) .unwrap(); (self.x, next_y, *next_tile) } }; match next_tile { Tile::Free => (self.x, self.y) = (next_x, next_y), Tile::Wall => break, Tile::None => unreachable!(), } } } } } fn walk2(&mut self, instr: &Instruction, grid: &[Vec], size: usize) { match instr { Instruction::Left => { self.dir = (isize::from(&self.dir) - 1).rem_euclid(4).into(); } Instruction::Right => { self.dir = (isize::from(&self.dir) + 1).rem_euclid(4).into(); } Instruction::Walk(dist) => { for _ in 0..*dist { let (next_x, next_y, next_dir) = self.next_coord(size); let next_tile = &grid[next_y][next_x]; match next_tile { Tile::Free => { self.x = next_x; self.y = next_y; self.dir = next_dir } Tile::Wall => break, Tile::None => unreachable!(), } } } } } fn next_coord(&self, size: usize) -> (usize, usize, Dir) { let face = get_face(self.x, self.y, size); let (xl, yl) = global_to_local(self.x, self.y, size); match face { 1 => match self.dir { Dir::R => (self.x + 1, self.y, Dir::R), Dir::L => { if xl == 0 { let (nx, ny) = local_to_global(xl, size - yl - 1, 4, size); (nx, ny, Dir::R) } else { (self.x - 1, self.y, Dir::L) } } Dir::U => { if yl == 0 { let (nx, ny) = local_to_global(yl, xl, 6, size); (nx, ny, Dir::R) } else { (self.x, self.y - 1, Dir::U) } } Dir::D => (self.x, self.y + 1, Dir::D), }, 2 => match self.dir { Dir::R => { if xl == size - 1 { let (nx, ny) = local_to_global(xl, size - yl - 1, 5, size); (nx, ny, Dir::L) } else { (self.x + 1, self.y, Dir::R) } } Dir::L => (self.x - 1, self.y, Dir::L), Dir::U => { if yl == 0 { let (nx, ny) = local_to_global(xl, size - 1, 6, size); (nx, ny, Dir::U) } else { (self.x, self.y - 1, Dir::U) } } Dir::D => { if yl == size - 1 { let (nx, ny) = local_to_global(yl, xl, 3, size); (nx, ny, Dir::L) } else { (self.x, self.y + 1, Dir::D) } } }, 3 => match self.dir { Dir::R => { if xl == size - 1 { let (nx, ny) = local_to_global(yl, xl, 2, size); (nx, ny, Dir::U) } else { (self.x + 1, self.y, Dir::R) } } Dir::L => { if xl == 0 { let (nx, ny) = local_to_global(yl, xl, 4, size); (nx, ny, Dir::D) } else { (self.x - 1, self.y, Dir::L) } } Dir::U => (self.x, self.y - 1, Dir::U), Dir::D => (self.x, self.y + 1, Dir::D), }, 4 => match self.dir { Dir::R => (self.x + 1, self.y, Dir::R), Dir::L => { if xl == 0 { let (nx, ny) = local_to_global(xl, size - yl - 1, 1, size); (nx, ny, Dir::R) } else { (self.x - 1, self.y, Dir::L) } } Dir::U => { if yl == 0 { let (nx, ny) = local_to_global(yl, xl, 3, size); (nx, ny, Dir::R) } else { (self.x, self.y - 1, Dir::U) } } Dir::D => (self.x, self.y + 1, Dir::D), }, 5 => match self.dir { Dir::R => { if xl == size - 1 { let (nx, ny) = local_to_global(xl, size - yl - 1, 2, size); (nx, ny, Dir::L) } else { (self.x + 1, self.y, Dir::R) } } Dir::L => (self.x - 1, self.y, Dir::L), Dir::U => (self.x, self.y - 1, Dir::U), Dir::D => { if yl == size - 1 { let (nx, ny) = local_to_global(yl, xl, 6, size); (nx, ny, Dir::L) } else { (self.x, self.y + 1, Dir::D) } } }, _ => match self.dir { Dir::R => { if xl == size - 1 { let (nx, ny) = local_to_global(yl, xl, 5, size); (nx, ny, Dir::U) } else { (self.x + 1, self.y, Dir::R) } } Dir::L => { if xl == 0 { let (nx, ny) = local_to_global(yl, xl, 1, size); (nx, ny, Dir::D) } else { (self.x - 1, self.y, Dir::L) } } Dir::U => (self.x, self.y - 1, Dir::U), Dir::D => { if yl == size - 1 { let (nx, ny) = local_to_global(xl, 0, 2, size); (nx, ny, Dir::D) } else { (self.x, self.y + 1, Dir::D) } } }, } } } // Look, i'm just hardcoding it // _12 // _3_ // 45_ // 6__ fn get_face(x: usize, y: usize, size: usize) -> usize { if y < size { if x < 2 * size { return 1; } return 2; } if y < 2 * size { return 3; } if y < 3 * size { if x < size { return 4; } return 5; } 6 } fn global_to_local(x: usize, y: usize, size: usize) -> (usize, usize) { let face = get_face(x, y, size); match face { 1 => (x - size, y), 2 => (x - 2 * size, y), 3 => (x - size, y - size), 4 => (x, y - 2 * size), 5 => (x - size, y - 2 * size), 6 => (x, y - 3 * size), _ => unreachable!(), } } fn local_to_global(x: usize, y: usize, face: usize, size: usize) -> (usize, usize) { match face { 1 => (x + size, y), 2 => (x + 2 * size, y), 3 => (x + size, y + size), 4 => (x, y + 2 * size), 5 => (x + size, y + 2 * size), 6 => (x, y + 3 * size), _ => unreachable!(), } } #[derive(Debug)] pub enum Tile { None, Free, Wall, } #[derive(Debug)] pub enum Instruction { Walk(usize), Left, Right, } #[derive(Debug)] pub enum Dir { R, D, L, U, } impl From<&Dir> for isize { fn from(value: &Dir) -> Self { match value { Dir::R => 0, Dir::D => 1, Dir::L => 2, Dir::U => 3, } } } impl From for Dir { fn from(value: isize) -> Self { match value { 0 => Dir::R, 1 => Dir::D, 2 => Dir::L, 3 => Dir::U, _ => panic!("invalid direction"), } } } fn parse_input(input: &str) -> IResult<&str, (Vec>, Vec)> { separated_pair(parse_grid, tag("\n\n"), parse_instructions)(input) } fn parse_grid(input: &str) -> IResult<&str, Vec>> { separated_list1( newline, many1(map(one_of(" .#"), |c| match c { ' ' => Tile::None, '.' => Tile::Free, '#' => Tile::Wall, _ => unreachable!(), })), )(input) } fn parse_instructions(input: &str) -> IResult<&str, Vec> { many1(map(alt((digit1, tag("R"), tag("L"))), |c| match c { "R" => Instruction::Right, "L" => Instruction::Left, dist => Instruction::Walk(dist.parse().unwrap()), }))(input) } #[cfg(test)] mod tests { use super::*; const INPUT: &str = " ...# .#.. #... .... ...#.......# ........#... ..#....#.... ..........#. ...#.... .....#.. .#...... ......#. 10R5L5R10L4R5L5"; #[test] fn day1() { assert_eq!(process_part_1(INPUT), 6032); } #[test] fn day2() { // no test, because the test folding is different than the input folding // SO FUCK THAT GARBAGE // assert_eq!(process_part_2(INPUT, 4), 5031); assert_eq!(0, 0); } }