aboutsummaryrefslogtreecommitdiffstats
path: root/AoC2022/23/solver.lisp
blob: f3291ae36e85b823be80ab8607e24ce6a46af61c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
(ql:quickload '(fiveam uiop arrows))

(defun elf-coordinates (filename)
  (let ((coord (make-hash-table :test #'equal)))
    (with-open-file (input filename)
      (loop for line = (read-line input nil nil)
            and y from 0
            while line
            do (loop for point across line
                     and x from 0
                     when (eq point #\#)
                       do (setf (gethash (cons x y) coord) t))))
    coord))

(defun bounding-box (elf-coordinates)
  (loop for k being the hash-key of elf-coordinates
        minimize (car k) into minx
        minimize (cdr k) into miny
        maximize (car k) into maxx
        maximize (cdr k) into maxy
        finally (return (list minx miny maxx maxy))))

(defparameter *moves*
  (vector (vector (cons -1 -1) (cons  0 -1) (cons  1 -1))   ;; North
          (vector (cons -1  1) (cons  0  1) (cons  1  1))   ;; South
          (vector (cons -1 -1) (cons -1  0) (cons -1  1))   ;; West
          (vector (cons  1 -1) (cons  1  0) (cons  1  1)))) ;; East

(defun draw (elves)
  (with-output-to-string (out)
    (destructuring-bind (minx miny maxx maxy) (bounding-box elves)
      (loop for y from miny to maxy
            do (princ #\Newline out)
               (loop for x from minx to maxx
                     do (princ (if (gethash (cons x y) elves)
                                   #\# #\.) out))))))

(defun pair-add (a b)
  (cons (+ (car a) (car b))
        (+ (cdr a) (cdr b))))

(defun test-move (dir elf others)
  (loop for next-dir across dir
        never (gethash (pair-add elf next-dir) others)))

(defun all-moves (step elves)
  (let ((next-places (make-hash-table :test #'equal)))
    (loop for elf being the hash-key of elves
          do
             (let ((options (loop for dirs below 4
                                  collect (arrows:-> (aref *moves* (mod (+ dirs step) 4))
                                                     (test-move elf elves)))))
               (unless (or (notany #'null options) ;; all free don't move
                           (every #'null options)) ;; all blocked don't move
                 (let ((prop
                         (arrows:->
                          (aref *moves* (mod (+ (position t options) step) 4))
                          (aref 1)
                          (pair-add elf))))
                   (setf (gethash prop next-places)
                         (cons elf (gethash prop next-places)))))))
    next-places))

(defun move-elves! (next-places elves)
  (loop for target being the hash-key of next-places using (hash-value source)
        when (null (cdr source)) ;; no conflicts on target
          do (remhash (car source) elves)
             (setf (gethash target elves) t)))

(defun empty-ground (elves)
  (destructuring-bind (minx miny maxx maxy) (bounding-box elves)
    (- (* (1+ (- maxx minx))
          (1+ (- maxy miny)))
       (hash-table-count elves))))

(defun solver (elves limit)
  (loop for step from 0 below limit
        for moves = (all-moves step elves)
        while (plusp (hash-table-count moves))
        do (move-elves! moves elves)
        finally (return step)))

(defun part1 (filename)
  (let ((elves (elf-coordinates filename)))
    (solver elves 10)
    (empty-ground elves)))

(fiveam:test solutions
  (fiveam:is (= 110  (part1 "eg-in")))
  (fiveam:is (= 4052 (part1 "input")))
  (fiveam:is (= 20 (1+ (solver (elf-coordinates "eg-in") 1000))))
  (fiveam:is (= 978 (1+ (solver (elf-coordinates "input") 1000)))))