Advent-of-Code/day19-scratch.lisp

215 lines
8.0 KiB
Common Lisp

;; 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?