--- /dev/null
+use std::{collections::HashMap, io::BufRead};
+
+use nalgebra::DMatrix;
+
+type Maze = DMatrix<char>;
+type Pos = (usize, usize);
+
+#[repr(u32)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
+pub enum Dir {
+ North = 0,
+ West = 1,
+ South = 2,
+ Est = 3,
+}
+
+pub fn read(reader: &mut dyn BufRead) -> (Maze, Pos, Dir) {
+ let mut reinder_pos = Pos::default();
+ let mut maze: Vec<char> = Vec::new();
+ let mut nb_rows = 0;
+ let mut line_skipped = 0;
+
+ for (i, l) in reader.lines().enumerate() {
+ let l = l.as_ref().unwrap().trim();
+ if l.is_empty() {
+ line_skipped += 1;
+ continue;
+ }
+ nb_rows += 1;
+ for (j, c) in l.chars().enumerate() {
+ if c == 'S' {
+ reinder_pos = (i - line_skipped, j);
+ maze.push('.');
+ } else {
+ maze.push(c);
+ }
+ }
+ }
+
+ (
+ Maze::from_row_slice(nb_rows, maze.len() / nb_rows, &maze),
+ reinder_pos,
+ Dir::Est,
+ )
+}
+
+fn neighbours(pos: &Pos) -> [(Pos, Dir); 4] {
+ let (i, j) = *pos;
+ [
+ ((i - 1, j), Dir::North),
+ ((i, j - 1), Dir::West),
+ ((i + 1, j), Dir::South),
+ ((i, j + 1), Dir::Est),
+ ]
+}
+
+fn turn_cost(d1: &Dir, d2: &Dir) -> i64 {
+ let diff = (*d1 as u32).abs_diff(*d2 as u32) as i64;
+ 1000 * if diff == 3 { 1 } else { diff }
+}
+
+pub fn best_score_and_nb_of_tiles_best_spot(
+ maze: &Maze,
+ start_pos: &Pos,
+ start_direction: &Dir,
+) -> (i64, i64) {
+ let mut scores: Vec<DMatrix<i64>> = std::iter::repeat_n(
+ DMatrix::from_element(maze.nrows(), maze.ncols(), i64::MAX),
+ 4,
+ )
+ .collect();
+ scores[*start_direction as usize][(start_pos.0, start_pos.1)] = 0;
+
+ let mut heads = vec![(*start_pos, *start_direction)];
+ let mut best_score = i64::MAX;
+ let mut pos_end = (0, 0);
+
+ while !heads.is_empty() {
+ let mut new_heads = vec![];
+ for (pos, dir) in heads.iter() {
+ let current_score = scores[*dir as usize][*pos];
+ if maze[*pos] == 'E' && current_score < best_score {
+ best_score = current_score;
+ pos_end = *pos;
+ } else {
+ for (pos_neighbour, dir_neighbour) in neighbours(pos) {
+ if maze[pos_neighbour] != '#' {
+ let score = current_score + turn_cost(dir, &dir_neighbour) + 1;
+ if score < scores[dir_neighbour as usize][pos_neighbour] {
+ scores[dir_neighbour as usize][pos_neighbour] = score;
+ new_heads.push((pos_neighbour, dir_neighbour));
+ }
+ }
+ }
+ }
+ }
+ heads = new_heads;
+ }
+
+ // Backtracking to find the number of tiles with the best spot.
+ let mut visited: HashMap<Pos, [bool; 4]> = HashMap::new();
+
+ for dir in [Dir::Est, Dir::North, Dir::South, Dir::West] {
+ if scores[dir as usize][pos_end] == best_score {
+ heads.push((pos_end, dir));
+ visited.entry(pos_end).or_default()[dir as usize] = true;
+ }
+ }
+
+ while !heads.is_empty() {
+ let mut new_heads = vec![];
+ for (pos, dir) in heads.iter() {
+ let (i, j) = *pos;
+ let pos_neighbour = match dir {
+ Dir::North => (i + 1, j),
+ Dir::West => (i, j + 1),
+ Dir::South => (i - 1, j),
+ Dir::Est => (i, j - 1),
+ };
+
+ for dir_neighbour in [Dir::Est, Dir::North, Dir::South, Dir::West] {
+ if scores[*dir as usize][*pos] - 1 - turn_cost(&dir_neighbour, dir)
+ == scores[dir_neighbour as usize][pos_neighbour]
+ {
+ let entry = visited.entry(pos_neighbour).or_default();
+ if !entry[dir_neighbour as usize] {
+ entry[dir_neighbour as usize] = true;
+ new_heads.push((pos_neighbour, dir_neighbour));
+ }
+ }
+ }
+ }
+ heads = new_heads;
+ }
+
+ (best_score, visited.len() as i64)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ static MAP_AND_MOVEMENTS_SIMPLE: &str = "
+#####
+#E..#
+###.#
+#S..#
+#####
+";
+
+ static MAP_AND_MOVEMENTS_A: &str = "
+###############
+#.......#....E#
+#.#.###.#.###.#
+#.....#.#...#.#
+#.###.#####.#.#
+#.#.#.......#.#
+#.#.#####.###.#
+#...........#.#
+###.#.#####.#.#
+#...#.....#.#.#
+#.#.#.###.#.#.#
+#.....#...#.#.#
+#.###.#.#.#.#.#
+#S..#.....#...#
+###############
+";
+
+ static MAP_AND_MOVEMENTS_B: &str = "
+#################
+#...#...#...#..E#
+#.#.#.#.#.#.#.#.#
+#.#.#.#...#...#.#
+#.#.#.#.###.#.#.#
+#...#.#.#.....#.#
+#.#.#.#.#.#####.#
+#.#...#.#.#.....#
+#.#.#####.#.###.#
+#.#.#.......#...#
+#.#.###.#####.###
+#.#.#...#.....#.#
+#.#.#.#####.###.#
+#.#.#.........#.#
+#.#.#.#########.#
+#S#.............#
+#################
+";
+
+ #[test]
+ fn part1() {
+ let (maze, start, direction) = read(&mut MAP_AND_MOVEMENTS_SIMPLE.as_bytes());
+ assert_eq!(
+ best_score_and_nb_of_tiles_best_spot(&maze, &start, &direction).0,
+ 2006
+ );
+
+ let (maze, start, direction) = read(&mut MAP_AND_MOVEMENTS_A.as_bytes());
+ assert_eq!(
+ best_score_and_nb_of_tiles_best_spot(&maze, &start, &direction).0,
+ 7036
+ );
+
+ let (maze, start, direction) = read(&mut MAP_AND_MOVEMENTS_B.as_bytes());
+ assert_eq!(
+ best_score_and_nb_of_tiles_best_spot(&maze, &start, &direction).0,
+ 11048
+ );
+ }
+
+ static MAP_AND_MOVEMENTS_MULTIPLE_PATHS_1: &str = "
+######
+##..E#
+#S..##
+######
+";
+
+ static MAP_AND_MOVEMENTS_MULTIPLE_PATHS_2: &str = "
+######
+#....#
+#S##E#
+#....#
+######
+";
+
+ #[test]
+ fn part2() {
+ let (maze, start, direction) = read(&mut MAP_AND_MOVEMENTS_MULTIPLE_PATHS_1.as_bytes());
+ assert_eq!(
+ best_score_and_nb_of_tiles_best_spot(&maze, &start, &direction).1,
+ 6
+ );
+
+ let (maze, start, direction) = read(&mut MAP_AND_MOVEMENTS_MULTIPLE_PATHS_2.as_bytes());
+ assert_eq!(
+ best_score_and_nb_of_tiles_best_spot(&maze, &start, &direction).1,
+ 10
+ );
+
+ let (maze, start, direction) = read(&mut MAP_AND_MOVEMENTS_A.as_bytes());
+ assert_eq!(
+ best_score_and_nb_of_tiles_best_spot(&maze, &start, &direction).1,
+ 45
+ );
+
+ let (maze, start, direction) = read(&mut MAP_AND_MOVEMENTS_B.as_bytes());
+ assert_eq!(
+ best_score_and_nb_of_tiles_best_spot(&maze, &start, &direction).1,
+ 64
+ );
+ }
+}