--- /dev/null
+(asdf:load-system :adventofcode2020)
+(in-package #:adventofcode2020)
+(named-readtables:in-readtable fn-reader)
+
+(defun parse-action (str)
+ (cl-ppcre:register-groups-bind
+ ((λ(char _ 0) action) (#'parse-integer scalar)) ("([NSEWLRF])([0-9]+)" str)
+ (list action scalar)))
+
+(defstruct ship
+ (heading #\E)
+ (ns-coord 0)
+ (ew-coord 0)
+ (waypoint-ns 1)
+ (waypoint-ew 10))
+
+(defun apply-turn-heading (heading degree)
+ (let ((directions #(#\N #\E #\S #\W))
+ (heading-delta (/ degree 90)))
+ (-<>
+ (position heading directions)
+ (+ heading-delta)
+ (mod 4)
+ (aref directions <>))))
+
+(defun apply-turn-space (x y degree)
+ (if (= degree 0) (list x y)
+ (destructuring-bind (x y) (apply-turn-space x y (- degree (* (signum degree) 90)))
+ (if (> degree 0)
+ (list y (* -1 x))
+ (list (* -1 y) x)))))
+
+(defun apply-action-ship (ship act-pair)
+ (destructuring-bind (action scalar) act-pair
+ (let ((new-ship (copy-structure ship)))
+ (case action
+ (#\N (setf (ship-ns-coord new-ship) (+ (ship-ns-coord ship) scalar)))
+ (#\S (setf (ship-ns-coord new-ship) (- (ship-ns-coord ship) scalar)))
+ (#\E (setf (ship-ew-coord new-ship) (+ (ship-ew-coord ship) scalar)))
+ (#\W (setf (ship-ew-coord new-ship) (- (ship-ew-coord ship) scalar)))
+ (#\L (setf (ship-heading new-ship) (apply-turn-heading (ship-heading ship) (* -1 scalar))))
+ (#\R (setf (ship-heading new-ship) (apply-turn-heading (ship-heading ship) (* +1 scalar))))
+ (#\F (setf new-ship (apply-action-ship new-ship (list (ship-heading ship) scalar)))))
+ new-ship)))
+
+(defun apply-action-waypoint (ship act-pair)
+ (destructuring-bind (action scalar) act-pair
+ (let ((new-ship (copy-structure ship)))
+ (case action
+ (#\N (setf (ship-waypoint-ns new-ship) (+ (ship-waypoint-ns ship) scalar)))
+ (#\S (setf (ship-waypoint-ns new-ship) (- (ship-waypoint-ns ship) scalar)))
+ (#\E (setf (ship-waypoint-ew new-ship) (+ (ship-waypoint-ew ship) scalar)))
+ (#\W (setf (ship-waypoint-ew new-ship) (- (ship-waypoint-ew ship) scalar)))
+ (#\L (destructuring-bind (x y) (apply-turn-space (ship-waypoint-ew ship) (ship-waypoint-ns ship) (* -1 scalar))
+ (setf (ship-waypoint-ew new-ship) x)
+ (setf (ship-waypoint-ns new-ship) y)))
+ (#\R (destructuring-bind (x y) (apply-turn-space (ship-waypoint-ew ship) (ship-waypoint-ns ship) (* +1 scalar))
+ (setf (ship-waypoint-ew new-ship) x)
+ (setf (ship-waypoint-ns new-ship) y)))
+ (#\F (setf (ship-ns-coord new-ship) (+ (ship-ns-coord ship) (* scalar (ship-waypoint-ns ship))))
+ (setf (ship-ew-coord new-ship) (+ (ship-ew-coord ship) (* scalar (ship-waypoint-ew ship))))))
+ new-ship)))
+
+
+(day 12 input
+ (let ((actions (mapcar #'parse-action (list-from input))))
+ (part1 (let ((final-ship (reduce #'apply-action-ship actions :initial-value (make-ship))))
+ (manhattan-distance (list 0 0) (list (ship-ew-coord final-ship)
+ (ship-ns-coord final-ship)))))
+ (part2 (let ((final-ship (reduce #'apply-action-waypoint actions :initial-value (make-ship))))
+ (manhattan-distance (list 0 0) (list (ship-ew-coord final-ship)
+ (ship-ns-coord final-ship)))))))
+
+(def-suite day12)
+(in-suite day12)
+
+(defvar *simple-actions*
+ '("F10" "N3" "F7" "R90" "F11"))
+
+(test apply-turns-heading
+ (is (equal #\S (apply-turn-heading #\E 90)))
+ (is (equal #\W (apply-turn-heading #\E 180)))
+ (is (equal #\N (apply-turn-heading #\E 270)))
+ (is (equal #\N (apply-turn-heading #\E -90)))
+ (is (equal #\W (apply-turn-heading #\E -180))))
+
+(test apply-actions-ship
+ (is (equalp
+ (make-ship :heading #\S :ns-coord -8 :ew-coord 17)
+ (reduce #'apply-action-ship (mapcar #'parse-action *simple-actions*)
+ :initial-value (make-ship)))))
+
+(run! 'day12)