diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2f7896d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+target/
diff --git a/day-12/Cargo.lock b/day-12/Cargo.lock
new file mode 100644
index 0000000..b7e1235
--- /dev/null
+++ b/day-12/Cargo.lock
@@ -0,0 +1,78 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "day-12"
+version = "0.1.0"
+dependencies = [
+ "indoc",
+]
+
+[[package]]
+name = "indoc"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47741a8bc60fb26eb8d6e0238bbb26d8575ff623fdc97b1a2c00c050b9684ed8"
+dependencies = [
+ "indoc-impl",
+ "proc-macro-hack",
+]
+
+[[package]]
+name = "indoc-impl"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce046d161f000fffde5f432a0d034d0341dc152643b2598ed5bfce44c4f3a8f0"
+dependencies = [
+ "proc-macro-hack",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "unindent",
+]
+
+[[package]]
+name = "proc-macro-hack"
+version = "0.5.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.54"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2af957a63d6bd42255c359c93d9bfdb97076bd3b820897ce55ffbfbf107f44"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
+
+[[package]]
+name = "unindent"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f14ee04d9415b52b3aeab06258a3f07093182b88ba0f9b8d203f211a7a7d41c7"
diff --git a/day-12/Cargo.toml b/day-12/Cargo.toml
new file mode 100644
index 0000000..35cad43
--- /dev/null
+++ b/day-12/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "day-12"
+version = "0.1.0"
+authors = ["Tristan Daniƫl Maat <tm@tlater.net>"]
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+indoc = "0.3"
diff --git a/day-12/input b/day-12/input
new file mode 100644
index 0000000..bedc29a
--- /dev/null
+++ b/day-12/input
@@ -0,0 +1,771 @@
+F8
+N2
+F32
+F17
+E4
+N4
+R90
+S2
+R90
+E3
+L90
+N5
+E2
+N2
+W5
+F78
+L180
+F19
+R90
+S1
+E2
+L180
+E1
+S5
+E4
+F62
+R180
+F16
+S2
+F8
+R180
+S1
+L90
+E4
+R90
+S3
+E5
+R180
+F87
+N2
+E2
+R90
+N2
+F2
+R90
+N5
+W4
+L90
+F42
+N1
+F93
+F87
+E2
+S4
+F73
+L270
+S2
+W3
+F48
+W5
+L180
+N1
+F53
+R90
+S2
+R90
+N2
+E2
+S5
+W3
+R90
+E2
+R90
+W1
+L180
+F29
+W1
+F56
+R90
+F34
+F74
+S1
+R90
+L90
+W4
+L90
+W5
+L90
+W1
+L90
+N5
+E2
+S2
+F58
+N5
+L90
+S4
+L90
+R270
+W4
+S4
+E3
+R180
+S4
+W3
+R90
+F36
+R90
+W1
+F73
+S4
+E1
+L90
+S4
+W5
+L90
+F20
+W3
+L180
+E3
+S1
+R90
+S5
+W3
+L90
+E5
+W2
+F21
+N4
+F83
+W4
+F48
+W3
+F4
+L90
+N5
+R270
+E1
+S5
+L180
+F44
+W5
+R180
+S3
+F30
+N5
+F87
+L90
+F69
+S5
+E1
+R90
+E2
+S3
+F40
+W4
+F97
+W5
+F20
+L180
+N5
+L90
+E5
+N3
+L90
+F13
+N2
+F38
+S5
+F27
+E5
+L180
+F59
+N3
+F2
+R90
+N2
+R90
+F56
+L90
+N4
+R90
+F12
+F34
+N3
+F93
+L270
+W3
+F74
+W4
+R90
+E2
+L180
+W3
+F12
+N5
+W1
+F98
+E4
+R180
+S1
+W5
+R90
+F96
+N2
+L90
+F36
+S1
+F3
+W3
+F100
+N5
+R90
+F33
+W3
+N5
+E3
+R90
+F33
+N5
+E2
+N1
+L90
+F84
+L270
+E1
+F28
+R180
+W3
+L90
+S2
+F88
+L90
+W2
+N1
+F3
+R90
+F56
+N1
+N4
+L90
+R90
+F97
+E5
+N4
+F38
+N1
+R90
+W1
+F60
+W3
+N1
+F59
+E1
+N3
+E3
+L180
+N1
+F53
+S1
+E2
+R90
+E2
+F6
+R180
+F36
+R180
+W2
+F81
+R90
+E4
+R90
+F97
+L90
+W1
+S1
+E5
+L180
+F34
+R180
+F64
+E2
+R180
+W3
+S5
+L90
+E4
+F12
+F58
+W3
+N3
+F77
+N4
+F32
+R90
+N2
+E4
+L90
+S5
+E1
+N5
+F44
+R90
+F5
+E4
+R90
+N5
+E4
+R180
+W3
+L90
+N1
+F1
+S3
+E5
+R180
+S3
+F86
+S5
+F61
+W3
+R270
+W5
+R90
+F26
+R180
+F92
+S5
+L90
+E5
+N5
+F82
+R90
+F22
+R90
+F23
+S1
+F42
+N4
+F76
+E1
+S1
+W3
+S2
+L90
+F19
+E4
+F41
+E2
+N2
+L90
+F34
+S4
+F20
+W3
+F18
+S1
+R90
+N3
+F38
+W3
+R90
+W4
+R90
+E2
+R90
+F10
+L90
+N4
+F94
+S1
+W3
+R180
+W5
+F74
+R90
+S4
+L180
+S3
+F74
+N5
+S4
+L90
+F34
+S2
+E5
+N5
+F28
+L90
+E1
+F31
+N1
+L90
+L90
+W2
+N5
+R90
+F1
+N5
+F48
+W2
+F50
+N2
+F62
+S4
+L90
+W5
+N1
+F12
+W3
+R90
+R90
+F75
+N5
+F69
+E3
+F19
+N2
+F77
+E1
+N4
+R180
+E3
+N2
+L90
+N1
+W1
+S4
+F85
+W1
+R90
+F74
+E5
+F73
+E4
+S3
+W4
+S5
+L90
+F49
+S5
+E5
+F5
+W2
+F58
+R90
+W5
+F53
+S4
+F86
+N2
+F88
+E5
+F59
+E1
+F56
+W2
+N4
+W4
+R180
+F16
+F25
+R180
+N3
+F4
+W4
+S4
+F98
+E5
+L90
+W4
+S1
+E2
+R90
+F96
+L270
+E1
+N1
+F55
+S1
+F10
+R90
+W2
+L90
+N5
+R90
+N4
+E4
+L90
+F52
+S3
+F43
+E2
+R90
+S3
+R90
+N4
+E1
+N4
+F15
+E3
+R270
+L180
+N2
+F43
+L90
+W2
+F19
+L90
+S5
+F58
+E4
+S4
+L90
+W1
+F9
+N4
+F38
+S5
+L90
+W1
+F39
+W5
+F83
+L180
+F99
+L90
+E3
+S2
+R90
+N3
+F35
+N1
+N3
+L90
+N4
+W5
+F26
+R270
+N2
+F7
+N1
+F16
+S4
+L90
+S5
+L180
+F5
+W1
+F32
+S2
+N3
+F82
+N4
+R90
+F27
+R180
+F20
+S1
+E3
+L90
+W3
+F23
+L180
+N3
+F34
+W1
+N3
+S2
+F80
+E5
+F65
+L90
+E5
+N1
+F80
+R90
+W3
+L90
+N1
+L180
+S1
+F65
+E3
+S1
+W3
+F89
+S1
+F24
+E5
+F85
+W1
+F87
+S1
+R90
+S4
+F3
+S3
+F23
+N4
+L90
+N5
+R90
+N2
+R90
+S2
+W4
+S2
+F95
+L90
+F52
+W1
+N5
+L90
+N4
+S3
+E3
+R90
+N2
+E1
+R180
+W4
+F82
+L180
+E5
+L90
+E4
+F65
+W5
+R90
+W5
+N5
+L180
+N4
+F22
+W3
+S4
+F60
+R90
+E5
+N3
+F32
+S2
+F80
+R90
+F18
+S3
+L90
+F90
+E3
+L90
+N3
+E5
+F79
+N5
+W4
+S5
+F100
+N1
+E3
+S3
+F49
+R180
+S3
+E2
+F1
+W1
+F5
+R180
+S5
+W3
+S3
+F67
+R270
+N3
+W3
+N1
+W3
+F37
+L90
+N3
+L90
+F68
+N3
+W4
+W2
+F26
+N3
+L90
+W3
+S2
+F7
+W3
+E3
+L270
+F64
+R90
+E4
+R90
+W3
+N1
+W1
+F98
+R270
+W5
+F45
+R90
+F49
+E4
+S2
+F58
+F56
+W3
+F57
+E3
+S5
+R180
+E3
+F82
+F57
+S3
+W2
+R90
+E2
+R90
+F95
+W4
+F85
+E3
+N3
+R90
+E5
+F31
+R90
+F20
+R90
+N5
+E3
+S4
+R180
+W1
+N5
+F72
+L90
+E3
+F46
+R180
+F18
+E3
+F48
+S2
+F84
+W3
+F88
+F44
+S2
+E4
+F77
+L90
+N4
+L90
+E2
+F22
+E5
+L90
+F79
+W1
+R90
+F41
+R180
+F54
diff --git a/day-12/src/main.rs b/day-12/src/main.rs
new file mode 100644
index 0000000..6212c23
--- /dev/null
+++ b/day-12/src/main.rs
@@ -0,0 +1,225 @@
+use std::fs;
+
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+    let input = fs::read_to_string("input")?;
+    let actions = parse_actions(&input)?;
+
+    // Part 1
+    let location = simulate_ship_movement(&actions);
+    println!("{}", location.0.abs() + location.1.abs());
+
+    // Part 2
+    let location = simulate_waypoint_movement(&actions);
+    println!("{}", location.0.abs() + location.1.abs());
+
+    Ok(())
+}
+
+#[derive(Copy, Clone, Debug)]
+enum Action {
+    North(u32),
+    South(u32),
+    East(u32),
+    West(u32),
+    Left(u32),
+    Right(u32),
+    Forward(u32),
+}
+
+impl Action {
+    fn new(input: &str) -> Result<Self, String> {
+        if input.len() < 2 {
+            Err(format!("Invalud input line: {}", input))?;
+        }
+
+        let (action, value) = input.split_at(1);
+        let value = value
+            .parse()
+            .map_err(|e| format!("Could not parse value: {}", e))?;
+
+        Ok(match action {
+            "N" => Self::North(value),
+            "S" => Self::South(value),
+            "E" => Self::East(value),
+            "W" => Self::West(value),
+            "L" => Self::Left(value),
+            "R" => Self::Right(value),
+            "F" => Self::Forward(value),
+            _ => Err(format!("Could not parse action: {}", action))?,
+        })
+    }
+
+    fn opposite_direction(self) -> Self {
+        match self {
+            Action::North(x) => Action::South(x),
+            Action::South(x) => Action::North(x),
+            Action::East(x) => Action::West(x),
+            Action::West(x) => Action::East(x),
+            _ => panic!("Invalid direction"),
+        }
+    }
+
+    fn right_direction(self) -> Self {
+        match self {
+            Action::North(x) => Action::East(x),
+            Action::South(x) => Action::West(x),
+            Action::East(x) => Action::South(x),
+            Action::West(x) => Action::North(x),
+            _ => panic!("Invalid direction"),
+        }
+    }
+
+    fn left_direction(self) -> Self {
+        match self {
+            Action::North(x) => Action::West(x),
+            Action::South(x) => Action::East(x),
+            Action::East(x) => Action::North(x),
+            Action::West(x) => Action::South(x),
+            _ => panic!("Invalid direction"),
+        }
+    }
+}
+
+fn parse_actions(input: &str) -> Result<Vec<Action>, String> {
+    input.lines().map(Action::new).collect()
+}
+
+fn simulate_ship_movement(actions: &Vec<Action>) -> (i64, i64) {
+    let mut coords: (i64, i64) = (0, 0);
+    let mut direction = Action::East(0);
+
+    for action in actions {
+        // If the action is Action::Forward, we instead want to move
+        // in a direction determined by our current angle.
+        //
+        // So we convert Action::Forward to a concrete direction.
+
+        let converted_action = if let &Action::Forward(amount) = action {
+            match direction {
+                Action::North(_) => Action::North(amount),
+                Action::South(_) => Action::South(amount),
+                Action::East(_) => Action::East(amount),
+                Action::West(_) => Action::West(amount),
+                _ => unreachable!("Direction can only be NSEW."),
+            }
+        } else {
+            *action
+        };
+
+        match converted_action {
+            Action::North(amount) => {
+                coords.1 += amount as i64;
+            }
+            Action::South(amount) => {
+                coords.1 -= amount as i64;
+            }
+            Action::East(amount) => {
+                coords.0 += amount as i64;
+            }
+            Action::West(amount) => {
+                coords.0 -= amount as i64;
+            }
+            Action::Left(amount) => match amount % 360 {
+                0 => direction = direction,
+                90 => direction = direction.left_direction(),
+                180 => direction = direction.opposite_direction(),
+                270 => direction = direction.right_direction(),
+                _ => panic!("Only 90 degree turns allowed"),
+            },
+            Action::Right(amount) => match amount % 360 {
+                0 => direction = direction,
+                90 => direction = direction.right_direction(),
+                180 => direction = direction.opposite_direction(),
+                270 => direction = direction.left_direction(),
+                _ => panic!("Only 90 degree turns allowed"),
+            },
+            Action::Forward(_) => unreachable!("Converted previously"),
+        }
+    }
+
+    coords
+}
+
+fn simulate_waypoint_movement(actions: &Vec<Action>) -> (i64, i64) {
+    let mut waypoint = (1, 10);
+    let mut ship = (0, 0);
+
+    for action in actions {
+        match *action {
+            Action::North(amount) => {
+                waypoint.0 += amount as i64;
+            }
+            Action::South(amount) => {
+                waypoint.0 -= amount as i64;
+            }
+            Action::East(amount) => {
+                waypoint.1 += amount as i64;
+            }
+            Action::West(amount) => {
+                waypoint.1 -= amount as i64;
+            }
+            Action::Left(amount) => match amount % 360 {
+                0 => {}
+                90 => waypoint = (waypoint.1, -waypoint.0),
+                180 => waypoint = (-waypoint.0, -waypoint.1),
+                270 => waypoint = (-waypoint.1, waypoint.0),
+                _ => panic!("Only 90 degree turns allowed"),
+            },
+            Action::Right(amount) => match amount % 360 {
+                0 => {}
+                90 => waypoint = (-waypoint.1, waypoint.0),
+                180 => waypoint = (-waypoint.0, -waypoint.1),
+                270 => waypoint = (waypoint.1, -waypoint.0),
+                _ => panic!("Only 90 degree turns allowed"),
+            },
+            Action::Forward(amount) => {
+                ship.0 += waypoint.0 * amount as i64;
+                ship.1 += waypoint.1 * amount as i64;
+            }
+        }
+    }
+
+    ship
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use indoc::indoc;
+
+    #[test]
+    fn test_simple() -> Result<(), Box<dyn std::error::Error>> {
+        let input = indoc!(
+            "F10
+             N3
+             F7
+             R90
+             F11
+            "
+        );
+
+        let actions = parse_actions(&input)?;
+        let location = simulate_ship_movement(&actions);
+        assert_eq!(location.0.abs() + location.1.abs(), 25);
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_simple2() -> Result<(), Box<dyn std::error::Error>> {
+        let input = indoc!(
+            "F10
+             N3
+             F7
+             R90
+             F11
+            "
+        );
+
+        let actions = parse_actions(&input)?;
+        let location = simulate_waypoint_movement(&actions);
+        assert_eq!(location.0.abs() + location.1.abs(), 286);
+
+        Ok(())
+    }
+}