(ql:quickload '(fiveam uiop cl-ppcre)) (defun material (string) (cond ((string= string "ore") :ore) ((string= string "clay") :clay) ((string= string "obsidian") :obsidian) ((string= string "geode") :geode))) (defun parse-robot-recipe (line) (cl-ppcre:register-groups-bind ((#'material robot) (#'parse-integer fq) (#'material fm) (#'parse-integer sq) (#'material sm)) ("(\\w+) robot costs (\\d+) (\\w+)(?: and (\\d+) (\\w+))?" line) (append (list robot fm fq) (when sm (list sm sq))))) (defun start-resources () (list :ore 0 :clay 0 :obsidian 0 :geode 0)) (defun start-bots () (list :ore 1 :clay 0 :obsidian 0 :geode 0)) (defun can-build-robot-p (requirements available) (loop for (material amount) on requirements by #'cddr always (<= amount (getf available material 0)))) (defun consume (requirements available) (loop for (material amount) on requirements by #'cddr do (decf (getf available material) amount)) available) (fiveam:test requirements (fiveam:is-false (can-build-robot-p '(:ore 6) '(:ore 5))) (fiveam:is-false (can-build-robot-p '(:ore 6) '(:clay 5))) (fiveam:is-true (can-build-robot-p '(:ore 2) '(:ore 5))) (fiveam:is-true (can-build-robot-p '(:ore 2) '(:clay 2 :ore 5))) (fiveam:is (equal '(:ore 2) (consume '(:ore 3) '(:ore 5))))) (defun material-collect (bots available-materials) (loop for (material amount) on bots by #'cddr nconc (list material (+ (getf available-materials material 0) amount)))) (defun bot-requirements (type recipes) (cdr (assoc type recipes :test #'eq))) (defun plist-merge (func p1 p2 &optional default) "Merge 2 flat plists P1 & P2 by FUNC. Use DEFAULT when value is missing on one list." (flet ((keys (plist) (loop for key in plist by #'cddr collect key))) (loop for key in (union (keys p1) (keys p2)) nconc (list key (funcall func (getf p1 key default) (getf p2 key default)))))) (defun max-costs (blueprint) (reduce (lambda (acc item) (plist-merge #'max acc item 0)) (mapcar #'cdr blueprint))) (defun can-build (recipes available-materials) (mapcan (lambda (bot-recipes) (when (can-build-robot-p (cdr bot-recipes) available-materials) (list (car bot-recipes)))) recipes)) (defun should-build (blueprints robots) (let ((max-cost (max-costs blueprints))) (loop for (type amount) on robots by #'cddr when (or (eq type :geode) (<= amount (getf max-cost type 0))) collect type))) (defun try! (build-next robots resources blueprints) (let ((req (bot-requirements build-next blueprints))) (when (can-build-robot-p req resources) (consume req resources) (incf (getf robots build-next 0))))) (defun next-step (build-next robots resources blueprints time-left) (if (zerop time-left) (list robots resources) (let ((new-robots (try! build-next robots resources blueprints))) (mapcar (lambda (next) (next-step next robots (material-collect robots resources) blueprints (1- time-left))) (if new-robots (should-build blueprints robots) (list build-next)))) )) (let ((blueprints (cdar (mapcar (lambda (l) (let ((blueprint (uiop:split-string l :separator ":."))) (cons (car blueprint) (mapcar #'parse-robot-recipe (butlast (cdr blueprint)))))) (uiop:read-file-lines "eg-in")))) ;; (materials '(:ore 8 :clay 20)) (materials (start-resources)) (bots (start-bots)) ;; (bots '(:geode 1 :ore 8 :clay 20)) (time-left 5)) (next-step :clay bots materials blueprints time-left) )