If you have a lisp installation, emacs, org-mode, and org-babel support for lisp installed you can run this by:
- Starting slime (
M-x slime
) - Typing
C-c C-c
in the block initialize. - In the repl type
(in-package :aoc-2018-15)
- Typing
C-c C-c
in the block answers
(unless (find-package :cl-ppcre)
(ql:quickload "cl-ppcre"))
(unless (find-package :iterate)
(ql:quickload "iterate"))
(unless (find-package :parseq)
(ql:quickload "parseq"))
(unless (find-package :fiveam)
(ql:quickload "fiveam"))
<<packages>>
(defpackage :aoc-2018-15
(:use :common-lisp
:iterate
:parseq
:fiveam)
(:export :problem-a
:problem-b))
(in-package :aoc-2018-15)
This is an “oh jeez” one. The input isn’t bad, a simple grid where:
#
- wall
.
- open space
G
- goblin
E
- elf
The map is parsed into a struct consisting of all open cavern spaces, elves, and goblins. I’ve let the health and attack power be parameters.
(defun parse-map (lines &key (goblin-hp 200) (goblin-ap 3) (elf-hp 200) (elf-ap 3))
(let ((maxx (length (car lines)))
(maxy (length lines))
(elves (make-hash-table))
(goblins (make-hash-table))
(grid (make-hash-table)))
(iter (for y from 0)
(for line in lines)
(iter (for x from 0)
(for c in-string line)
(case c
(#\. (setf (gethash (complex x y) grid) t))
(#\G (setf (gethash (complex x y) grid) t)
(setf (gethash (complex x y) goblins)
(make-player :kind :goblin :position (complex x y) :hp goblin-hp :ap goblin-ap)))
(#\E (setf (gethash (complex x y) grid) t)
(setf (gethash (complex x y) elves)
(make-player :kind :elf :position (complex x y) :hp elf-hp :ap elf-ap)))
(otherwise nil))))
(make-game :grid grid :elves elves :goblins goblins :maxx maxx :maxy maxy :round 0)))
I’m going to represent the game with a simple struct: three hashtables
and the max x and y coordinates from the input (useful for printing
out). I’ve added round
to the game
struct so that I can resume
execution. This was from a general “lessons learned” over the past
couple weeks. Infinite loops happen, so now I put an upper-bound
whether it’s needed or not. In this case it worked out for me.
By keeping the round I was able to execute smaller numbers of the main game logic and evaluate progress, and then resume without having to start the whole game over. All that happened in the REPL so not much to show you with it here.
(defstruct game
grid
elves
goblins
maxx
maxy
round)
(defstruct player
kind
position
hp
ap)
(defun read-input (file)
(iter (for line in-file file using #'read-line)
(collect line)))
(defparameter *input*
(parse-map (read-input "input/15.txt")))
All order ties are in “reading order”. Since complex numbers aren’t
directly comparable, I’ll make a function that can compare them and
pass that to sort
. You can use any key, by default this will just
sort a list of complex numbers.
(defun reading-order (list &key (key #'identity))
(flet ((complex<= (c1 c2)
(or (< (imagpart c1) (imagpart c2))
(and (= (imagpart c1) (imagpart c2))
(< (realpart c1) (realpart c2)))
(= c1 c2))))
(sort list #'complex<= :key key)))
I needed this utility function often enough that I decided to make it its own thing. Given a position and a table with keys that are also complex number encoded positions, this will return the 4 neighbors (if they exist). I didn’t use it as fully as I should have, I may modify some areas to make use of it.
(defun get-neighbors (position table)
(iter (repeat 4)
(with offset = 1)
(when (gethash (+ position offset) table)
(collect (gethash (+ position offset) table)))
(setf offset (* offset #C(0 1)))))
I should add these to a library, I’ve needed them for several problems now.
I also want to be able to print the grid, for debugging purposes.
(defun print-grid (game)
(format t "Round #~d~%" (game-round game))
(let ((goblins (game-goblins game))
(elves (game-elves game))
(grid (game-grid game)))
(iter (for y from 0 below (game-maxy game))
(for players-in-row = nil)
(iter (for x from 0 below (game-maxx game))
(let ((coord (complex x y)))
(cond ((null (gethash coord grid))
(format t "#"))
((gethash coord goblins)
(format t "G")
(push (gethash coord goblins) players-in-row))
((gethash coord elves)
(format t "E")
(push (gethash coord elves) players-in-row))
(t (format t ".")))))
(iter (for p in players-in-row)
(case (player-kind p)
(:elf (format t " (E ~d ~d)" (player-hp p) (player-ap p)))
(:goblin (format t " (G ~d ~d)" (player-hp p) (player-ap p)))))
(format t "~%"))
(format t "Elf HP: ~d~%"
(iter (for (k v) in-hashtable elves)
(sum (player-hp v))))
(format t "# Elves: ~d~%" (hash-table-count elves))
(format t "Goblin HP: ~d~%"
(iter (for (k v) in-hashtable goblins)
(sum (player-hp v))))
(format t "# Goblins: ~d~%" (hash-table-count goblins))))
Some rules for the game:
- Attacks only happen horizontally and vertically. Preference to lowest health, and then reading order.
- Movement only happens horizontally and vertically, preference to reading order when there are multiple options.
- If a combatant is adjacent to an enemy at the start of their turn, they attack.
- If no adjacent combatants, the creature will:
- Determine the empty spaces next to all enemies.
- Determine if any of those are reachable.
- If they are, determine the shortest path to all reachable, adjacent-to-enemy, spaces.
- Reading order priority to which of those spaces they go to.
- Once a target position is selected, a route has to be chosen. Shortest path, again, but if there are multiple the tie goes to reading order priority.
- The game ends when any combatant finds that they’ve wiped out the enemy.
- Rounds end when all combatants have gone, so the game can end in the middle of a round for counting purposes.
The desired result for Part 1 is the number of full rounds multiplied by the sum of the HP of the survivors.
This is the main kernel of the game loop. My first version of player movement got unwieldy, so I cleared it all out and now I’m starting over.
(defun execute-round (game)
(let ((combatants nil)
(goblins (game-goblins game))
(elves (game-elves game)))
(iter (for (k v) in-hashtable goblins)
(push v combatants))
(iter (for (k v) in-hashtable elves)
(push v combatants))
(setf combatants (reading-order combatants :key #'player-position))
(iter (while combatants)
;; Get the current combatant
(let* ((current (pop combatants))
(enemies (case (player-kind current)
(:goblin elves)
(:elf goblins)))
(position (player-position current))
(neighbors (reading-order (get-neighbors position enemies) :key #'player-position)))
(when (= 0 (hash-table-count enemies))
(return nil))
(unless neighbors
(move-player current game))
(setf neighbors (reading-order (get-neighbors (player-position current) enemies) :key #'player-position))
(when neighbors
(setf neighbors (sort neighbors #'<= :key #'player-hp))
(setf neighbors (remove-if (lambda (v)
(> (player-hp v) (player-hp (car neighbors))))
neighbors))
(setf neighbors (reading-order neighbors :key #'player-position))
(let ((victim (car neighbors)))
(decf (player-hp victim) (player-ap current))
(when (<= (player-hp victim) 0)
(setf combatants (remove victim combatants))
(remhash (player-position victim) enemies))))
(finally (incf (game-round game))
(return t))))))
This is the part that stumped me before. I had parts of it, but it was too cumbersome to debug. The problem I ran into was that I was letting the players get caught up in loops with poor path finding. They need to actually move along a path towards the enemy, not just to the closest position (which is what I did the first time). I tore that all out, here’s v2.
Alright, I’m done working on this. But for anyone reading: This is the cause of my performance problems. My order here has the path searching stuff executed numerous times for each player. Really, it should just be run once, in reverse. Start at the player and generate the minimum spanning tree. Then the potential locations can be compared using the already computed distances.
(defun move-player (player game)
(let* ((enemies (case (player-kind player)
(:goblin (game-elves game))
(:elf (game-goblins game))))
(allies (case (player-kind player)
(:elf (game-elves game))
(:goblin (game-goblins game))))
(position (player-position player))
(potentials (intersection (in-range enemies game) (reachable-hash position game)))
(min-path (iter (for p in potentials)
(minimizing (path-length position p game)))))
(when min-path
(setf potentials
(remove-if (lambda (p)
(> (path-length position p game) min-path))
potentials))
(multiple-value-bind (distance step) (path-length position (car (reading-order potentials)) game)
(declare (ignore distance))
(remhash position allies)
(setf (player-position player) step)
(setf (gethash step allies) player)))))
If I understand it correctly, I need to find the path lengths (not Manhattan distance) to each of the potential targets. Then select the target with the shortest path distance, reading order to break ties. This is awful code. I’m sorry.
(defun path-length (start end game)
(let ((graph (make-hash-table))
(search nil)
(min-path-length (+ (game-maxx game) (game-maxy game))))
(setf (gethash end graph) 0)
;; start off with the initial points to consider
(iter (repeat 4)
(with offset = 1)
(when (open-spacep (+ end offset) game)
(push (+ end offset) search))
(setf offset (* offset #C(0 1))))
(iter (while search)
(let ((next (car (last search))))
(setf search (remove next search))
(setf (gethash next graph)
(iter (repeat 4)
(with offset = 1)
(when (and (open-spacep (+ next offset) game)
(not (gethash (+ next offset) graph)))
(push (+ next offset) search))
(when (gethash (+ next offset) graph)
(minimizing (1+ (gethash (+ next offset) graph))))
(setf offset (* offset #C(0 1)))))))
(setf min-path-length (iter (repeat 4)
(with offset = 1)
(when (gethash (+ start offset) graph)
(minimizing (1+ (gethash (+ start offset) graph))))
(setf offset (* offset #C(0 1)))))
(values min-path-length
(when min-path-length
(car (reading-order
(iter (repeat 4)
(with offset = 1)
(when (and (gethash (+ start offset) graph)
(= (gethash (+ start offset) graph) (1- min-path-length)))
(collecting (+ start offset)))
(setf offset (* offset #C(0 1))))))))))
This returns a list of all open spaces adjacent to enemies. This logic is used a lot in my program to test if there’s an open space.
(defun in-range (enemies game)
(let ((in-range nil))
(iter (for (k v) in-hashtable enemies)
(for offset = 1)
(iter (repeat 4)
(when (open-spacep (+ offset k) game)
(push (+ offset k) in-range))
(setf offset (* offset #C(0 1)))))
in-range))
open-spacep
replaces logic like this:
(and (not (gethash (+ offset k) enemies))
(not (gethash (+ offset k) allies))
(gethash (+ offset k) grid))
Which has shown up in a lot of my other functions.
(defun open-spacep (position game)
(and (not (gethash position (game-goblins game)))
(not (gethash position (game-elves game)))
(gethash position (game-grid game))))
(defun reachable (position game)
(let ((search (list position))
(reachable nil)
(grid (make-hash-table)))
(iter (for (k v) in-hashtable (game-grid game))
(when (open-spacep k game)
(setf (gethash k grid) t)))
(iter (while search)
(iter (repeat 4)
(with pos = (pop search))
(with offset = 1)
(when (gethash (+ pos offset) grid)
(push (+ pos offset) search)
(push (+ pos offset) reachable)
(remhash (+ pos offset) grid))
(setf offset (* offset #C(0 1)))))
reachable))
The round limit here is just to save me from infinite loops. But if you resume with the same game instance then this works out nicely to pause and restart if you want to play out portions of the game instead of the whole thing.
(defun run-game (game &optional (round-limit 1000))
(print-grid game)
(iter (while (execute-round game))
(for i from 0 below round-limit))
(print-grid game))
(defun solve-a (game)
(run-game game)
(let ((round (game-round game))
(besthp (max (iter (for (k v) in-hashtable (game-goblins game))
(sum (player-hp v)))
(iter (for (k v) in-hashtable (game-elves game))
(sum (player-hp v))))))
(format t "The game ended in Round ~d and the winning side had ~d hit points, for a score of ~d.~%"
round besthp (* round besthp))))
(defun problem-a () (format t "Problem 15 A: ~a~%" (solve-a *input*)))
I’m more annoyed at this one than interested in cleaning up my unmaintainable code. It’s inefficient and ugly throughout. But I got part 1 finally.
Part 2 asks us to run the game over and over increasing the elf attack power from the baseline of 3 until they’re just able to win, and then to report the round times the elf HP of that case.
I’m going to cheat a little bit on this one. I’m going to use the REPL and do a binary search.
3: Lost.
50: Won after 19 rounds with 1799 HP
25: Won after 27 rounds with 1628 HP
12: Won after 52 rounds with 1083 HP
6: Lost after 84 rounds.
9: Won after 72 rounds with 620 HP
7: Lost after 132 rounds.
8: Won after 85 round with 442 HP.
Dammit. The problem is actually: Without a single death. Starting back at 50.
50: All elves lived, 19 rounds and 1799 HP
25: All elves lived, 27 rounds and 1628 HP
12: One elf died
19: All elves lived, 34 rounds and 1439 HP
15: All elves lived, 45 rounds and 1280 HP
13: Lost an elf.
14: All elves lived, 43 rounds and 1187 HP: 51041
(defun problem-b () (format t "Problem 15 B: ~a~%" (identity *input*)))
<<game-struct>>
<<read-input>>
<<parse-map>>
<<reading-order>>
<<get-neighbors>>
<<print-grid>>
<<move-player>>
<<path-length>>
<<reachable>>
<<in-range>>
<<execute-round>>
<<run-game>>
<<solve-a>>
<<initialize>>
(defparameter *debug* nil)
<<structs>>
<<functions>>
<<input>>
<<problem-a>>
<<problem-b>>
(problem-a)
(problem-b)
Round #0 ################################ ##########..........############ ########G..................##### (G 200 3) #######..G.GG...............#### (G 200 3) (G 200 3) (G 200 3) #######....G.......#......###### (G 200 3) ########.G.G...............#E..# (E 200 3) (G 200 3) (G 200 3) #######G.................#.....# (G 200 3) ########.......................# ########G.....G....#.....##....# (G 200 3) (G 200 3) ########.....#....G.........#### (G 200 3) #########..........##....E.E#.## (E 200 3) (E 200 3) ##########G..G..........#####.## (G 200 3) (G 200 3) ##########....#####G....####E.## (E 200 3) (G 200 3) ######....G..#######.....#.....# (G 200 3) ###....#....#########......##### ####........#########..E...##### (E 200 3) ###.........#########......##### ####G....G..#########......##### (G 200 3) (G 200 3) ####..#.....#########....####### ######.......#######...E.####### (E 200 3) ###.G.....E.G.#####.....######## (G 200 3) (E 200 3) (G 200 3) #.....G........E.......######### (E 200 3) (G 200 3) #......#..#..####....#.######### #...#.........###.#..########### ##............###..############# ######.....E####..############## (E 200 3) ######...........############### #######....E....################ (E 200 3) ######...####...################ ######...###....################ ###.....###..##..############### ################################ Elf HP: 2000 # Elves: 10 Goblin HP: 4000 # Goblins: 20 Round #101 ################################ ##########..........############ ########...................##### #######.....................#### #######............#......###### ########...................#...# #######.........G........#.....# (G 200 3) ########........GG...G.........# (G 200 3) (G 131 3) (G 200 3) ########...........#..G..##....# (G 200 3) ########.....#..............#### #########.........G##G......#.## (G 152 3) (G 191 3) ##########..............#####.## ##########....#####.G...####..## (G 200 3) ######.....G.#######.G...#.....# (G 188 3) (G 200 3) ###....#....#########......##### ####........#########......##### ###........G#########......##### (G 92 3) ####.......G#########......##### (G 200 3) ####..#.....#########....####### ######.......#######.....####### ###...........#####.....######## #.................G....######### (G 200 3) #......#..#..####.G..#.######### (G 200 3) #...#.........###.#..########### ##............###..############# ######......####..############## ######...........############### #######.........################ ######...####...################ ######...###....################ ###.....###..##..############### ################################ Elf HP: 0 # Elves: 0 Goblin HP: 2554 # Goblins: 14 The game ended in Round 101 and the winning side had 2554 hit points, for a score of 257954. Problem 15 A: NIL Problem 15 B: #S(GAME :GRID #<HASH-TABLE :TEST EQL :COUNT 448 {10022AD6A3}> :ELVES #<HASH-TABLE :TEST EQL :COUNT 0 {1005127A93}> :GOBLINS #<HASH-TABLE :TEST EQL :COUNT 14 {1005127EB3}> :MAXX 32 :MAXY 32 :ROUND 101)
Some simple grids to test printing and single round advancement.
(let ((game (parse-map (list "######"
"#.GE.#"
"#....#"
"######"))))
(print-grid game)
(execute-round game)
(print-grid game))
Expected result: both start at 200 HP, both end at 197 HP.
Round #0 ###### #.GE.# (E 200 3) (G 200 3) #....# ###### Elf HP: 200 # Elves: 1 Goblin HP: 200 # Goblins: 1 Round #1 ###### #.GE.# (E 197 3) (G 197 3) #....# ###### Elf HP: 197 # Elves: 1 Goblin HP: 197 # Goblins: 1
(let ((game (parse-map (list "######"
"#.GE.#"
"#....#"
"######"))))
(run-game game))
The goblin should survive, and he does. He wins because he hits first.
Round #0 ###### #.GE.# (E 200 3) (G 200 3) #....# ###### Elf HP: 200 # Elves: 1 Goblin HP: 200 # Goblins: 1 Round #67 ###### #.G..# (G 2 3) #....# ###### Elf HP: 0 # Elves: 0 Goblin HP: 2 # Goblins: 1
(let ((game (parse-map (list "######"
"#.GE.#"
"#..G.#"
"######"))))
(run-game game))
Expected: Elf dies after 200/6 rounds (rounded down, so round 33). Top Goblin has HP reduced by 3 33 times to 101 HP. Bottom Goblin has full health. This gives me confidence my round count is correct.
Round #0 ###### #.GE.# (E 200 3) (G 200 3) #..G.# (G 200 3) ###### Elf HP: 200 # Elves: 1 Goblin HP: 400 # Goblins: 2 Round #33 ###### #.G..# (G 101 3) #..G.# (G 200 3) ###### Elf HP: 0 # Elves: 0 Goblin HP: 301 # Goblins: 2
(let ((game (parse-map (list "######"
"#..G.#"
"#....#"
"#..E.#"
"######"))))
(run-game game))
Expected: The Goblin moves one south, and is attacked on that same round by the Elf. This gives the Elf the advantage of first attack and he wins after 67 rounds.
Round #0 ###### #..G.# (G 200 3) #....# #..E.# (E 200 3) ###### Elf HP: 200 # Elves: 1 Goblin HP: 200 # Goblins: 1 Round #67 ###### #....# #..G.# (G 2 3) #....# ###### Elf HP: 0 # Elves: 0 Goblin HP: 2 # Goblins: 1
(let ((game (parse-map (list "#########"
"#G......#"
"#.E.#...#"
"#..##..G#"
"#...##..#"
"#...#...#"
"#.G...G.#"
"#.....G.#"
"#########"))))
(run-game game))
Round #0 ######### #G......# (G 200 3) #.E.#...# (E 200 3) #..##..G# (G 200 3) #...##..# #...#...# #.G...G.# (G 200 3) (G 200 3) #.....G.# (G 200 3) ######### Elf HP: 200 # Elves: 1 Goblin HP: 1000 # Goblins: 5 Round #20 ######### #.G.....# (G 137 3) #G.G#...# (G 200 3) (G 200 3) #.G##...# (G 200 3) #...##..# #.G.#...# (G 200 3) #.......# #.......# ######### Elf HP: 0 # Elves: 0 Goblin HP: 937 # Goblins: 5
(let ((game (parse-map (list "#######"
"#.E...#"
"#.#..G#"
"#.###.#"
"#E#G#G#"
"#...#G#"
"#######"))))
(run-game game))
Round #0 ####### #.E...# (E 200 3) #.#..G# (G 200 3) #.###.# #E#G#G# (G 200 3) (G 200 3) (E 200 3) #...#G# (G 200 3) ####### Elf HP: 400 # Elves: 2 Goblin HP: 800 # Goblins: 4 Round #54 ####### #.....# #.#G..# (G 200 3) #.###.# #.#.#.# #G.G#G# (G 200 3) (G 38 3) (G 98 3) ####### Elf HP: 0 # Elves: 0 Goblin HP: 536 # Goblins: 4
(let ((game (parse-map (list
"#######"
"#E.G#.#"
"#.#G..#"
"#G.#.G#"
"#G..#.#"
"#...E.#"
"#######"))))
(run-game game))
Round #0 ####### #E.G#.# (G 200 3) (E 200 3) #.#G..# (G 200 3) #G.#.G# (G 200 3) (G 200 3) #G..#.# (G 200 3) #...E.# (E 200 3) ####### Elf HP: 400 # Elves: 2 Goblin HP: 1000 # Goblins: 5 Round #35 ####### #G.G#.# (G 98 3) (G 200 3) #.#G..# (G 200 3) #..#..# #...#G# (G 95 3) #...G.# (G 200 3) ####### Elf HP: 0 # Elves: 0 Goblin HP: 793 # Goblins: 5
(let ((game (parse-map (list "#########"
"#G..G..G#"
"#.......#"
"#.......#"
"#G..E..G#"
"#.......#"
"#.......#"
"#G..G..G#"
"#########"))))
(iter (repeat 3)
(print-grid game)
(execute-round game))
(print-grid game))
Round #0 ######### #G..G..G# (G 200 3) (G 200 3) (G 200 3) #.......# #.......# #G..E..G# (G 200 3) (E 200 3) (G 200 3) #.......# #.......# #G..G..G# (G 200 3) (G 200 3) (G 200 3) ######### Elf HP: 200 # Elves: 1 Goblin HP: 1600 # Goblins: 8 Round #1 ######### #.G...G.# (G 200 3) (G 200 3) #...G...# (G 197 3) #...E..G# (G 200 3) (E 200 3) #.G.....# (G 200 3) #.......# #G..G..G# (G 200 3) (G 200 3) (G 200 3) #.......# ######### Elf HP: 200 # Elves: 1 Goblin HP: 1597 # Goblins: 8 Round #2 ######### #..G.G..# (G 200 3) (G 200 3) #...G...# (G 194 3) #.G.E.G.# (G 200 3) (E 197 3) (G 200 3) #.......# #G..G..G# (G 200 3) (G 200 3) (G 200 3) #.......# #.......# ######### Elf HP: 197 # Elves: 1 Goblin HP: 1594 # Goblins: 8 Round #3 ######### #.......# #..GGG..# (G 200 3) (G 191 3) (G 200 3) #..GEG..# (G 200 3) (E 185 3) (G 200 3) #G..G...# (G 200 3) (G 200 3) #......G# (G 200 3) #.......# #.......# ######### Elf HP: 185 # Elves: 1 Goblin HP: 1591 # Goblins: 8
(let ((game (parse-map (list "################################"
"#################.....##########"
"#################..#.###########"
"#################.........######"
"##################......########"
"#################G.GG###########"
"###############...#..###########"
"###############......G..########"
"############..G.........########"
"##########.G.....G......########"
"##########......#.........#..###"
"##########...................###"
"#########G..G.#####....E.G.E..##"
"######..G....#######...........#"
"#######.....#########.........##"
"#######..#..#########.....#.####"
"##########..#########..G.##..###"
"###########G#########...E...E.##"
"#########.G.#########..........#"
"#########GG..#######.......##.E#"
"######.G......#####...##########"
"#...##..G..............#########"
"#...#...........###..E.#########"
"#.G.............###...##########"
"#................###############"
"##.........E.....###############"
"###.#..............#############"
"###..G........E.....############"
"###......E..........############"
"###......#....#E#...############"
"###....####.#...##.#############"
"################################"))))
(run-game game))
The consensus seems to be that this one should be 68 rounds and 2812 health.
Round #0 ################################ #################.....########## #################..#.########### #################.........###### ##################......######## #################G.GG########### (G 200 3) (G 200 3) (G 200 3) ###############...#..########### ###############......G..######## (G 200 3) ############..G.........######## (G 200 3) ##########.G.....G......######## (G 200 3) (G 200 3) ##########......#.........#..### ##########...................### #########G..G.#####....E.G.E..## (E 200 3) (G 200 3) (E 200 3) (G 200 3) (G 200 3) ######..G....#######...........# (G 200 3) #######.....#########.........## #######..#..#########.....#.#### ##########..#########..G.##..### (G 200 3) ###########G#########...E...E.## (E 200 3) (E 200 3) (G 200 3) #########.G.#########..........# (G 200 3) #########GG..#######.......##.E# (E 200 3) (G 200 3) (G 200 3) ######.G......#####...########## (G 200 3) #...##..G..............######### (G 200 3) #...#...........###..E.######### (E 200 3) #.G.............###...########## (G 200 3) #................############### ##.........E.....############### (E 200 3) ###.#..............############# ###..G........E.....############ (E 200 3) (G 200 3) ###......E..........############ (E 200 3) ###......#....#E#...############ (E 200 3) ###....####.#...##.############# ################################ Elf HP: 2000 # Elves: 10 Goblin HP: 4000 # Goblins: 20 Round #68 ################################ #################.....########## #################..#.########### #################.........###### ##################......######## #################....########### ###############...#..########### ###############.........######## ############............######## ##########..............######## ##########......#.......G.#..### (G 200 3) ##########...................### #########.....#####......G....## (G 200 3) ######.......#######....G.G....# (G 119 3) (G 23 3) #######....G#########...GG....## (G 200 3) (G 188 3) (G 119 3) #######..#..#########.G.G.#.#### (G 200 3) (G 44 3) ##########..#########..G.##..### (G 200 3) ###########.#########.........## #########...#########..........# #########..G.#######.G.....##..# (G 200 3) (G 200 3) ######...GG.G.#####...########## (G 200 3) (G 200 3) (G 200 3) #...##.G...............######### (G 119 3) #...#....G......###....######### (G 200 3) #...............###...########## #................############### ##...............############### ###.#..............############# ###.................############ ###.................############ ###......#....#.#...############ ###....####.#...##.############# ################################ Elf HP: 0 # Elves: 0 Goblin HP: 2812 # Goblins: 17
(def-suite aoc.2018.15)
(in-suite aoc.2018.15)
(test reading-order
(is (equal `((#C(0 0) 4) (#C(1 1) 3))
(reading-order '((#C(1 1) 3) (#C(0 0) 4)) :key #'car))))
(run! 'aoc.2018.15)
Running test suite AOC.2018.15 Running test READING-ORDER . Did 1 check. Pass: 1 (100%) Skip: 0 ( 0%) Fail: 0 ( 0%)