day 16 monstrocity calculated overnight

This commit is contained in:
efim 2022-12-18 06:37:18 +00:00
parent 914abe5a1f
commit 3126cd48ab
1 changed files with 276 additions and 1 deletions

View File

@ -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,277 @@
;; CC : #<VERTICLE-DATA CC with flow: 1; is opened NIL {1004C57E33}>
;; DD : #<VERTICLE-DATA DD with flow: 1; is opened NIL {1004C57EB3}>
;; EE : #<VERTICLE-DATA EE with flow: 1; is opened T {1004C57F33}>
;;; 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)
;; FINAL CALCULATION
;; (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"?