(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) (let ((first-material (fset:with (fset:empty-bag) fm fq))) (fset:map (robot (if sm (fset:with first-material sm sq) first-material)))))) (defun can-build-robot-p (requirements available) (fset:subbag? requirements available)) (defun consume (requirements available) (fset:bag-difference available requirements)) (defun material-collect (bots available-materials) (fset:bag-sum bots available-materials)) (defun bot-requirements (type recipes) (fset:lookup recipes type)) (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))) (if (can-build-robot-p req resources) (values (consume req resources) (fset:with robots build-next)) (values resources robots)))) (defun next-step (build-next robots resources blueprints time-left) (if (zerop time-left) ;; (list robots resources) (fset:multiplicity resources :geode) ;; resources (let ((req (bot-requirements build-next blueprints))) (if (can-build-robot-p req resources) (reduce (lambda (max next) (max max (next-step next (fset:with robots build-next) (material-collect robots (consume req resources)) blueprints (1- time-left)))) '(:geode :obsidian :clay :ore) :initial-value 0) (next-step build-next robots (material-collect robots resources) blueprints (1- time-left)))))) (defun start-resources () ;; (list :ore 0 :clay 0 :obsidian 0 :geode 0) (fset:empty-bag)) (defun start-bots () ;; (list :ore 1 :clay 0 :obsidian 0 :geode 0) (fset:bag :ore)) (let ((blueprints (cdar (mapcar (lambda (l) (let ((blueprint (uiop:split-string l :separator ":."))) (cons (car blueprint) (reduce #'fset:map-union (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 24)) (next-step :clay bots materials blueprints time-left) )