;; https://adventofcode.com/2022/day/19 ;; whelp. do i do somehow DP? generic over parameters of blueprints somehow ;; we could potentially calculate optimal step in forward time, but i'm not sure how. ;; and backward time - no easy way to know which robots will be available? ;; ;; intuitive idea, have some state of (resource1, recource2, robot1, robot2) ;; and have 2 types of steps, one - production is simple ;; then we can have different types of actions - build robot1 \ build robot2 - parallel these out ;; somehow prune ideally, but then move forward in time and get max of these. i guess it's ok plan ;; so. ;; - each robot collects 1 of it's own resource per minute ;; so function to update state with new resources is common ;; - cost of one new robot, consumed immediately, robot is produced in 1 minute ;; costs are different and listed in the bluerint ;; i suppose overall format of the blueprint is the same, only numbers change. ;; so could hardcode the translation ;; ;; and i guess i'd have one class of "blueprint" and several instances, ;; each instance does the updating of the state. (in-package :day-19) (defclass state () ((geodes :initform 0 :initarg :geodes) (ore :initform 0 :initarg :ore) (clay :initform 0 :initarg :clay) (obsidian :initform 0 :initarg :obsidian) (geodes-robot :initform 0 :initarg :geodes-robot) (ore-robot :initform 1 :initarg :ore-robot) (clay-robot :initform 0 :initarg :clay-robot) (obsidian-robot :initform 1 :initarg :obsidian-robot))) (defclass blueprint () ((ore-robot-cost :initarg :ore) (clay-robot-cost :initarg :clay) (obsidian-robot-cost :initarg :obsidian) (geode-robot-cost :initarg :geode))) ;; wrote like this initially (make-instance 'state) ;; i'd like to have code across building robots, checking whether robot can be build ;; to be shared. would i want to do it with classes? ;; state could have hashmaps from symbol to the amount ;; do i want to have plists? (defparameter *my-plist* '(:ore 1 :clay 15)) *my-plist* (getf *my-plist* :ore) (getf *my-plist* :clay) (setf (getf *my-plist* :clay) 3) ;; i guess i like that ;; then i could have robot costs as plists as well? ;; i could iterate over the plist with destructuring ;; and blueprint can just be plist of plists ;; we don't really need generic method dispatch ;; but for state - i'd kind of want to have static field ;; to share found max and pruning (loop for (resource cost) on '(:ore 3 :clay 14) by #'cddr ;; for (resource cost) in (getf *test-blueprint* :obsidian) collect (list (* 2 cost) 'is 'cost resource) ) ;; yes. comprehension is possible (defparameter *test-state* (make-instance 'state)) (setf (getf (resources *test-state*) :ore 0) 5) (setf (getf (robots *test-state*) :ore 0) 2) (setf (getf (robots *test-state*) :obsidian 0) 5) ;; now let's check my function for can-create (print *test-state*) (can-create-robot *test-blueprint* :ore *test-state*) (can-create-robot *test-blueprint* :clay *test-state*) (can-create-robot *test-blueprint* :geode *test-state*) (can-create-robot *test-blueprint* :obsidian *test-state*) ;; yay, i guess ;; ;; i had the error of putting quoted lists into the quoted list ;; ;; and now function to create new state with resources for a particular robot deducted? ;; and i guess with that robot already increased. ;; that function would signify passing of one turn \ minute (setq *test-state* (make-instance 'state)) (setf (getf (resources *test-state*) :ore 0) 3) (defparameter *another-state* (create-robot *test-blueprint* :clay *test-state*)) ;; well, that seems to work. ;; now i'd need to create blueprints from the lines. ;; then for each of the blueprint, calculate maximum of geodes. ;; multiply with the :id and sum. ;; ;; ok, i guess. i'd want a function that takes in blueprint. ;; gets initial state. and recurses searching for maximum ;; maybe even saving into blueprint as well. ;; how would that recursion look? ;; ore is added at the end of the minute. ;; resources for building is taken out at the beginning of the minute ;; built bot is added at the end of the minute ;; ;; so, i could ;; - calculate resources to be added ;; - for each possible (on old resources) bot build ;; recurse with bot cost deducted and new resources added ;; so next functions would be ;; - resources-to-be-collected :: just the plist of additional resources that would be ;; generated in 1 turn ;; and ;; - add-resources :: modifying operation that would update state *test-state* (calc-resources-to-be-collected *test-state*) ;; lol. (add-resources '(:spagetty 1 :tuna 2) *test-state*) ;; and it works ;; so, now only main loop i suppose. and maybe-maybe later-later pruning (get-possible-bot-builds *test-blueprint* *test-state*) (defmethod find-max-geod (blueprints (s state) minute) ;; (format t "in step for ~a; with ~a~%" minute s) (if (= 25 minute) (getf (resources s) :geode 0) (progn (let* ((will-collect-this-minute (calc-resources-to-be-collected s)) (max-if-building (loop for bot-type in *add-types* for state-with-new-bot = (create-robot blueprints bot-type s) when state-with-new-bot maximize (progn (add-resources will-collect-this-minute state-with-new-bot) (find-max-geod blueprints state-with-new-bot (1+ minute))))) (if-not-building (let ((state-copy (copy-state s))) ;; (break) (add-resources will-collect-this-minute state-copy) (find-max-geod blueprints state-copy (1+ minute))))) (max (or max-if-building 0) if-not-building))))) ;; Blueprint 1: ;; Each ore robot costs 4 ore. ;; Each clay robot costs 2 ore. ;; Each obsidian robot costs 3 ore and 14 clay. ;; Each geode robot costs 2 ore and 7 obsidian. ;; Blueprint 2: ;; Each ore robot costs 2 ore. ;; Each clay robot costs 3 ore. ;; Each obsidian robot costs 3 ore and 8 clay. ;; Each geode robot costs 3 ore and 12 obsidian. ;; do i just test this? (setq *test-blueprint* '(:ore (:ore 4) :clay (:ore 2) :obsidian (:ore 3 :clay 14) :geode (:ore 2 :obsidian 7))) (setq *test-state* (make-instance 'state)) (print (find-max-geod *test-blueprint* *test-state* 1)) ;; => 0 ;; that's because i have no ability to "wait" ;; whoops ;; so. do i want, um. add one more attempted call after the loop in the iteration? ;; now. the looping is serious. ;; would it work for me to order keys geode first ;; now we seem to get geodes first, yay ;; maybe just run without printing? ;; let's check manually that when i do state copy, the plists are independent? (setq *test-state* (make-instance 'state)) *test-state* (setq *another-state* (copy-state *test-state*)) (incf (getf (resources *another-state*) :ore 0)) *another-state* (add-resources '(:seeds 151) *another-state*) (incf (getf (robots *another-state*) :obsidian 0)) ;; oh, i didn't check that state returned from the "create bot" is independent (setq *test-state* (make-instance 'state)) (add-resources '(:ore 10) *test-state*) (setq *another-state* (create-robot *test-blueprint* :clay *test-state*)) ;; ugh. resources stays shared. ;; WTF, why ;; manually create new list, i guess then then do set to the 'copied state'? ;; this is unpleasant ;; so, i guess use (copy-list ;; ok. the numbers seem ok. but this is long. ;; how do i trim this? ;; if on step 10 there's already a state with 3 obsidian machines. ;; does this state would always be ahead of ;; state on step 10 with 1 obsidian machine? ;; ;; it seems so! ;; for which reason? because if the state got 3 geode machines, it will be able to get more? ;; i suppose only when i reach case where each new step can add one more geod machine ;; only then i can guess the state is actually domeeneering? ;; but only over states with same amount of steps? ;; ok. let's commit what i have right now and try to trim?