diff --git a/day16-scratch-graph-utils.lisp b/day16-scratch-graph-utils.lisp index 5aa1707..96c3da3 100644 --- a/day16-scratch-graph-utils.lisp +++ b/day16-scratch-graph-utils.lisp @@ -741,7 +741,8 @@ -(visit-each-once-recursive-max-vented 'aa 30 *test-graph* *test-vertices-map* *test-shortest-paths*) +;; Calculating PART 1 +;; (visit-each-once-recursive-max-vented 'aa 30 *test-graph* *test-vertices-map* *test-shortest-paths*) ;; well 1570 is not correct 1651 ;; what am i missing? ;; i am opening starting Node with 0 flow @@ -753,3 +754,276 @@ ;; CC : # ;; DD : # ;; EE : # + +;;; PART 2 +;; need to compare 2 cases: +;; me running solo for 30 minutes +;; or two persons - me and elephant running for 26 minutes + +;; oh, shit. now i'd need coordination between recusive calls? for them to share the actual state. +;; or what? do i just on each step do twice decisions and not share state + +;; just a separate function, for 2 actors, would call both and compare +;; but it takes 1 turn to open valve. is that a problem? +;; it can be! because no guarantee that both start opening at same moment. +;; lots of changes then. need to iterate one step at a time +;; so person can be "in state, i just came in, turning the gas", i'm tired. let's go outside +;; so it's still better to be recursive, since calling for all possibilities. +;; but need to do iteration one tick at a time. setting state for p1 and p2 (for the next iteration) +;; and only adding value of cur-node-1|2 when it actually becoming turned on +;; and additional filtering out of other player's node +;; +;; +;; so, also "moving to other room" is not instantaneous +;; let's do: +;; cur-node-p1 +;; p1-state +;; cur-node-p2 +;; p2-state +;; +;; p.-state is current state or next state and should be excluded +;; +;; exit condition - if no more next nodes AND both players done opening +;; so let's do also DONE state, which is set to player when there's no more next nodes +;; and exit condition - both in DONE +;; +;; check that before DONE we added last room of the player to total +;; +;; I want to see if I could use lists and sybols as states: +;; OPENING '(GOING n) DONE where n should be readable number of how many turns of going left. +;; on (GOING 0) set next OPENING +;; on OPENING - since it's 1 turn - we should add target node to tally and set new GOING or DONE +;; +;; let's see if there's such thing as DESTRUCTURING CASE +(let + ;; ((x 'opening)) + ;; ((x '(going 0))) + ((x '(going 5))) + (cond + ((eq x 1) "one") + ((eq 'opening x) "opening") + ((equal x '(going 0)) + (format nil "were here! ~a~%" (second x))) + ((and (listp x) + (eq (first x) 'going)) + (format nil "going, ~a turns left" (second x))) + (t "default"))) + +(let ((x '(going 5))) + (case x + (1 "one") + ('opening "opening") + ((list going b) (format nil "going, ~a turns left~%" (second x))) + ((list going 0) (format nil "were here! ~a~%" (second x))) + (t "default"))) + +;; now what would I want to do baced on the state of the player? +;; set next state and do what? modify the state passing further +;; that's possible i think +;; ugh. ugh. +;; so, what - generate all next states for the player +;; and in addition i guess mutate the vertices-data-map for next call? ugh. +;; +;; if one player finished OPENING, then we for each of their possible next move create separate v-data-map +;; +;; what if we operate players sequentially, doing 2 player move ticks per 1 time tick. +;; that might be nice. then only need to update 1 player state. +;; could have "steping player state & node" as first two arguments, always switching them. +;; it would keep lots of logic same, as one player update. +;; +;; exit when both are done. that could be nice, yeah. +;; and if active player is done - just pass the turn to another. +;; when do I increment time? store "turn" increment it by 1 every recursion, and / 2 to deduct from total time +;; i guess it's ok + +;; removing other player node from possible nodes +(remove 'bb '((aa 5) (bb 7) (cc 1)) :test (lambda (removing-name name-and-dist) (eq removing-name (first name-and-dist)))) +(remove 'cc '((aa 5) (bb 7) (cc 1)) :test (lambda (removing-name name-and-dist) (eq removing-name (first name-and-dist)))) +(remove 'ee '((aa 5) (bb 7) (cc 1)) :test (lambda (removing-name name-and-dist) (eq removing-name (first name-and-dist)))) + +(defparameter *max-so-far* 0) + +(defun 2-persons-visit-each-once-recursive-max-vented (active-pl-node active-pl-state + inactive-pl-node inactive-pl-state + current-turn total-allotted-time + freed-to-end-gas-accum + graph vertices-data-map shortest-paths) + (let* ((time-left (- total-allotted-time (floor (/ current-turn 2)))) + (not-opened-possible-nodes (get-possible-next-vs active-pl-node graph vertices-data-map shortest-paths time-left)) + (possible-next-nodes (remove inactive-pl-node not-opened-possible-nodes ; remove other player target node from available + :test (lambda (removing-name name-and-dist) + (eq removing-name (first name-and-dist))))) + (cur-node-gas-per-turn (flow (gethash active-pl-node vertices-data-map))) + (for-open-current-total-release (max 0 ; in case we get to -1 time remaining + (* cur-node-gas-per-turn time-left) + ; freed gas after opening and staying + )) + + (next-turn (1+ current-turn))) + ;; (format t "Turn:~% + ;; active pl: state ~a, node ~a + ;; inactive pl: state ~a, node ~a + ;; accum: ~a + ;; possible next: ~a + ;; current turn: ~a; time remaining ~a~%" + ;; active-pl-state active-pl-node + ;; inactive-pl-state inactive-pl-node + ;; freed-to-end-gas-accum + ;; possible-next-nodes + ;; current-turn time-left) + (when (> freed-to-end-gas-accum *max-so-far*) + (setq *max-so-far* freed-to-end-gas-accum) + (format t "updating max so far to: ~a~%" *max-so-far*) + ) + (cond + ((and (eq active-pl-state 'DONE) + (eq inactive-pl-state 'DONE)) + ;; (print "Total DONE processing") + ;; recursion EXIT condition - both are done, + ;; before becoming DONE and yielding turn previous player + ;; counted how much steam is added "until the end of allotted-time" and added to accum + freed-to-end-gas-accum) + ((eq active-pl-state 'DONE) + ;; (print "One player DONE") + ;; active player is DONE but not another, tick the turn and yield the turn + (2-persons-visit-each-once-recursive-max-vented + inactive-pl-node inactive-pl-state + active-pl-node 'DONE ; keep staying DONE + next-turn total-allotted-time + freed-to-end-gas-accum + graph vertices-data-map shortest-paths)) + ((and (listp active-pl-state) + (equal active-pl-state '(going 1))) ; am i here off by one? + ;; (print "Processing ARRIVAL") + ;; active player came to target state, set to OPENING + (2-persons-visit-each-once-recursive-max-vented + inactive-pl-node inactive-pl-state + active-pl-node 'OPENING ; for one turn + next-turn total-allotted-time + freed-to-end-gas-accum + graph vertices-data-map shortest-paths)) + ((and (listp active-pl-state) + (listp inactive-pl-state) + (equal (first active-pl-state) 'going) + (equal (first inactive-pl-state) 'going) + (not (= 1 (second inactive-pl-state)))) + ;; both are GOING, can find amount to decrement, decrement both and add 2*n to turn + (let ((common-skip (1- (min (second active-pl-state) (second inactive-pl-state))))) + ; for 5 and 3; min is 3, common diminish 2 - to get one of them to 1 + + (2-persons-visit-each-once-recursive-max-vented + active-pl-node `(going ,(- (second active-pl-state) common-skip)) + inactive-pl-node `(going ,(- (second inactive-pl-state) common-skip)) + (+ current-turn (* 2 common-skip)) total-allotted-time + freed-to-end-gas-accum + graph vertices-data-map shortest-paths) + ) + ) + + ((eq active-pl-state 'OPENING) + ;; (print "in OPENING processing") + ;; active player is OPENING, so it's already done. add current steam to final tally accum + ;; then select next state OR set to done + (let ((for-open-current-vertices-map (let ((table (alexandria:copy-hash-table vertices-data-map))) + (maphash (lambda (k v) + (setf (gethash k table) + (copy-instance v))) + table) + (setf (is-opened-p (gethash active-pl-node table)) + t ) + ;; well, this is still done on every turn, lazy would be nice + table))) + (if (not possible-next-nodes) + ;; set state to DONE + (2-persons-visit-each-once-recursive-max-vented + inactive-pl-node inactive-pl-state + active-pl-node 'DONE ; will exit recursion when both DONE + next-turn total-allotted-time + (+ freed-to-end-gas-accum for-open-current-total-release) + ; updated accum + graph + for-open-current-vertices-map ; updated State map + shortest-paths) + ;; call recursively for all possible states with the added accumulator + ;; else - there are some possible nodes to visit + ;; main loop - check if OPENING, GOING or what + (loop for (next-node dist) + in possible-next-nodes + for next-max = (if (= 0 cur-node-gas-per-turn) + ;; if we for some reason (for example on start) get to node with 0 flow + (2-persons-visit-each-once-recursive-max-vented + inactive-pl-node inactive-pl-state + next-node `(going ,dist) + ; starting to GO to selected NODe + next-turn total-allotted-time + freed-to-end-gas-accum ; same accum + graph + vertices-data-map ; for 0 flow - NOT updated State map + shortest-paths) + ;; if were in NONZERO FLOW node - add to accum + (2-persons-visit-each-once-recursive-max-vented + inactive-pl-node inactive-pl-state + next-node `(going ,dist) + ; starting to GO to selected NODe + next-turn total-allotted-time + (+ freed-to-end-gas-accum for-open-current-total-release) + ; updated accum + graph + for-open-current-vertices-map ; updated State map + shortest-paths) + ) + maximize next-max into max-freed + finally (return max-freed))))) + ((and (listp active-pl-state) + (eq (first active-pl-state) + 'going)) + ;; (print "Processing GOING") + ;; active player is still going + ;; switch active player and increment turn + (2-persons-visit-each-once-recursive-max-vented + inactive-pl-node inactive-pl-state + active-pl-node `(going ,(1- (second active-pl-state))) + ; one step less + next-turn total-allotted-time + freed-to-end-gas-accum + graph vertices-data-map shortest-paths) + + ) + (t "SHOULD NOT HAPPEN") + ))) + +;; to start up - set turn 0, allotted time 24. both players as OPENING 'aa starting turn +;; HERE INIT +(defparameter *test-graph* (graph-utils:make-graph)) +(defparameter *test-vertices-map* (make-hash-table)) +(read-file-data "day16-input.txt" *test-graph* *test-vertices-map*) +;; (read-file-data "day16-test.txt" *test-graph* *test-vertices-map*) +;; (read-file-data "day16-simpler-test.txt" *test-graph* *test-vertices-map*) +;; (read-file-data "day16-even-simpler-test.txt" *test-graph* *test-vertices-map*) +(defparameter *test-shortest-paths* (graph-utils:all-pairs-shortest-paths *test-graph*)) +(print-hashmap *test-shortest-paths*) +(get-possible-next-vs 'aa *test-graph* *test-vertices-map* *test-shortest-paths* 30) + + +(2-persons-visit-each-once-recursive-max-vented 'aa 'opening 'aa 'opening + 0 26 0 + *test-graph* *test-vertices-map* *test-shortest-paths*) +;; 1482 is less than 1707 +;; not 1500 is less than 1707 +;; now 1581 is still less than 1707 +;; yep, off-by-one in OPENING -> GOING n. should be n-1 + +;; so, after 2 ticks, we have 26 - / 2 2 25 time remaining + +;; now let's calculate for main task + +;; Turn: +;; active pl: state (GOING 4), node DO +;; inactive pl: state (GOING 4), node UX +;; accum: 1336 +;; possible next: ((KZ 3) (JM 5) (NO 3)) +;; current turn: 37; time remaining 8 +;; +;; what were you doing for 24-8 = 16 turns each? well, walking between rooms, yeah +;; +;; while it runs, let's add printing "max so far"?