/* aoc-c.c: Advent of Code 2021, day 23 parts 1 & 2 * * Copyright (C) 2022 Bruno Raoult ("br") * Licensed under the GNU General Public License v3.0 or later. * Some rights reserved. See COPYING. * * You should have received a copy of the GNU General Public License along with this * program. If not, see . * * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #include #include "pool.h" #include "debug.h" #include "bits.h" #include "list.h" typedef enum { A, B, C, D } amphipod_t; static const u64 cost[] = { 1, 10, 100, 1000 }; #define _HALLWAY 0x7F /* 0x000001111111 */ #define _ROOM_A 0xF00 /* 0x111100000000*/ #define _ROOM_B 0xF000 /* 0x1111000000000000*/ #define _ROOM_C 0xF0000 /* 0x11110000000000000000*/ #define _ROOM_D 0xF00000 /* 0x111100000000000000000000*/ #define _ROOM 0xFFFF00 #define BIT(c) (1u << (c)) #define RAND_SEED 1 /* seed for random generator */ #define HASH_SIZE 3000017 /* prime */ static u32 rooms[4] = { _ROOM_A, _ROOM_B, _ROOM_C, _ROOM_D }; static u64 result = -1; typedef struct { u32 from, to; } move_t; typedef struct pos { u32 amp[4]; /* bitboards */ u32 final; /* bitboard: 1 if final destination */ int moves; u64 cost; /* cost till this position */ u64 eval; /* cost + estimation to final pos */ u32 occupied; u64 zobrist; /* for zobrist_2() */ move_t moves_list[64]; struct list_head list; } pos_t; /* All possible moves between hallway and rooms */ typedef struct { uint mask; /* blocking spaces */ uint dist; } possible_move_t; static possible_move_t moves[24][24]; typedef struct hash { u64 zobrist; /* zobrist hash */ u32 amp[4]; move_t moves_list[64]; int moves; struct list_head list; } hash_t; struct { u32 count; struct list_head list; } hasht[HASH_SIZE]; static inline u32 get_occupancy(pos_t *pos) { return pos->amp[A] | pos->amp[B] | pos->amp[C] | pos->amp[D]; } pool_t *pool_pos; pool_t *pool_hash; LIST_HEAD(pos_queue); static u64 zobrist_table[24][4]; char *cells[] = { "H1", "H2", "H3", "H4", "H5", "H6", "H7", "BAD", "A1", "A2", "A3", "A4", "B1", "B2", "B3", "B4", "C1", "C2", "C3", "C4", "D1", "D2", "D3", "D4", }; /* * ############# * #ab.c.d.e.fg# * ###h#i#j#k### * #l#m#n#o# * #p#q#r#s# * #t#u#v#w# * ######### * * We name a-g H1-H7, h & l are A1-A2, i & m are B1 & B2, etc... */ /* note that to determine of hallway space is left or right, * we simply need to divide the room number by 4. */ typedef enum { _H1 = 0, _H2, _H3, _H4, _H5, _H6, _H7, /* 0-6 */ _WRONG = 7, /* 7 */ _A1 = 8, _A2, _A3, _A4, /* 8-11 */ _B1, _B2, _B3, _B4, /* 12-15 */ _C1, _C2, _C3, _C4, /* 16-19 */ _D1, _D2, _D3, _D4, /* 20-23 */ } _space_t; #define HALLWAY(b) ((b) & _HALLWAY) #define ROOM(b) ((b) & _ROOM) #define IN_HALLWAY(c) ((int)(c) >= _H1 && (int)(c) <= _H7) #define IN_ROOM(c) (((int)(c) >= _A1 && (int)(c) <= _A4) || \ ((int)(c) >= _B1 && (int)(c) <= _B4) || \ ((int)(c) >= _C1 && (int)(c) <= _C4) || \ ((int)(c) >= _D1 && (int)(c) <= _D4)) #define HASH_SEEN (HASH_SIZE + 1) #define LEFT 0 #define RIGHT 1 static s32 room_exit[4][2][6] = { { { _H2, _H1, -1 }, /* room A left */ { _H3, _H4, _H5, _H6, _H7, -1 } /* room A right */ }, { { _H3, _H2, _H1, -1 }, /* room B left */ { _H4, _H5, _H6, _H7, -1 } /* room B right */ }, { { _H4, _H3, _H2, _H1, -1 }, /* room C left */ { _H5, _H6, _H7, -1 } /* room C right */ }, { { _H5, _H4, _H3, _H2, _H1, -1 }, /* room D left */ { _H6, _H7, -1 } /* room D right */ } }; static char *int2bin(u32 mask) { static char ret[64]; for (int i = 0; i < 32; ++i) { ret[31-i] = mask & BIT(i)? '1': '0'; } ret[32] = 0; return ret; } static char *int2pos(u32 mask) { static char res[1024]; char *p = res; for (int i = 0; i < 32; ++i) { if (mask & BIT(i)) { *p++ = ' '; strcpy(p, cells[i]); p += 2; } } return res; } static void moves_print(move_t *moves, int nmoves) { for (int i = 0; i < nmoves; ++i) printf(" %s-%s", cells[moves[i].from], cells[moves[i].to]); printf("\n"); } static void amp_print_flat(u32 *amp) { int rows = popcount32(*amp); for (int i = _H1; i <= _H7; ++i) { /* hallway */ if (i >= 2 && i <= 5) log(1, "$"); log(1, "%c", BIT(i) & amp[A]? 'A': BIT(i) & amp[B]? 'B': BIT(i) & amp[C]? 'C': BIT(i) & amp[D]? 'D': '.'); } for (int j = 0; j < 4; ++j) { log(1, " "); for (int i = 0; i < rows; ++i) { int offset = 8 + i; log(1, "%c", BIT(offset + 4 * j) & amp[A]? 'A': BIT(offset + 4 * j) & amp[B]? 'B': BIT(offset + 4 * j) & amp[C]? 'C': BIT(offset + 4 * j) & amp[D]? 'D': '.'); } } log(1, "\n"); } static void burrow_print_flat(pos_t *pos) { int rows = popcount32(pos->amp[0]); log_f(1, "rows=%d moves=%2d cost=%6lu: ", rows, pos->moves, pos->cost); amp_print_flat(pos->amp); } static void burrow_print(pos_t *pos) { u32 tmp; int count; int rows = popcount32(pos->amp[0]); log_f(1, "rows=%d moves=%d cost=%lu\n", rows, pos->moves, pos->cost); for (int i = 0; i < 4; ++i) { bit_for_each32_2(count, tmp, pos->amp[i]) { log(1, " %d", count); } } log(1, "\n"); log(1, "#############\n#"); for (int i = _H1; i <= _H7; ++i) { /* hallway */ if (i >= 2 && i <= 5) log(1, "$"); log(1, "%c", BIT(i) & pos->amp[A]? 'A': BIT(i) & pos->amp[B]? 'B': BIT(i) & pos->amp[C]? 'C': BIT(i) & pos->amp[D]? 'D': '.'); } log(1, "#\n"); for (int i = 0; i < rows; ++i) { int offset = 8 + i; log(1, i? " #": "###"); for (int j = 0; j < 4; ++j) { log(1, "%c#", BIT(offset + 4 * j) & pos->amp[A]? 'A': BIT(offset + 4 * j) & pos->amp[B]? 'B': BIT(offset + 4 * j) & pos->amp[C]? 'C': BIT(offset + 4 * j) & pos->amp[D]? 'D': '.'); //log(4, "%c#", bits & BIT(pos + 4 * j)? 'X': '.'); } log(1, i? "\n": "##\n"); } log(1, " #########\n"); } /* from https://stackoverflow.com/a/33021408/3079831 */ #define IMAX_BITS(m) ((m)/((m)%255+1) / 255%255*8 + 7-86/((m)%255+12)) #define RAND_MAX_WIDTH IMAX_BITS(RAND_MAX) _Static_assert((RAND_MAX & (RAND_MAX + 1u)) == 0, "RAND_MAX not a Mersenne number"); static u64 rand64(void) { u64 r = 0; for (int i = 0; i < 64; i += RAND_MAX_WIDTH) { r <<= RAND_MAX_WIDTH; r ^= (unsigned) rand(); } return r; } static void zobrist_init() { log_f(1, "seed=%d rand_max=%d\n", RAND_SEED, RAND_MAX); srand(RAND_SEED); for (int i = 0; i < 24; ++i) { for (int j = 0; j < 4; ++j) { zobrist_table[i][j] = rand64(); log(10, "%lu ", zobrist_table[i][j]); } } } static inline u64 zobrist_1(pos_t *pos) { u32 tmp; int bit; u64 zobrist = 0; for (int amp = 0; amp < 4; ++amp) { bit_for_each32_2(bit, tmp, pos->amp[amp]) { //log_f(2, "amp=%d/%c bit=%d\n", amp, amp+'A', bit); zobrist ^= zobrist_table[bit][amp]; } } log_f(1, "zobrist=%lu -> %lu\n", zobrist, zobrist % HASH_SIZE); return zobrist; } /* calculate zobrist hash from previous zobrist value */ static inline u64 zobrist_2(pos_t *pos, int amp, u32 from, u32 to) { u64 zobrist = pos->zobrist; zobrist ^= zobrist_table[from][amp]; zobrist ^= zobrist_table[to][amp]; log_f(1, "zobrist old=%lu new=%lu (%lu) (amp=%d from=%u to=%u)\n", pos->zobrist, zobrist, zobrist % HASH_SIZE, amp, from, to); if (zobrist % HASH_SIZE == 2321581) { log(1, "Zobi: "); burrow_print_flat(pos); log(1, "Zobi moves: "); moves_print(pos->moves_list, pos->moves); //for (int i = 0; i < pos->moves; ++i) { // log(1, " %s-%s", cells[pos->moves_list[i].from], cells[pos->moves_list[i].to]); //} //log(1, "\n"); } return zobrist; } static void hash_init() { for (int i = 0; i < HASH_SIZE; ++i) { hasht[i].count = 0; INIT_LIST_HEAD(&hasht[i].list); } } /* get a position from memory pool */ static hash_t *get_hash(pos_t *pos) { hash_t *new; if (!(new = pool_get(pool_hash))) return NULL; for (int i = 0; i < 4; ++i) new->amp[i] = pos->amp[i]; /* copy moves list */ for (int i = 0; i < 64; ++i) new->moves_list[i] = pos->moves_list[i]; new->moves = pos->moves; INIT_LIST_HEAD(&new->list); return new; } static void hash_stats() { u64 ncollisions = 0; u32 min = -1, max = 0, minpos = 0, maxpos = 0; for (int i = 0; i < HASH_SIZE; ++i) { ncollisions += hasht[i].count; if (hasht[i].count < min) { min = hasht[i].count; minpos = i; } else if (hasht[i].count > max) { max = hasht[i].count; maxpos = i; } } printf("hash: size=%d min=%u (pos=%u) max=%u (pos=%u) total=%lu average=%lu\n", HASH_SIZE, min, minpos, max, maxpos, ncollisions, ncollisions / HASH_SIZE); } static u64 hash(pos_t *pos, int amp, u32 from, u32 to) { hash_t *cur; u64 zobrist; int hashpos; //pos_t pos_tmp = *pos; log(1, "Zobi0A: amp=%c from=%s to=%s bfrom=%d bto=%d\n", amp + 'A', cells[from], cells[to], BIT(from), BIT(to)); //log(1, "Zobi00: "); //amp_print_flat(pos_tmp.amp); //pos_tmp.amp[amp] ^= BIT(from); //pos_tmp.amp[amp] |= BIT(to); //pos_tmp.moves_list[pos_tmp.moves].from = from; //pos_tmp.moves_list[pos_tmp.moves].to = to; //pos_tmp.moves++; log(1, "Zobi01: "); amp_print_flat(pos->amp); /* we use the 2 first chars of the amps 32, this should be enough * to avoid most collisions */ zobrist = zobrist_2(pos, amp, from, to); pos->zobrist = zobrist; hashpos = zobrist % HASH_SIZE; log_f(1, "zobrist=%lu->%u, count=%d\n", zobrist, hashpos, hasht[hashpos].count); list_for_each_entry(cur, &hasht[hashpos].list, list) { if (zobrist == cur->zobrist) { if (pos->amp[0] == cur->amp[0] && pos->amp[1] == cur->amp[1] && pos->amp[2] == cur->amp[2] && pos->amp[3] == cur->amp[3]) { log(1, "zobrist collision for same positions\n"); log(1, "Zobirst=%lu->%u, count=%d\n", zobrist, hashpos, hasht[hashpos].count); log(1, "Zobi cur: "); amp_print_flat(cur->amp); log(1, "Zobi cur: "); moves_print(cur->moves_list, cur->moves); log(1, "Zobi tmp: "); amp_print_flat(pos->amp); log(1, "Zobi tmp: "); moves_print(pos->moves_list, pos->moves); log(1, "Returning zobrist=HASH_SEEN\n"); return HASH_SEEN; } else { log(1, "zobrist collision for different positions:\n"); log(1, "Zobirst=%lu->%u, count=%d\n", zobrist, hashpos, hasht[hashpos].count); log(1, "Zobi cur: "); amp_print_flat(cur->amp); log(1, "Zobi cur: "); moves_print(cur->moves_list, cur->moves); log(1, "Zobi tmp: "); amp_print_flat(pos->amp); log(1, "Zobi tmp: "); moves_print(pos->moves_list, pos->moves); } } } hasht[hashpos].count++; log(1, "adding hash count=%u\n", hasht[hashpos].count); cur = get_hash(pos); cur->zobrist = zobrist; cur->amp[amp] ^= BIT(from); cur->amp[amp] |= BIT(to); cur->moves_list[cur->moves].from = from; cur->moves_list[cur->moves].to = to; cur->moves++; list_add(&cur->list, &hasht[hashpos].list); log(1, "Returning zobrist=%lu\n", zobrist); return zobrist; } /* evaluate current position : * eval = cost + eval_to_dest * cost is current cost * eval is cost to join top of destination room */ static int room2room_dist[][4] = { { 4, 4, 6, 8}, { 4, 4, 4, 6}, { 6, 4, 4, 4}, { 8, 6, 4, 4} }; /* static u64 eval_amp(pos_t *pos, int amp, u32 cell) */ /* { */ /* u32 bit = BIT(cell); */ /* int rows = popcount32(pos->amp[0]); */ /* int dist; */ /* int left = rows - popcount32(rooms[amp] & pos->final); */ /* if (ROOM(bit)) { /\* in a room *\/ */ /* int curroom = cell / 4 - 2; */ /* dist = cell % 4; /\* distance to top of room *\/ */ /* dist += room2room_dist[curroom][amp]; */ /* dist += --left; /\* distance to final room *\/ */ /* log(1, "eval %c room(%s) = %d\n", amp + 'A', cells[cell], dist); */ /* } else { /\* in hallway *\/ */ /* int dest = (amp + 2) * 4; */ /* dist = moves[cell][dest].dist; */ /* dist += --left; /\* distance to final room *\/ */ /* log(1, "eval %c hallway(%s) = %d\n", amp + 'A', cells[cell], dist); */ /* } */ /* return cost[amp] * dist; */ /* } */ static u64 eval(pos_t *pos) { u32 amp, tmp; int cell; int rows = popcount32(pos->amp[0]); u64 eval = 0; for (amp = A; amp <= D; ++amp) { u32 todo = pos->amp[amp] & ~pos->final; /* ignore finished ones */ int destroom = amp, dist; int left = rows - popcount32(rooms[destroom] & pos->final); printf("amp=%d %u final=%u\n", amp, pos->amp[amp], pos->final); printf("amp=%c pos=%s\n", amp + 'A', int2bin(pos->amp[amp])); printf("amp= tod=%s\n", int2bin(todo)); printf("amp= fin=%s\n", int2bin(pos->final)); bit_for_each32_2(cell, tmp, todo) { u32 bit = BIT(cell); if (ROOM(bit)) { /* in a room */ int curroom = cell / 4 - 2; dist = cell % 4; /* distance to top of room */ dist += room2room_dist[curroom][destroom]; dist += --left; /* distance to final room */ log(1, "eval %c room(%s) = %d\n", amp + 'A', cells[cell], dist); } else { /* in hallway */ int dest = (destroom + 2) * 4; dist = moves[cell][dest].dist; dist += --left; /* distance to final room */ log(1, "eval %c hallway(%s) = %d\n", amp + 'A', cells[cell], dist); } //printf("eval = %lu %lu\n", cost[amp] * dist, eval_amp(pos, amp, cell)); eval += cost[amp] * dist; } } //log_f(1, "pos=%p amp=%d from=%u to=%u\n", pos, amp, from, to); return eval; } /* * ############# * #ab.c.d.e.fg# * ###h#i#j#k### * #l#m#n#o# * #p#q#r#s# * #t#u#v#w# * ######### */ /* set bits between 2 positions * setbits(2, 4) -> 0011100 */ static u32 setbits(int from, int to) { u32 ret = 0; for (int i = from; i < to; ++i) ret |= BIT(i); log_f(4, "(%d, %d) = %d\n", from, to, ret); return ret; } static int h2h[][7] = { { 2, 1, 1, 3, 5, 7, 8 }, /* A */ { 4, 3, 1, 1, 3, 5, 6 }, /* B */ { 6, 5, 3, 1, 1, 3, 4 }, /* C */ { 8, 7, 5, 3, 1, 1, 2 } /* D */ }; /* get a position from memory pool */ static pos_t *get_pos(pos_t *from) { pos_t *new; if (!(new = pool_get(pool_pos))) return NULL; if (!from) { new->amp[0] = new->amp[1] = new->amp[2] = new->amp[3] = 0; new->final = new->occupied = new->zobrist = 0; new->cost = new->eval = 0; new->moves = 0; } else { *new = *from; } INIT_LIST_HEAD(&new->list); return new; } /* release a position from list */ static void free_pos(pos_t *pos) { if (pos) { pool_add(pool_pos, pos); } else { log(1, "Fatal bar\n"); exit(1); } } /* push position to stack */ static void push_pos(pos_t *pos) { pos_t *cur; if (!pos) { log(1, "Fatal foo\n"); exit(1); } if (!list_empty(&pos_queue)) { //list_add_tail(&pos->list, &pos_queue); //} else { list_for_each_entry(cur, &pos_queue, list) { if (cur->eval > pos->eval) { log(1, "adding %lu before %lu\n", pos->eval, cur->eval); list_add_tail(&pos->list, &cur->list); return; } else { log(1, "not adding %lu before %lu\n", pos->eval, cur->eval); } } } list_add_tail(&pos->list, &pos_queue); } /* pop a position from stack */ static pos_t *pop_pos() { pos_t *pos; pos = list_first_entry_or_null(&pos_queue, pos_t, list); if (pos) list_del(&pos->list); return pos; } static void print_moves(possible_move_t (*moves)[24]) { int hallway, room; log(4, "From rooms to hallway\n"); for (room = _A1; room <= _D4; ++room) { for (hallway = _H1; hallway <= _H7; ++hallway) { log(4, "%s -> %s ", cells[room], cells[hallway]); log(4, "dist=%d ", moves[room][hallway].dist); log(4, "mask="); log(4, "%s\n", int2pos(moves[room][hallway].mask)); } } log(4, "From hallway to rooms\n"); for (hallway = _H1; hallway <= _H7; ++hallway) { for (room = _A1; room <= _D4; ++room) { log(4, "%s -> %s ", cells[hallway], cells[room]); log(4, "dist=%d ", moves[hallway][room].dist); log(4, "mask="); log(4, "%s\n", int2pos(moves[hallway][room].mask)); } } } static void mask_print(u32 bits) { u32 tmp; int count; int rows = popcount32(bits); if (rows > 4) rows /= 4; log(10, "%u: popcount=%d ", bits, popcount32(bits)); bit_for_each32_2(count, tmp, bits) { log(10, " %d", count); } log(10, "\n"); log(10, "#############\n#"); for (int i = _H1; i <= _H7; ++i) { /* hallway */ if (i >= 2 && i <= 5) log(10, "$"); log(10, "%c", bits & BIT(i)? 'X': '.'); } log(10, "#\n"); for (int i = 0; i < rows; ++i) { int pos = 8 + i; log(10, i? " #": "###"); for (int j = 0; j < 4; ++j) { log(10, "%c#", bits & BIT(pos + 4 * j)? 'X': '.'); } log(10, i? "\n": "##\n"); } log(10, " #########\n"); } /* calculate distance and move mask for all possible moves * (room -> hallway ans hallway -> room) */ static possible_move_t (*init_moves())[24] { int hallway, room, dist, pos; u32 mask_h, mask_r; for (room = _A1; room <= _D4; ++room) { pos = (room >> 2) - 2; for (hallway = _H1; hallway <= _H7; ++hallway) { dist = h2h[pos][hallway] + room % 4 + 1; log(3, "\ndist(%s, %s) = %d - h2h=%d pos=%d\n", cells[room], cells[hallway], dist, h2h[pos][hallway], pos); /* from rool to hallway */ if (room >> 2 > hallway) mask_h = setbits(hallway, room >> 2); else mask_h = setbits(room >> 2, hallway + 1); mask_r = setbits(room & ~3, room); log(5, "\tmask_r=%d <%s>", mask_r, int2bin(mask_r)); log(5, "\tmask_h=%d <%s>\n", mask_h, int2bin(mask_h)); log(3, "%s\n", int2pos(mask_r | mask_h)); moves[room][hallway].mask = mask_r | mask_h; moves[room][hallway].dist = dist; /* from hallway to room */ if (room >> 2 > hallway) mask_h = setbits(hallway + 1, room >> 2); else mask_h = setbits(room >> 2, hallway); mask_r = setbits(room & ~3, room + 1); log(5, "\tmask_r=%d <%s>", mask_r, int2bin(mask_r)); log(5, "\tmask_h=%d <%s>\n", mask_h, int2bin(mask_h)); log(3, "%s\n", int2pos(mask_r | mask_h)); moves[hallway][room].mask = mask_r | mask_h; moves[hallway][room].dist = dist; } log(3, "\n"); } return moves; } static pos_t *newmove(pos_t *pos, amphipod_t amp, u32 from, u32 to) { int rows = popcount32(pos->amp[0]); possible_move_t *move = &moves[from][to]; pos_t *newpos, pos_tmp = *pos; u64 newcost = pos->cost + move->dist * cost[amp]; u32 bit_from = BIT(from), bit_to = BIT(to); int ok = popcount32(pos->final); u64 zobrist; log_f(1, "rows=%d amp=%c from=%s to=%s dist=%u ok=%d cost=%lu zobrist=%lu\n", rows, amp + 'A', cells[from], cells[to], move->dist, ok, move->dist * cost[amp], pos->zobrist); /* if ((IN_HALLWAY(from) && !IN_ROOM(to)) || (IN_ROOM(from) && !IN_HALLWAY(to)) || (HALLWAY(bit_from) && !ROOM(bit_to)) || (ROOM(bit_from) && !HALLWAY(bit_to))) { log(1, "BUG genmove!\n"); } */ if (newcost >= result) return NULL; pos_tmp = *pos; pos_tmp.amp[amp] ^= bit_from; pos_tmp.amp[amp] |= bit_to; pos_tmp.occupied ^= bit_from; pos_tmp.occupied |= bit_to; pos_tmp.moves_list[pos_tmp.moves].from = from; pos_tmp.moves_list[pos_tmp.moves].to = to; pos_tmp.moves++; pos_tmp.cost = newcost; if (ROOM(bit_to)) { /* to room */ //newpos->ok++; pos_tmp.final |= bit_to; log(1, "Final destination %s, ok=%d, final=%s\n", cells[to], popcount32(pos_tmp.final), int2pos(pos_tmp.final)); if (popcount32(pos_tmp.final) == rows * 4) { log(1, "found solution! cost=%lu\n", pos_tmp.cost); if (pos_tmp.cost < result) { result = pos_tmp.cost; log(1, "New best=%lu moves=%u List:", result, pos_tmp.moves); moves_print(pos_tmp.moves_list, pos_tmp.moves); } return NULL; } } /* final move, cannot fail */ /* if (ok == rows * 4 - 1) { u64 final = newcost; log(1, "found solution! cost=%lu\nZobi10 ", final); burrow_print_flat(pos); if (final < result) { result = final; log(1, "New best=%lu moves=%u+1 List:", result, pos->moves); for (int i = 0; i < pos->moves; ++i) { log(1, " %s-%s", cells[pos->moves_list[i].from], cells[pos->moves_list[i].to]); } log(1, "\n"); } } */ //if (zobrist != zobrist_1(&pos_tmp)) { // printf("Zobi Fuck1\n"); // exit(1); //} //if (zobrist != pos_tmp.zobrist) { // printf("Zobi Fuck2\n"); // exit(1); //} //zobrist = hash1(pos); zobrist = hash(&pos_tmp, amp, from, to); if (ok < rows * 4 - 1) { /* */ if (zobrist == HASH_SEEN) { log(1, "collision, skipping move : "); burrow_print_flat(&pos_tmp); return NULL; } } pos_tmp.eval = eval(&pos_tmp) + pos_tmp.cost; if (!(newpos = get_pos(&pos_tmp))) return NULL; burrow_print_flat(newpos); push_pos(newpos); log(1, "New position: moves=%d cost=%lu\n", newpos->moves, newpos->cost); return newpos; } /* generate all moves from a given position */ static void genmoves(pos_t *pos) { amphipod_t amp; u32 tmp; int bit; int rows = popcount32(pos->amp[0]); if (pos->cost >= result) return; log_f(1, "position :\n"); burrow_print(pos); for (amp = A; amp <= D; ++amp) { u32 cur = pos->amp[amp]; bit_for_each32_2(bit, tmp, cur) { log(3, "toto -> %s %d\n", int2bin(bit), bit); if (bit >= _A1) { /* in a room */ if (BIT(bit) & pos->final) { log(1, "position already ok\n"); continue; } /* TODO: already in dest room */ int room = (bit >> 2) - 2; log(3, "room = %d\n", room); for (int side = LEFT; side <= RIGHT; ++side) { int *d = room_exit[room][side]; for (; *d != -1; ++d) { log(3, "checking %c from %s to %s\n", amp + 'A', cells[bit], cells[*d]); possible_move_t *move = &moves[bit][*d]; log(3, "mask=%s dist=%d cost=%lu\n", int2bin(move->mask), move->dist, move->dist * cost[amp]); if (move->mask & pos->occupied) { log(3, "blocked!\n"); break; } newmove(pos, amp, bit, *d); } } } else { /* hallway */ u32 room = rooms[amp], found = 0; int dest, count = 0, tmp; log(1, "%c from %s to %#x (%s)\n", amp + 'A', cells[bit], room, int2bin(room)); if (room & pos->occupied && !(room & pos->final)) { log(1, "room is occupied\n"); continue; /* skip current amp pos */ } log(1, "room is available\n"); bit_for_each32_2(dest, tmp, room) { possible_move_t *move = &moves[bit][dest]; log(1, " %d(%s) -> %d(%s) : ", bit, cells[bit], dest, cells[dest]); if (move->mask & pos->occupied) { log(1, "blocked\n"); break; } log(1, "valid\n"); found = dest; if (++count >= rows) break; } if (found) { log(1, "found %s -> %s\n", cells[bit], cells[found]); newmove(pos, amp, bit, found); } } } } return; } /* minimal parsing: We just read the 3-5 lines to get * the amphipods location in side rooms */ static char *part2str[] = { " #D#C#B#A#", " #D#B#A#C#" }; static pos_t *read_input(int part) { size_t alloc = 0; ssize_t buflen; char *buf = NULL; int line = 0, adjline = 0; pos_t *pos = get_pos(NULL); u32 bit; while ((buflen = getline(&buf, &alloc, stdin)) > 0) { buf[--buflen] = 0; log(3, "line=%d str=%s\n", line, buf); if (line == 2 || line == 3) { if (part == 2 && line == 3) { for (int i = 0; i < 2; ++i) { bit = 8 + adjline - 2; for (int j = 0; j < 4; ++j) { int amp = part2str[i][j * 2 + 3] - 'A'; pos->amp[amp] |= BIT(bit); log(3, "setting bit %d to %c\n", bit, amp + 'A'); bit += 4; } adjline++; } } bit = 8 + adjline - 2; for (int i = 0; i < 4; ++i) { int amp = buf[i * 2 + 3] - 'A'; pos->amp[amp] |= BIT(bit); log(3, "setting bit %d to %c\n", bit, amp + 'A'); bit += 4; } } line++; adjline++; } pos->occupied = get_occupancy(pos); /* check if some amphipods are already in correct place */ for (int room = 0; room < 4; ++room) { u32 mask = pos->amp[room]; if (mask & rooms[room]) { log(3, "found amp %c in room %d\n", room + 'A', room); mask &= rooms[room]; int room1 = 8 + room * 4; for (int cell = part * 2 - 1; cell >= 0; --cell) { if (BIT(room1 + cell) & mask) { log(3, " -> Match for cell %d\n", cell + 1); pos->final |= BIT(room1 + cell); //pos->ok++; } else { log(3, " -> No Match for cell %d\n", cell + 1); break; } //if (mask && mask & rooms[cell]) } } } free(buf); return pos; } static s64 part1() { return 1; } static s64 part2() { return 2; } static int usage(char *prg) { fprintf(stderr, "Usage: %s [-d debug_level] [-p part]\n", prg); return 1; } int main(int ac, char **av) { int opt, part = 1; pos_t *pos; while ((opt = getopt(ac, av, "d:p:")) != -1) { switch (opt) { case 'd': debug_level_set(atoi(optarg)); break; case 'p': /* 1 or 2 */ part = atoi(optarg); if (part < 1 || part > 2) return usage(*av); break; default: return usage(*av); } } if (optind < ac) return usage(*av); pool_pos = pool_create("pos", 1024, sizeof(pos_t)); pool_hash = pool_create("hash", 1024, sizeof(hash_t)); zobrist_init(); hash_init(); init_moves(); print_moves(moves); pos = read_input(part); pos->zobrist = zobrist_1(pos); push_pos(pos); /* for (int i = 0; i < 4; ++ i) { log(1, "------------ %c\n", 'A' + i); mask_print(pos->amp[i]); } */ mask_print(pos->occupied); burrow_print(pos); while ((pos = pop_pos())) { genmoves(pos); free_pos(pos); } hash_stats(); printf("%s : res=%ld\n", *av, result); printf("%s : res=%ld\n", *av, part == 1? part1(): part2()); exit(0); /* dummy calls for flycheck */ eval(pos); }