aoc/2021/11/11.lisp

94 lines
3.2 KiB
Common Lisp

(load (merge-pathnames "../common.lisp" *load-truename*))
(defconstant tile-floor #\.)
(defconstant tile-empty #\L)
(defconstant tile-taken #\#)
(defun parse-input (input)
(make-array (list (length input) (length (car input)))
:initial-contents input))
(defconstant test-data
(parse-input '("L.LL.LL.LL"
"LLLLLLL.LL"
"L.L.L..L.."
"LLLL.LL.LL"
"L.LL.LL.LL"
"L.LLLLL.LL"
"..L.L....."
"LLLLLLLLLL"
"L.LLLLLL.L"
"L.LLLLL.LL")))
(defun safe-at (state x y max-x max-y &key (default tile-floor))
(if (and (>= x 0) (>= y 0) (< x max-x) (< y max-y))
(aref state x y)
default))
(defun count-neighbours (state x y neighbour)
(destructuring-bind (width height) (array-dimensions state)
(loop for i in '(-1 0 1)
sum (loop for j in '(-1 0 1)
count (and (not (and (zerop i) (zerop j)))
(eq tile-taken (funcall neighbour state x y i j width height)))))))
(defun neighbour-at (state x y i j max-x max-y)
(safe-at state (+ x i) (+ y j) max-x max-y))
(defun closest-in-direction (state x y dir-x dir-y max-x max-y)
(let* ((next-x (+ x dir-x)) (next-y (+ y dir-y))
(next (safe-at state next-x next-y max-x max-y :default NIL)))
(if (eq next tile-floor)
(closest-in-direction state next-x next-y dir-x dir-y max-x max-y)
next)))
(defun next-cell-state-changed (state x y neighbour limit)
(let ((current (aref state x y)))
(when (not (eq current tile-floor))
(let ((neighbours (count-neighbours state x y neighbour)))
(or (and (char= current tile-empty) (zerop neighbours))
(and (char= current tile-taken) (>= neighbours limit)))))))
(defun next-state-delta (state max-x max-y neighbour limit)
(loop for i from 0 to (1- max-x)
append (loop for j from 0 to (1- max-y)
when (next-cell-state-changed state i j neighbour limit)
collect (list i j))))
(defun flip-state (state x y)
(setf (aref state x y)
(case (aref state x y)
(#\. tile-floor)
(#\L tile-taken)
(#\# tile-empty))))
(defun next-state (state delta)
(loop for pos in delta do
(flip-state state (car pos) (cadr pos))))
(defun until-stable (state neighbour limit)
(destructuring-bind (width height) (array-dimensions state)
(loop as delta = (next-state-delta state width height neighbour limit)
until (null delta)
do (next-state state delta)
finally (return state))))
(defun count-taken (state)
(destructuring-bind (width height) (array-dimensions state)
(loop for x from 0 to (1- width)
sum (loop for y from 0 to (1- height)
count (char= tile-taken (safe-at state x y width height))))))
(defun solution (input count limit)
(count-taken (until-stable input count limit)))
(defun part1 (input)
(solution input #'neighbour-at 4))
(defun part2 (input)
(solution input #'closest-in-direction 5))
(let ((input (read-data)))
(format t "Part 1: ~A~%" (part1 (parse-input input)))
(format t "Part 2: ~A~%" (part2 (parse-input input))))