13 static int debug_level
= -1;
17 debug_level
= atoi(getenv("DEBUG") ?: "0");
20 static void __attribute__((format(printf
, 2, 3)))
21 debug_raw(int level
, const char *fmt
, ...) {
24 debug_level
= atoi(getenv("DEBUG") ?: "0");
25 if (debug_level
>= level
) {
27 vfprintf(stderr
, fmt
, ap
);
31 #define debug(...) debug_raw(1, __VA_ARGS__)
33 static int __attribute__((noreturn
)) __attribute__((format(printf
, 1, 2)))
34 die(const char *fmt
, ...)
49 static int maxx
, maxy
;
52 uint64_t seen
; /* bitmap: 0-25 keys, 32-57 doors */
59 struct progress to_keys
[26];
61 static struct pt cursor
[4];
62 static struct pt keys
[26];
63 static struct pt doors
[26];
64 static int k
; /* number of keys */
66 enum dir
{ NONE
= -1, U
, R
, D
, L
};
72 if (c
< 'a' || c
> 'z')
73 die("invalid key '%c'", c
);
74 return &keys
[c
- 'a'];
77 static uint64_t key_mask(char c
) {
78 if (c
< 'a' || c
> 'z')
79 die("invalid key '%c'", c
);
80 return 1 << (c
- 'a');
85 if (c
< 'A' || c
> 'Z')
86 die("invalid door '%c'", c
);
87 return &doors
[c
- 'A'];
90 static uint64_t door_mask(char c
) {
91 if (c
< 'A' || c
> 'Z')
92 die("invalid key '%c'", c
);
93 return 1ULL << (32 + (c
- 'A'));
97 minu(unsigned int a
, unsigned b
) {
107 for (y
= 0; y
< maxy
; y
++) {
108 for (x
= 0; x
<= maxx
; x
++)
109 debug(" %c", grid
[y
][x
].tile
);
110 for (x
= 0; x
< maxx
; x
++)
111 debug("%2d", grid
[y
][x
].distance
);
117 mask_to_str(uint64_t mask
) {
118 static char buf
[26 * 2 + 1];
121 for (i
= 0; i
< 26; i
++)
124 for (i
= 0; i
< 26; i
++)
125 if (mask
& (1ULL << (32 + i
)))
132 dump_point(struct pt
*p
, int k
) {
135 debug("point %c at %d,%d quadrant %d:\n", p
->id
, p
->x
, p
->y
, p
->q
);
136 for (i
= 0; i
< k
; i
++)
137 if (p
->to_keys
[i
].distance
== -1)
138 debug( "to key %c: impossible, reached by other bot\n", 'a' + i
);
140 debug(" to key %c: %d steps, encounter '%s'\n", 'a' + i
,
141 p
->to_keys
[i
].distance
, mask_to_str(p
->to_keys
[i
].seen
));
145 scan_one(int x
, int y
, enum dir from
, short d
, uint64_t seen
,
146 struct progress
*prog
, int q
) {
152 if (grid
[y
][x
].tile
== '#')
154 if ((unsigned) grid
[y
][x
].distance
< d
)
156 grid
[y
][x
].distance
= d
;
159 seen
|= door_mask(t
);
160 else if (islower(t
)) {
162 prog
[t
- 'a'].distance
= d
;
163 prog
[t
- 'a'].seen
= seen
;
170 r
= minu(r
, scan_one(x
, y
- 1, U
, d
, seen
, prog
, q
));
172 r
= minu(r
, scan_one(x
+ 1, y
, R
, d
, seen
, prog
, q
));
174 r
= minu(r
, scan_one(x
, y
+ 1, D
, d
, seen
, prog
, q
));
176 r
= minu(r
, scan_one(x
- 1, y
, L
, d
, seen
, prog
, q
));
181 scan_from(struct pt
*p
, int k
, int q
) {
184 debug("scanning from %d,%d (%c)\n", p
->x
, p
->y
, p
->id
);
185 for (j
= 0; j
< maxy
; j
++)
186 for (i
= 0; i
< maxx
; i
++)
187 grid
[j
][i
].distance
= -1;
188 r
= scan_one(p
->x
, p
->y
, NONE
, 0, 0, p
->to_keys
, q
);
193 /* https://en.wikipedia.org/wiki/A*_search_algorithm */
194 #define MAXWORK 10000
195 #define MAXNODE (128 * 1024)
196 #define QUAD(n) (((n) >> 28) & 3)
197 #define FROM_Q(n, q) (((n) >> (32 + (q) * 8)) & 0x1f)
198 #define FROM(n) FROM_Q(n, QUAD(n))
199 #define KEYS(n) ((n) & ((1 << 26) - 1))
201 long long n
; /* combination of QUAD, FROM and KEYS */
202 long long parent
; /* which node we were on previously */
203 short g
; /* cheapest known path from start to here */
204 short f
; /* estimate based on g + h(n) */
207 static void *root
; /* tree of nodes visited */
210 long long node
; /* node needing another visit */
211 short next
; /* index of next pending, sorted by decreasing f, -1 if none */
212 short prev
; /* index of prev node, -1 if none */
213 short f
; /* estimated distance */
215 static short head
= -1; /* head of pending, -1 if empty */
216 static short unused
; /* first unused index of pending */
217 static short avail
= -1; /* head of free list for reuse of slots in pending */
220 drop(long long node
) {
223 if (pending
[i
].node
== node
) {
225 head
= pending
[i
].next
;
227 pending
[pending
[i
].prev
].next
= pending
[i
].next
;
228 if (pending
[i
].next
!= -1)
229 pending
[pending
[i
].next
].prev
= pending
[i
].prev
;
231 pending
[i
].next
= avail
;
241 add(long long node
, int f
) {
247 avail
= pending
[next
].next
;
250 if (++unused
== MAXWORK
)
251 die("recompile with larger MAXWORK");
253 pending
[next
].node
= node
;
255 if (head
== -1 || f
< pending
[head
].f
) {
256 pending
[next
].next
= head
;
257 pending
[next
].prev
= -1;
261 while (pending
[i
].next
!= -1 && f
>= pending
[pending
[i
].next
].f
)
263 pending
[next
].next
= pending
[i
].next
;
264 pending
[next
].prev
= i
;
265 pending
[i
].next
= next
;
267 if (pending
[next
].next
!= -1)
268 pending
[pending
[next
].next
].prev
= next
;
272 compare(const void *pa
, const void *pb
) {
273 const struct node
*a
= pa
, *b
= pb
;
275 return (b
->n
> a
->n
) - (b
->n
< a
->n
);
279 lookup(long long node
) {
280 struct node
*n
= &nodes
[nnodes
];
284 p
= tsearch(n
, &root
, compare
);
286 die("out of memory");
287 if (*(struct node
**)p
== n
&& ++nnodes
== MAXNODE
)
288 die("recompile with larger MAXNODE");
289 return *(struct node
**)p
;
293 path(long long node
, int q
) {
299 if (n
->parent
== -1) {
300 puts("<uncomputed...>");
306 else if (QUAD(n
->n
) == q
)
307 putchar(FROM_Q(n
->n
, q
) + 'a');
313 h(long long n
, int bots
) {
316 for (q
= 0; q
< bots
; q
++) {
317 int from
= FROM_Q(n
, q
);
318 struct pt
*p
= from
== 0x1f ? &cursor
[q
] : &keys
[from
];
321 for (i
= 0; i
< k
; i
++)
322 if (!(n
& (1 << i
)) && p
->q
== q
&& p
->to_keys
[i
].distance
> b
)
323 b
= p
->to_keys
[i
].distance
;
334 from_str(long long n
, int bots
) {
335 static char buf
[5] = "";
338 for (i
= 0; i
< bots
; i
++)
339 buf
[i
] = FROM_Q(n
, i
) == 0x1f ? '@' : FROM_Q(n
, i
) + 'a';
352 memset(nodes
, -1, sizeof nodes
);
354 for (q
= 0; q
< bots
; q
++)
355 next
|= 0x1fLL
<< (32 + q
* 8);
358 n
->f
= h(next
, bots
);
361 long long current
= pending
[head
].node
;
368 if (KEYS(n
->n
) == (1 << k
) - 1) {
369 printf("search done with %d iterations, %d nodes, %d depth\n",
370 count
, nnodes
, unused
);
372 n2
->parent
= current
;
376 debug("considering %07llx at %s from quad %lld\n", KEYS(n
->n
),
377 from_str(n
->n
, bots
), QUAD(n
->n
));
379 for (i
= 0; i
< k
; i
++) {
380 if (current
& (1 << i
))
381 continue; /* already have key */
383 p
= FROM_Q(n
->n
, q
) == 0x1f ? &cursor
[q
] : &keys
[FROM_Q(n
->n
, q
)];
384 if (((p
->to_keys
[i
].seen
>> 32) | (int)p
->to_keys
[i
].seen
)
385 & ~(KEYS(n
->n
) | (1 << i
)))
386 continue; /* still blocked by door, or will reach another key first */
387 tentative
= n
->g
+ p
->to_keys
[i
].distance
;
388 next
= current
| (1 << i
);
389 next
&= ~((0x1fLL
<< (32 + q
* 8)) | (3 << 28));
390 next
|= ((0LL + i
) << (32 + q
* 8)) | (q
<< 28);
392 if (tentative
< n2
->g
) {
393 n2
->parent
= current
;
395 n2
->f
= tentative
+ h(next
, bots
);
398 debug("need to visit %07llx through %s quad %d, steps %d + %d = %d\n",
399 KEYS(n2
->n
), from_str(next
, bots
), q
, n2
->g
, n2
->f
-n2
->g
, n2
->f
);
403 die("no path found");
407 main(int argc
, char **argv
) {
415 if (argc
> 1 && strcmp(argv
[1], "-"))
416 if (!(stdin
= freopen(argv
[1], "r", stdin
))) {
421 while ((ch
= getchar()) != EOF
) {
422 grid
[y
][x
].tile
= ch
;
425 die("uneven line lengths");
431 die("recompile with larger MAX y");
435 } else if (ch
>= 'a' && ch
<= 'z') {
438 } else if (ch
>= 'A' && ch
<= 'Z') {
441 } else if (ch
!= '.' && ch
!= '#')
442 die("unexpected character %c", ch
);
446 die("duplicate character %c", ch
);
453 die("expected wall in row 0");
455 die("recompile with larger MAX x");
457 } else if (x
> maxx
) {
458 die("uneven line length");
462 for (i
= 0; i
< k
; i
++)
464 die("nonconsecutive keys");
467 if (d
!= count
|| d
> k
)
468 die("mismatched doors");
470 die ("missing cursor");
471 printf("Operating on %dx%d grid from %d,%d, with %d keys, %d doors\n",
472 maxx
, maxy
, cursor
[0].x
, cursor
[0].y
, k
, d
);
474 for (i
= 0; i
< k
; i
++)
476 scan_from(door(i
+ 'A'), k
, 0);
477 for (i
= 0; i
< k
; i
++)
478 scan_from(key(i
+ 'a'), k
, 0);
479 best
= scan_from(&cursor
[0], k
, 0);
480 printf("current closest key %d steps away\n", best
);
481 printf("all paths explored, furthest distance %d\n", deepest
);
482 if (deepest
* k
> SHRT_MAX
)
483 die("recompile with 32-bit distance");
486 printf("best path requires %d steps, using path ", best
);
491 if (grid
[y
- 1][x
- 1].tile
!= '.' || grid
[y
- 1][x
].tile
!= '.' ||
492 grid
[y
- 1][x
+ 1].tile
!= '.' || grid
[y
][x
- 1].tile
!= '.' ||
493 grid
[y
][x
+ 1].tile
!= '.' || grid
[y
+ 1][x
- 1].tile
!= '.' ||
494 grid
[y
+ 1][x
].tile
!= '.' || grid
[y
+ 1][x
+ 1].tile
!= '.')
495 die("grid not suitable for part 2");
496 grid
[y
- 1][x
].tile
= '#';
497 grid
[y
][x
- 1].tile
= '#';
498 grid
[y
+ 1][x
].tile
= '#';
499 grid
[y
][x
+ 1].tile
= '#';
502 for (i
= 0; i
< k
; i
++) {
503 memset(key(i
+ 'a')->to_keys
, -1, sizeof p
->to_keys
);
504 scan_from(key(i
+ 'a'), k
, 0);
508 cursor
[1] = cursor
[0];
510 cursor
[2] = cursor
[0];
513 cursor
[3] = cursor
[0];
515 for (i
= 0; i
< 4; i
++) {
516 memset(cursor
[i
].to_keys
, -1, sizeof cursor
[i
].to_keys
);
518 scan_from(&cursor
[i
], k
, i
);
523 memset(pending
, -1, sizeof pending
);
525 printf("using 4 bots requires %d steps, with path:\n", best
);
526 for (i
= 0; i
< 4; i
++)