Index: day15.rkt ================================================================== --- day15.rkt +++ day15.rkt @@ -8,36 +8,58 @@ threading) ;; Good ol’ positions. (struct posn (x y) #:transparent) -;; The concept of “reading order” is an important one in this puzzle. We can use -;; this comparison function with `sort` to ensure a list of positions is sorted -;; according to how you’d encounter them reading left-to-right, top to bottom. -;; This is where I mention that this program views 0,0 as “top left”. +;; The concept of “reading order” is an important one in this puzzle. Fighters move, +;; targets and paths are chosen in order of how you’d encounter them reading the grid +;; left-to-right, top to bottom. +;; The two functions below are going to do all the work of determining sort order +;; for us, whenever we need it. +;; This is also where I mention that this program views 0,0 as “top left”. (define (posnindex g x y) - (+ (* (grid-cols g) y) x)) +;; The “target” tracks the end-point of the path the grid describes (when used as a +;; “path map”, see below +(struct grid (vec rows cols start target) #:transparent) + +;; Translate between an x,y pair of coordinates and an index into the grid vector +(define (posn→index g p) + (+ (* (grid-cols g) (posn-y p)) (posn-x p))) + +(define (index→posn g i) + (posn (modulo i (grid-cols g)) + (quotient i (grid-cols g)))) + +;; This will come in handy later. +;; A path-step is a sub-type of posn with +(struct path-step (dist) #:super struct:posn #:transparent) ;; Create a grid from a list of strings each representing a row, filling each ;; spot with the corresponding character in the string (define (lines->grid line-strs) (define row-count (length line-strs)) @@ -44,11 +66,13 @@ (define col-count (string-length (first line-strs))) (grid (apply vector-append (map list->vector (map string->list line-strs))) row-count - col-count)) + col-count + #f + #f)) (define test-map (lines->grid '("#######" "#E..G.#" @@ -58,28 +82,26 @@ ;; Grids and Positions: put them together ;; Reference the value at given position in a grid (define (grid-ref g p) - (match-define (posn x y) p) - (vector-ref (grid-vec g) (coords->index g x y))) + (vector-ref (grid-vec g) (posn→index g p))) ;; Change the value at given position (define (grid-mark! g pos v) - (match-define (posn x y) pos) - (vector-set! (grid-vec g) (coords->index g x y) v)) + (vector-set! (grid-vec g) (posn→index g pos) v)) ;; Used to determine if a fighter could move into a given spot. ;; Anything besides "." counts as an obstruction (incl. other fighters) (define (grid-clear-at? g p) (equal? (grid-ref g p) #\.)) ;; Make a blank grid of the same dimensions, for use in making “path grids” (see ;; further below) -(define (copy-blank-grid g) - (match-define (grid _ rows cols) g) - (grid (make-vector (* rows cols) #f) rows cols)) +(define (copy-blank-grid g [start #f] [target #f]) + (match-define (grid _ rows cols _) g) + (grid (make-vector (* rows cols) #f) rows cols start target)) ;; (For debugging) Represent the grid as a square of single-character values (define (display-grid g [g2 #f]) (define grid-size (* (grid-cols g) (grid-rows g))) (display @@ -116,11 +138,11 @@ (posn x (- y 1))))) ;; Get all the EMPTY neighboring points of a given spot OR list of spots. ;; If a (listof posn?) is passed, ensures the returned list does not include ;; any of the original positions. -(define (free-neighbors-at world pos) +(define (free-neighbors-of world pos) (cond [(posn? pos) (~> (neighbor-coords world pos) (filter (curry grid-clear-at? world) _))] [(list? pos) (~> (map (curry neighbor-coords world) pos) @@ -135,18 +157,86 @@ (define (not-yet-checked? pmap pos) (not (grid-ref pmap pos))) ;; Find the most direct path(s) to a fighter from an end-position (define (path-grid world f end-pos) - (define result-grid (copy-blank-grid world)) - (define goal-pts (free-neighbors-at world f)) + (define result-grid (copy-blank-grid world f end-pos)) + (define goal-pts (free-neighbors-of world f)) (grid-mark! result-grid end-pos 0) (let loop ([pts-to-check (list end-pos)] [i 1]) - (define new-coords (~> (free-neighbors-at world pts-to-check) + (define new-coords (~> (free-neighbors-of world pts-to-check) (filter (curry not-yet-checked? result-grid) _))) (for-each (lambda (p) (grid-mark! result-grid p i)) new-coords) (cond [(not (empty? (set-intersect new-coords goal-pts))) result-grid] [(empty? new-coords) #f] [else (loop new-coords (+ 1 i))]))) + +;; What is the distance of the path represented by a path-map? +(define (path-distance pmap) + (~>> (neighbor-coords pmap (grid-start pmap)) + (filter-map (curry grid-ref pmap)) ; Weeds out #f values + (apply min))) + +;; Get only the shortest path(s) from a list of path maps +(define (shortest plst) + (define shortest-distance (apply min (map (curry path-distance f) plst))) + (define (among-shortest? pmap) (equal? shortest-distance (path-distance pmap))) + (filter among-shortest? plst)) + +;; What is the actual next step a fighter should take along a particular path, +;; breaking ties by reading order? +(define (next-step pmap f) + (define (coord→step c) (cond [c (path-step (posn-x c) (posn-y c) (grid-ref pmap c))] [else #f])) + (define possibles + (~>> (neighbor-coords f) + (filter-map coord→step))) + (define min-dist (min (map path-step-dist possibles))) + (~>> (filter (λ (ps) (equal? min-dist (path-step-dist ps))) possibles) + reading-order + first)) + + + +;; +;; Let’s start doing stuff with fighters + +;; Make a list of fighters from a grid, with the results in reading order. +(define (grid->fighters g) + (for/fold ([fighters '()] + #:result (reading-order fighters)) + ([val (in-vector (grid-vec g))] + [idx (in-naturals)]) + (cond [(member val '(#\G #\E)) + (match-define (posn x y) (index→posn g idx)) + (cons (fighter x y val STARTING-HP) fighters)] + [else fighters]))) + +(define (fighter-located-in? f lst) + (not (empty? (filter (curry posn=? f) lst)))) + +(define (enemies? f1 f2) + (not (equal? (fighter-type f1) (fighter-type f2)))) + +(define (enemies-of f1 flst) + (filter (curry enemies? f1) flst)) + +(define (adjacent-enemies world f all-enemies) + (define adjacent-posns (neighbor-coords world f)) + (filter (curryr fighter-located-in? adjacent-posns) all-enemies)) + +(define (fighter-alive? f) + (positive? (fighter-hp f))) + +(define (determine-next-move world f enemies) + #t) + +(define fs (grid->fighters test-map)) +(define f (first fs)) +(define es (enemies-of f fs)) +(define possible-targets (free-neighbors-of test-map es)) +(define possible-paths (filter-map (curry path-grid test-map f) possible-targets)) + + +