diff options
Diffstat (limited to '2022/24/common.rs')
| -rw-r--r-- | 2022/24/common.rs | 204 |
1 files changed, 204 insertions, 0 deletions
diff --git a/2022/24/common.rs b/2022/24/common.rs new file mode 100644 index 0000000..0af4789 --- /dev/null +++ b/2022/24/common.rs | |||
| @@ -0,0 +1,204 @@ | |||
| 1 | // This code is more complicated than it should be. The reason is that | ||
| 2 | // for part 1 I decided to ignore the start and finish position, and not | ||
| 3 | // to treat them as other cells. This way I could get away with not | ||
| 4 | // considering wall cells '#' at all. | ||
| 5 | // But then for part 2 it was more convenient to include them, so I | ||
| 6 | // added an enum Position {} that treats start and finish as special | ||
| 7 | // cases. This is quite messy. | ||
| 8 | |||
| 9 | use std::collections::{VecDeque, HashSet}; | ||
| 10 | |||
| 11 | #[derive(PartialEq, Debug)] | ||
| 12 | pub enum Direction { Up, Down, Left, Right, None } | ||
| 13 | |||
| 14 | impl Direction { | ||
| 15 | pub fn from_byte(b: u8) -> Direction { | ||
| 16 | match b { | ||
| 17 | b'^' => Direction::Up, | ||
| 18 | b'v' => Direction::Down, | ||
| 19 | b'>' => Direction::Right, | ||
| 20 | b'<' => Direction::Left, | ||
| 21 | b'.' => Direction::None, | ||
| 22 | x => panic!("unexpected char '{}'", x as char) | ||
| 23 | } | ||
| 24 | } | ||
| 25 | } | ||
| 26 | |||
| 27 | #[derive(Hash, Eq, PartialEq, Copy, Clone, Debug)] | ||
| 28 | pub enum Position { | ||
| 29 | Start, | ||
| 30 | Finish, | ||
| 31 | Other { i: usize, j: usize } | ||
| 32 | } | ||
| 33 | |||
| 34 | impl Position { | ||
| 35 | pub fn dist(&self, other: Position) -> usize { | ||
| 36 | match (*self, other) { | ||
| 37 | (Position::Start, Position::Start) | | ||
| 38 | (Position::Finish, Position::Finish) => | ||
| 39 | 0, | ||
| 40 | (Position::Start, Position::Finish) | | ||
| 41 | (Position::Finish, Position::Start) => | ||
| 42 | usize::MAX, // Not technically correct, but it works | ||
| 43 | (Position::Start | Position::Finish, Position::Other { i, j }) | | ||
| 44 | (Position::Other { i, j }, Position::Start | Position::Finish) => | ||
| 45 | i + j, | ||
| 46 | (Position::Other { i: i1, j: j1 }, | ||
| 47 | Position::Other { i: i2, j: j2 }) => { | ||
| 48 | let i = (i1 as i64 - i2 as i64).abs() as usize; | ||
| 49 | let j = (j1 as i64 - j2 as i64).abs() as usize; | ||
| 50 | i + j | ||
| 51 | } | ||
| 52 | } | ||
| 53 | } | ||
| 54 | } | ||
| 55 | |||
| 56 | pub struct Map { | ||
| 57 | pub tiles: Vec<Vec<Direction>>, | ||
| 58 | pub imax: usize, | ||
| 59 | pub jmax: usize | ||
| 60 | } | ||
| 61 | |||
| 62 | #[derive(Hash, Eq, PartialEq, Copy, Clone, Debug)] | ||
| 63 | struct Node { | ||
| 64 | p: Position, | ||
| 65 | t: usize | ||
| 66 | } | ||
| 67 | |||
| 68 | impl Map { | ||
| 69 | fn submod(i: usize, t: usize, m: usize) -> usize { | ||
| 70 | let maybeneg = ((i as i64) - (t as i64)) % m as i64; | ||
| 71 | (maybeneg as usize + m) % m | ||
| 72 | } | ||
| 73 | |||
| 74 | pub fn is_busy(&self, p: Position, t: usize) -> bool { | ||
| 75 | match p { | ||
| 76 | Position::Start | Position::Finish => false, | ||
| 77 | Position::Other { i, j } => { | ||
| 78 | let iup = Self::submod(i, t, self.imax); | ||
| 79 | let idown = (i + t) % self.imax; | ||
| 80 | let jleft = Map::submod(j, t, self.jmax); | ||
| 81 | let jright = (j + t) % self.jmax; | ||
| 82 | |||
| 83 | self.tiles[iup][j] == Direction::Down || | ||
| 84 | self.tiles[idown][j] == Direction::Up || | ||
| 85 | self.tiles[i][jleft] == Direction::Right || | ||
| 86 | self.tiles[i][jright] == Direction::Left | ||
| 87 | } | ||
| 88 | } | ||
| 89 | } | ||
| 90 | |||
| 91 | #[allow(dead_code)] | ||
| 92 | pub fn print(&self, t: usize) { | ||
| 93 | print!("#."); | ||
| 94 | for _ in 0..self.jmax { print!("#"); } | ||
| 95 | println!(); | ||
| 96 | |||
| 97 | for i in 0..self.imax { | ||
| 98 | print!("#"); | ||
| 99 | for j in 0..self.jmax { | ||
| 100 | let p = Position::Other { i, j }; | ||
| 101 | print!("{}", if self.is_busy(p, t) { '*' } else { '.' }); | ||
| 102 | } | ||
| 103 | println!("#"); | ||
| 104 | } | ||
| 105 | |||
| 106 | for _ in 0..self.jmax { print!("#"); } | ||
| 107 | print!(".#"); | ||
| 108 | println!(); | ||
| 109 | } | ||
| 110 | |||
| 111 | fn mv(&self, p: Position, d: Direction) -> Option<Position> { | ||
| 112 | if d == Direction::None { return Some(p); } | ||
| 113 | match p { | ||
| 114 | Position::Start => if d == Direction::Down { | ||
| 115 | Some(Position::Other { i: 0, j: 0 }) | ||
| 116 | } else { None }, | ||
| 117 | Position::Finish => if d == Direction::Up { | ||
| 118 | Some(Position::Other { i: self.imax-1, j: self.jmax-1 }) | ||
| 119 | } else { None }, | ||
| 120 | Position::Other { i, j } => match d { | ||
| 121 | Direction::Up => if i > 0 { | ||
| 122 | Some(Position::Other { i: i-1, j }) | ||
| 123 | } else { | ||
| 124 | if i == 0 && j == 0 { | ||
| 125 | Some(Position::Start) | ||
| 126 | } else { None } | ||
| 127 | }, | ||
| 128 | Direction::Down => if i < self.imax-1 { | ||
| 129 | Some(Position::Other { i: i+1, j }) | ||
| 130 | } else { | ||
| 131 | if i == self.imax-1 && j == self.jmax-1 { | ||
| 132 | Some(Position::Finish) | ||
| 133 | } else { None } | ||
| 134 | }, | ||
| 135 | Direction::Left => if j > 0 { | ||
| 136 | Some(Position::Other { i, j: j-1 }) | ||
| 137 | } else { None }, | ||
| 138 | Direction::Right => if j < self.jmax-1 { | ||
| 139 | Some(Position::Other { i, j: j+1 }) | ||
| 140 | } else { None }, | ||
| 141 | Direction::None => panic!("stupid rust") | ||
| 142 | } | ||
| 143 | } | ||
| 144 | } | ||
| 145 | |||
| 146 | fn maybe_push(&self, x: Node, d: Direction, v: &mut HashSet<Node>) { | ||
| 147 | if let Some(p) = self.mv(x.p, d) { | ||
| 148 | if !self.is_busy(p, x.t+1) { | ||
| 149 | v.insert(Node { p, t: x.t+1 }); | ||
| 150 | } | ||
| 151 | } | ||
| 152 | } | ||
| 153 | |||
| 154 | pub fn shortest_path( | ||
| 155 | &self, | ||
| 156 | start: Position, | ||
| 157 | finish: Position, | ||
| 158 | t0: usize | ||
| 159 | ) -> usize { | ||
| 160 | let mut vnext = HashSet::<Node>::new(); | ||
| 161 | vnext.insert(Node { p: start, t: t0 }); | ||
| 162 | while !vnext.is_empty() { | ||
| 163 | let mut v = vnext.clone().into_iter().collect::<Vec<_>>(); | ||
| 164 | // Heuristic: explore first the positions closer to the target | ||
| 165 | v.sort_by(|a, b| a.p.dist(finish).cmp(&(b.p.dist(finish)))); | ||
| 166 | let mut v = VecDeque::<Node>::from(v); | ||
| 167 | vnext.clear(); | ||
| 168 | while !v.is_empty() { | ||
| 169 | let x = v.pop_front().unwrap(); | ||
| 170 | if x.p == finish { return x.t; } | ||
| 171 | |||
| 172 | self.maybe_push(x, Direction::Up, &mut vnext); | ||
| 173 | self.maybe_push(x, Direction::Down, &mut vnext); | ||
| 174 | self.maybe_push(x, Direction::Left, &mut vnext); | ||
| 175 | self.maybe_push(x, Direction::Right, &mut vnext); | ||
| 176 | if !self.is_busy(x.p, x.t+1) { | ||
| 177 | self.maybe_push(x, Direction::None, &mut vnext); | ||
| 178 | } | ||
| 179 | } | ||
| 180 | } | ||
| 181 | panic!("queue is empty"); | ||
| 182 | } | ||
| 183 | } | ||
| 184 | |||
| 185 | pub fn read_map_from_stdin() -> Map { | ||
| 186 | let mut tiles = vec![]; | ||
| 187 | let mut line = String::new(); | ||
| 188 | while std::io::stdin().read_line(&mut line).unwrap() > 0 { | ||
| 189 | let bytes = line.as_bytes(); | ||
| 190 | if bytes[2] == b'#' { | ||
| 191 | line.clear(); | ||
| 192 | continue; | ||
| 193 | } | ||
| 194 | let i = tiles.len(); | ||
| 195 | tiles.push(vec![]); | ||
| 196 | for j in 1..bytes.len()-2 { | ||
| 197 | tiles[i].push(Direction::from_byte(bytes[j])); | ||
| 198 | } | ||
| 199 | line.clear(); | ||
| 200 | } | ||
| 201 | let imax = tiles.len(); | ||
| 202 | let jmax = tiles[0].len(); | ||
| 203 | Map { tiles, imax, jmax } | ||
| 204 | } | ||
