diff --git a/day12/common.rs b/day12/common.rs
new file mode 100644
index 0000000..c6ef906
--- /dev/null
+++ b/day12/common.rs
@@ -0,0 +1,184 @@
+// Copyright 2022 Christian Ulrich
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+use std::collections::HashSet;
+use std::io;
+
+pub struct Map {
+ squares: Vec>,
+ start_pos: (usize, usize),
+ end_pos: (usize, usize),
+}
+
+#[derive(Debug, Clone)]
+struct DijkstraInfo {
+ pos: (usize, usize),
+ shortest_distance: usize,
+ previous: Option<(usize, usize)>,
+}
+
+#[derive(Debug)]
+pub struct ShortestPath {
+ dijkstra_table: Vec,
+ current: (usize, usize),
+ start_pos: (usize, usize),
+}
+
+impl Map {
+ pub fn from_stdin() -> Result {
+ const ERROR: &str = "cannot parse map";
+ let squares: Result>, _> = io::stdin()
+ .lines()
+ .map(|line| Ok(line.map_err(|_| ERROR)?.into_bytes()))
+ .collect();
+ let mut squares = squares?;
+ let mut start = None;
+ let mut end = None;
+ for y in 0..squares.len() {
+ if squares[y].len() != squares[0].len() {
+ return Err(ERROR);
+ }
+ for x in 0..squares[y].len() {
+ match squares[y][x] as char {
+ 'S' if { start.is_none() } => {
+ start = Some((x, y));
+ squares[y][x] = 'a' as u8;
+ }
+ 'S' if { start.is_some() } => return Err(ERROR),
+ _ => (),
+ }
+ match squares[y][x] as char {
+ 'E' if { end.is_none() } => {
+ end = Some((x, y));
+ squares[y][x] = 'z' as u8;
+ }
+ 'E' if { end.is_some() } => return Err(ERROR),
+ _ => (),
+ }
+ }
+ }
+ Ok(Self {
+ squares: squares,
+ start_pos: start.ok_or(ERROR)?,
+ end_pos: end.ok_or(ERROR)?,
+ })
+ }
+
+ pub fn square_height(&self, pos: (usize, usize)) -> Option {
+ let (x, y) = pos;
+ self.squares.get(y)?.get(x).cloned()
+ }
+
+ pub fn start_pos(&self) -> (usize, usize) {
+ self.start_pos
+ }
+
+ pub fn end_pos(&self) -> (usize, usize) {
+ self.end_pos
+ }
+
+ fn neighbors(&self, pos: (usize, usize)) -> Option> {
+ let mut result = vec![];
+ let height = self.square_height(pos)?;
+ let (x, y) = pos;
+ if x > 0 {
+ result.push((x - 1, y));
+ }
+ if x < self.squares[0].len() - 1 {
+ result.push((x + 1, y));
+ }
+ if y > 0 {
+ result.push((x, y - 1));
+ }
+ if y < self.squares.len() - 1 {
+ result.push((x, y + 1));
+ }
+ let is_accessible = |pos: (usize, usize)| {
+ let neighbor_height = self.square_height(pos).unwrap();
+ u8::abs_diff(neighbor_height, height) <= 1 || neighbor_height > height
+ };
+ Some(
+ result
+ .iter()
+ .filter(|&pos| is_accessible(*pos))
+ .cloned()
+ .collect(),
+ )
+ }
+
+ fn dijkstra_table(&self, start_pos: (usize, usize)) -> Vec {
+ let mut result = Vec::with_capacity(self.squares.len() * self.squares[0].len());
+ for y in 0..self.squares.len() {
+ for x in 0..self.squares[0].len() {
+ let info = DijkstraInfo {
+ pos: (x, y),
+ shortest_distance: if (x, y) == start_pos { 0 } else { usize::MAX },
+ previous: None,
+ };
+ result.push(info);
+ }
+ }
+ result
+ }
+
+ pub fn shortest_path(&self) -> ShortestPath {
+ // we reverse start_pos and end_pos so the ShortestPath will have the right order
+ let start_pos = self.end_pos;
+ let end_pos = self.start_pos;
+
+ let mut table = self.dijkstra_table(start_pos);
+ let mut unvisited: HashSet<(usize, usize)> = table.iter().map(|i| i.pos).collect();
+ while let Some(info) = table
+ .iter()
+ .filter(|i| i.shortest_distance < usize::MAX && unvisited.contains(&i.pos))
+ .min_by_key(|i| i.shortest_distance)
+ .cloned()
+ {
+ for neighbor_pos in self.neighbors(info.pos).unwrap() {
+ let neighbor_info = table.iter_mut().find(|n| n.pos == neighbor_pos).unwrap();
+ let distance = info.shortest_distance + 1;
+ if distance < neighbor_info.shortest_distance {
+ neighbor_info.shortest_distance = distance;
+ neighbor_info.previous = Some(info.pos);
+ }
+ }
+ unvisited.remove(&info.pos);
+ }
+
+ ShortestPath {
+ dijkstra_table: table,
+ current: end_pos,
+ start_pos: start_pos,
+ }
+ }
+}
+
+impl Iterator for ShortestPath {
+ type Item = (usize, usize);
+
+ fn next(&mut self) -> Option {
+ if self.current == self.start_pos {
+ return None;
+ }
+ let result = self.current;
+ let info = self
+ .dijkstra_table
+ .iter()
+ .find(|i| i.pos == self.current)
+ .unwrap();
+ self.current = info.previous.unwrap();
+ Some(result)
+ }
+}
diff --git a/day12/input b/day12/input
new file mode 100644
index 0000000..3bff85e
--- /dev/null
+++ b/day12/input
@@ -0,0 +1,41 @@
+abaaaaacaaaacccccccccaaaaaaccccccccccccccccccccccccccccccccccaaaaaa
+abaaaaacaaaaccccaaaaaaaaaacccccccccccccccccccccccccccccccccccaaaaaa
+abaaacccaaaaccccaaaaaaaaaaacccaacccccccccccaacccccccccccccccccaaaaa
+abaaaacccaacccccaaaaaaaaaaaaaaaaacccccccccccacccccccccccccccccccaaa
+abacaacccccccccccaaaaaaaaaaaaaaaaccccccccccaacccccccccccccccccccaaa
+abcccacccccccccccaaaaaaaccaaaaaaaccccccccccclllcccccacccccccccccaac
+abccccccccccccccccaaaaaccccccccccccccccccclllllllcccccccccccccccccc
+abaaacccccccccccccaaaaaccccccccccccccccaakklllllllcccccccccaacccccc
+abaaacccccccccccacccaaaccccccccccccccccakkklpppllllccddaaacaacccccc
+abaaacccaaacccccaacaaaccccccccccccccccckkkkpppppllllcddddaaaacccccc
+abaacccaaaacccccaaaaaccccccccccccccccckkkkpppppppllmmddddddaaaacccc
+abaaaccaaaaccccccaaaaaacaaacccccccccckkkkpppuuuppplmmmmdddddaaacccc
+abaaacccaaaccccaaaaaaaacaaaaccccccckkkkkoppuuuuuppqmmmmmmdddddacccc
+abcccccccccccccaaaaaaaacaaaacccccjkkkkkooppuuuuuuqqqmmmmmmmddddcccc
+abccccccccccccccccaaccccaaaccccjjjjkoooooouuuxuuuqqqqqqmmmmmddecccc
+abacaaccccccccccccaacccccccccccjjjjoooooouuuxxxuvvqqqqqqqmmmeeecccc
+abaaaacccccccacccaccccccccccccjjjjoootuuuuuuxxxyvvvvvqqqqmmmeeecccc
+abaaaaacccccaaacaaacccccccccccjjjoooottuuuuuxxyyvvvvvvvqqmnneeecccc
+abaaaaaccaaaaaaaaaaccccccccaccjjjooottttxxxxxxyyyyyyvvvqqnnneeecccc
+abaaaccccaaaaaaaaaacccccccaaccjjjoootttxxxxxxxyyyyyyvvqqqnnneeecccc
+SbcaaccccaaaaaaaaaaccccaaaaacajjjnnntttxxxxEzzzyyyyvvvrrqnnneeccccc
+abcccccccaaaaaaaaaaacccaaaaaaaajjjnnntttxxxxyyyyyvvvvrrrnnneeeccccc
+abcccccccaaaaaaaaaaacccccaaaaccjjjnnnnttttxxyyyyywvvrrrnnneeecccccc
+abcccccccccaaaaaaccaccccaaaaaccciiinnnnttxxyyyyyyywwrrnnnneeecccccc
+abccccccccccccaaacccccccaacaaaccciiinnnttxxyywwyyywwrrnnnffeccccccc
+abccccccccccccaaacccccccaccaaaccciiinnnttwwwwwwwwwwwrrrnnfffccccccc
+abccccccccccccccccccccccccccccccciiinnnttwwwwsswwwwwrrrnnfffccccccc
+abaaaccaaccccccccccccccccccccccccciinnnttswwwssswwwwrrroofffacccccc
+abaaccaaaaaacccccccccccccccccaaacciinnntssssssssssrrrrooofffacccccc
+abaccccaaaaacccccccaaacccccccaaaaciinnnssssssmmssssrrrooofffacccccc
+abaacaaaaaaacccccccaaaaccccccaaaaciiinmmmssmmmmmoosroooooffaaaacccc
+abaaaaaaaaaaaccccccaaaaccccccaaacciiimmmmmmmmmmmoooooooofffaaaacccc
+abcaaaaaaaaaaccccccaaaaccccccccccccihhmmmmmmmhggoooooooffffaaaccccc
+abcccccaaacaccccccccaaccccccccccccchhhhhhhhhhhggggggggggffaaacccccc
+abaccccaacccccccccccaaaccccccccccccchhhhhhhhhhgggggggggggcaaacccccc
+abaaaccccaccccccccccaaaacccaacccccccchhhhhhhaaaaaggggggcccccccccccc
+abaaaccccaaacaaaccccaaaacaaaacccccccccccccccaaaacccccccccccccccaaac
+abaacccccaaaaaaaccccaaaaaaaaacccccccccccccccaaacccccccccccccccccaaa
+abaaaccccaaaaaaccccaaaaaaaaccccccccccccccccccaacccccccccccccccccaaa
+abccccccaaaaaaaaaaaaaaaaaaacccccccccccccccccaaccccccccccccccccaaaaa
+abcccccaaaaaaaaaaaaaaaaaaaaacccccccccccccccccccccccccccccccccaaaaaa
diff --git a/day12/part1.rs b/day12/part1.rs
new file mode 100644
index 0000000..97b979e
--- /dev/null
+++ b/day12/part1.rs
@@ -0,0 +1,26 @@
+// Copyright 2022 Christian Ulrich
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+// usage: ./part1 < input
+
+pub mod common;
+
+use common::Map;
+
+fn main() -> Result<(), &'static str> {
+ let map = Map::from_stdin()?;
+ println!("{:?}", map.shortest_path().count());
+ Ok(())
+}
diff --git a/day12/testinput b/day12/testinput
new file mode 100644
index 0000000..86e9cac
--- /dev/null
+++ b/day12/testinput
@@ -0,0 +1,5 @@
+Sabqponm
+abcryxxl
+accszExk
+acctuvwj
+abdefghi