src/2018/days/day-13.lisp @ e41337e3b59b

Accessors
author Steve Losh <steve@stevelosh.com>
date Wed, 15 Dec 2021 23:10:57 -0500
parents 182bdd87fd9e
children (none)
(advent:defpackage* :advent/2018/13)
(in-package :advent/2018/13)

;;;; Cart ---------------------------------------------------------------------
(defun left (velocity)
  (* #c(0 -1) velocity))

(defun right (velocity)
  (* #c(0 1) velocity))

(defun straight (velocity)
  velocity)

(defun horizontalp (velocity)
  (zerop (imagpart velocity)))

(defun verticalp (velocity)
  (zerop (realpart velocity)))


(defparameter *ai* '#1=(left straight right . #1#))

(defstruct cart
  position
  velocity
  (ai *ai*))


(defun turn-intersection (cart)
  (callf (cart-velocity cart) (pop (cart-ai cart))))

(defun turn-corner (cart corner)
  (callf (cart-velocity cart)
         (let ((v (verticalp (cart-velocity cart))))
           (ecase corner
             (#\\ (if v #'left #'right))
             (#\/ (if v #'right #'left))))))


(defun cart-rune (cart)
  (ecase (cart-velocity cart)
    (#c(0 -1) #\^)
    (#c(0 1) #\v)
    (#c(-1 0) #\<)
    (#c(1 0) #\>)))


;;;; Carts ---------------------------------------------------------------------
(defun make-carts (sequence)
  (iterate (for cart :in-whatever sequence)
           (collect-hash ((cart-position cart) cart))))

(defun cart-at (carts position)
  (gethash position carts))

(defun insert-cart (carts cart)
  (setf (gethash (cart-position cart) carts) cart))

(defun remove-cart (carts cart)
  (remhash (cart-position cart) carts))

(define-condition collision ()
  ((position :initarg :position :accessor collision-position)))

(defun move-cart (carts cart)
  (with-slots (position velocity) cart
    (remove-cart carts cart)
    (incf position velocity)
    (if-let ((other-cart (cart-at carts position)))
      (restart-case (error 'collision :position position)
        (remove-crashed-carts ()
          (remove-cart carts other-cart)
          (push cart *dead-carts*)
          (push other-cart *dead-carts*)))
      (insert-cart carts cart)))
  (values))

(defun remove-crashed-carts (condition)
  (declare (ignore condition))
  (invoke-restart 'remove-crashed-carts))


;;;; Track --------------------------------------------------------------------
(deftype track ()
  '(simple-array character (* *)))

(defun track-at (track position)
  (aref track (realpart position) (imagpart position)))

(defun print-track (track carts)
  (destructuring-bind (width height) (array-dimensions track)
    (dotimes (y height)
      (dotimes (x width)
        (when (zerop x)
          (terpri))
        (write-char
          (if-let ((cart (cart-at carts (complex x y))))
            (cart-rune cart)
            (aref track x y)))))
    (terpri)))

(defun cornerp (track position)
  (find (track-at track position) "\\/"))

(defun intersectionp (track position)
  (char= (track-at track position) #\+))


;;;; Input Parsing ------------------------------------------------------------
(defun cart-rune-velocity (rune)
  (ecase rune
    (#\^ #c(0 -1))
    (#\v #c(0 1))
    (#\< #c(-1 0))
    (#\> #c(1 0))))

(defun cart-rune-p (rune)
  (find rune "^v<>"))

(defun track-rune (cart-or-track-rune)
  (case cart-or-track-rune
    ((#\^ #\v) #\|)
    ((#\< #\>) #\-)
    (t cart-or-track-rune)))

(defun parse-track (lines)
  (alexandria:removef lines "" :test #'string=)
  (let ((track (make-array (list (alexandria:extremum (mapcar #'length lines) '>)
                                 (length lines))
                 :element-type 'character
                 :initial-element #\space))
        (carts nil))
    (iterate
      (for line :in lines)
      (for y :from 0)
      (iterate
        (for rune :in-string line)
        (for x :from 0)
        (when (cart-rune-p rune)
          (push (make-cart
                  :position (complex x y)
                  :velocity (cart-rune-velocity rune))
                carts))
        (setf (aref track x y) (track-rune rune))))
    (values track (make-carts carts))))


;;;; Simulation ---------------------------------------------------------------
(defparameter *dead-carts* nil)

(defun tick-cart (track carts cart)
  (unless (member cart *dead-carts*)
    (move-cart carts cart)
    (let ((pos (cart-position cart)))
      (cond
        ((cornerp track pos) (turn-corner cart (track-at track pos)))
        ((intersectionp track pos) (turn-intersection cart)))))
  (values))

(defun cart< (cart1 cart2)
  (let* ((p1 (cart-position cart1))
         (p2 (cart-position cart2))
         (x1 (realpart p1))
         (y1 (imagpart p1))
         (x2 (realpart p2))
         (y2 (imagpart p2)))
    ;; cart1 moves before cart2 if it's:
    (or (< y1 y2) ; higher up
        (and (= y1 y2) (< x1 x2))))) ; or further left

(defun tick-carts (track carts)
  (dolist (cart (sort (alexandria:hash-table-values carts) #'cart<))
    (tick-cart track carts cart)))


;;;; Solve --------------------------------------------------------------------
(defparameter *example* (format nil "
/->-\\
|   |  /----\\
| /-+--+-\\  |
| | |  | v  |
\\-+-/  \\-+--/
  \\------/
"))

(defun format-position (position)
  (format nil "~D,~D" (realpart position) (imagpart position)))

(defun part-1 (lines)
  (multiple-value-bind (track carts) (parse-track lines)
    (handler-case (loop (tick-carts track carts))
      (collision (collision) (format-position (collision-position collision))))))

(defun part-2 (lines)
  (multiple-value-bind (track carts) (parse-track lines)
    (handler-bind ((collision #'remove-crashed-carts))
      (iterate
        (tick-carts track carts)
        (for tick :from 1)
        (for carts-remaining = (hash-table-count carts))
        (finding
          (_ carts alexandria:hash-table-values first cart-position format-position)
          :such-that (= 1 carts-remaining))))))

(define-problem (2018 13) (data read-lines) ("83,49" "73,36")
  (values (part-1 data)
          (part-2 data)))