#include "action_list.h" // =================================================================== // Helper method declarations // =================================================================== #define DANGER_MIN(a,b) (((a)<(b))?(a):(b)) #define CLR_STONE NUM_MASK static inline void action_list_prepend(action_list_t *list, const enum A_TYPE type, const int8_t loc, const uint8_t data0, const uint8_t data1); // =================================================================== // Exported method implementations // =================================================================== void action_list_free(action_list_t *list) { if (list) { action_node_t *n = list->head, *nn; while (n) { nn = n->next; free(n); n = nn; } free(list); } } // Keep track of move offsets static int8_t deltas[4]; void action_list_init(void) { deltas[0] = +board_size; deltas[1] = -board_size; deltas[2] = -1; deltas[3] = +1; } action_list_t *action_list_generate(void) { action_list_t *result = malloc(sizeof(struct action_list_s)); // TODO: trap errno result->length = 0; result->head = NULL; const uint8_t black = (ply < 2) ? (ply==1) : (ply & 1), material = (black) ? black_count : white_count, flat = material & 0x7F, cap = (ply >= 2 && (material & 0x80)), standing = (ply >= 2 && flat); // Step across the board, reversed because we prepend to the list for (int8_t row = board_size - 1; row >= 0; row--) { for (int8_t col = board_size - 1; col >= 0; col--) { // We'll need these at various points: the location of this // square and the maximum number of stones we could pick up const int8_t loc = THE_COORDS(col, row); const uint8_t count = DANGER_MIN(COUNT_AT(loc), board_size); // Only try moves after CPS if (ply >= 2 && count && ((colours[loc] & 1) == current_colour)) { // There are stones, let's try moving them // Pre-compute end-stops and crushes uint8_t end_stops[4], crushes[4] = {0, 0, 0, 0}; // These are upper bounds, not counting walls and such. UP DOWN LEFT RIGHT end_stops[0] = DANGER_MIN(board_size - row - 1, count); end_stops[1] = DANGER_MIN(row, count); end_stops[2] = DANGER_MIN(col, count); end_stops[3] = DANGER_MIN(board_size - col - 1, count); // Now we check for caps and walls const uint8_t cap_top = STONE_AT(loc) == STONE_CAPSTONE; for (uint8_t d = 0; d < 4; d++){ const int8_t delta = deltas[d]; const uint8_t stop = end_stops[d]; end_stops[d] = 0; for (uint8_t k = 1; k <= stop; k++) { const enum STONE_VARIANT stone = STONE_AT(loc+k*delta); if (stone == STONE_STANDING) { if (cap_top) { crushes[d] = 0xFF; end_stops[d]++; } break; } else if (stone == STONE_CAPSTONE) { break; } end_stops[d]++; } } /* * For each direction, generate all possible ordered integer * partitions of 1 ≤ num ≤ count whose number of summands is * exactly 1 ≤ summands ≤ min(end_stops[dir], num) -- we write * summands as steps */ for (enum MOVE_DIRECTION dir = M_UP; dir <= M_RIGHT; dir++) { for (uint8_t num = 1; num <= count; num++) { for (uint8_t steps = 1; steps <= end_stops[dir] && steps <= num; steps++) { // TODO: Generalise to board_size! uint8_t gaps = 0x07 >> (board_size-steps-1); // 0b0000[0111] because 4-1=3 and 5-1=4 do { /* * We skip the partition if it calls for multiple stones at * the end with a crush. */ const uint8_t last_drop_check = (num > 1) ? (gaps & 1<<(num - 2)) : 1; if (crushes[dir] == 0 || last_drop_check) { // We have to record a crush! // THIS IS WHERE THE PROBLEM IS const uint8_t crush = (steps == end_stops[dir]) && crushes[dir]; action_list_prepend(result, A_MOVE, loc, (crush << 7) | gaps, (dir<<4) | num); } /* * With thanks to * https://graphics.stanford.edu/~seander/bithacks.html#NextBitPermutation * we have the following magic to generate the next * permutation of steps-many set bits */ uint8_t t = (gaps | (gaps - 1)); gaps = (t + 1) | (((~t & -~t) - 1) >> (__builtin_ctz(gaps) + 1)); } while (gaps && (gaps + 1 <= (1<<(num-1)))); } } } } else if (count == 0 && material) { // Empty square, generate placements if (flat) { action_list_prepend(result, A_PLACE, loc, STONE_FLAT, 0); if (standing) action_list_prepend(result, A_PLACE, loc, STONE_STANDING, 0); } if (cap) action_list_prepend(result, A_PLACE, loc, STONE_CAPSTONE, 0); } } } return result; } void action_take(action_node_t *action) { const int8_t loc = action->loc; if (action->type == A_PLACE) { const uint8_t black = (current_colour == C_BLACK); switch (action->data0) { case STONE_FLAT: { if (black) black_count--; else white_count--; colours[loc] = current_colour; celldat[loc] = NUM_INC | STONE_FLAT; break; } case STONE_STANDING: { if (black) black_count--; else white_count--; colours[loc] = current_colour; celldat[loc] = NUM_INC | STONE_STANDING; break; } default: { if (black) black_count &= 0x7F; else white_count &= 0x7F; colours[loc] = current_colour; celldat[loc] = NUM_INC | STONE_CAPSTONE; break; } } } else { const uint8_t gaps = action->data0 & 0x7F, // not interested in // whether we crushed, // it will work out by // anyway because we // overwrite the top // stone type. See (*) // later for when we do // need to know. num = action->data1 & 0x0F, // unpack dir = action->data1 >> 4; int8_t delta = deltas[dir]; // Use the Kernighan method to count the set bits int8_t steps = 1; for (uint8_t _gaps = gaps; _gaps; steps++) _gaps &= _gaps - 1; // Move top stone type to destination celldat[loc+steps*delta] &= CLR_STONE; // necessary for crushing celldat[loc+steps*delta] |= STONE_AT(loc); celldat[loc] &= CLR_STONE; celldat[loc] |= STONE_FLAT; // should be optimised out uint8_t total = 1, gap_bit = 1 << (num - 2); // it's not important // what negative // shifts do here, we // don't use gap_bit // if num < 2 // move stuff starting at destination for (uint8_t d = 1; d < num; d++, total++, gap_bit >>= 1) { // We took a step, move everything over so far if (gaps & gap_bit) { colours[loc+steps*delta] <<= total; colours[loc+steps*delta] |= colours[loc] & ((1 << total) - 1); colours[loc] >>= total; celldat[loc+steps*delta] += total*NUM_INC; celldat[loc] -= total*NUM_INC; // Reset for next step total = 0; steps--; } } // Move what remains (steps == 1 here always, so we simplify) colours[loc+delta] <<= total; colours[loc+delta] |= colours[loc] & ((1 << total) - 1); colours[loc] >>= total; celldat[loc+delta] += total*NUM_INC; celldat[loc] -= total*NUM_INC; } // Next ply ply++; if (ply == 2) { current_colour = C_WHITE; } else { if (current_colour == C_BLACK) current_colour = C_WHITE; else current_colour = C_BLACK; } } void action_undo(action_node_t *action) { // Previous ply if (ply>0) ply--; if (ply == 1) { current_colour = C_WHITE; } else { if (current_colour == C_BLACK) current_colour = C_WHITE; else current_colour = C_BLACK; } const int8_t loc = action->loc; if (action->type == A_PLACE) { const uint8_t black = (current_colour == C_BLACK); celldat[loc] = 0; if (action->data0 == STONE_CAPSTONE) { if (black) black_count |= 0x80; else white_count |= 0x80; } else { if (black) black_count++; else white_count++; } } else { // See action_take for comments, this is the time reversal, but // there is one caveat -- undoing a crush! (*) const uint8_t gaps = action->data0 & 0x7F, crush = action->data0 & 0x80, num = action->data1 & 0x0F, dir = action->data1 >> 4; const int8_t delta = deltas[dir]; int8_t steps = 1; uint8_t gap_bit = 1, total = 1; for (int8_t d = 1; d < num; d++, total++, gap_bit <<= 1) { if (gaps & gap_bit) { colours[loc] <<= total; colours[loc] |= colours[loc+steps*delta] & ((1 << total) - 1); colours[loc+steps*delta] >>= total; celldat[loc] += total*NUM_INC; celldat[loc+steps*delta] -= total*NUM_INC; total = 0; steps++; } } colours[loc] <<= total; colours[loc] |= colours[loc+steps*delta] & ((1 << total) - 1); colours[loc+steps*delta] >>= total; celldat[loc] += total*NUM_INC; // celldat[loc] &= CLR_STONE; is not necessary, as STONE_FLAT == 0 celldat[loc] |= STONE_AT(loc+steps*delta); celldat[loc+steps*delta] -= total*NUM_INC; celldat[loc+steps*delta] &= CLR_STONE; if (crush) { celldat[loc+steps*delta] |= STONE_STANDING; } else { celldat[loc+steps*delta] |= STONE_FLAT; // should be optimised out } } } void action_to_ptn(action_node_t* action, char* out_ptn) { const int8_t loc = action->loc; if (action->type == A_PLACE) { generate_place(loc, action->data0, out_ptn); } else { const uint8_t gaps = action->data0 & 0x7F, num = action->data1 & 0x0F, // unpack dir = action->data1 >> 4; uint8_t drops[board_size]; // we only ever need board_size-1 in // drops actually, the last spot is to // skip a bounds check at (**) uint8_t mask = 1, steps = 0; // Translate to a drop sequence drops[0] = 1; mask = 1; for (uint8_t d = 1; d < num; d++) { if (gaps & mask) { steps++; drops[steps] = 1; // (**) no bounds check } else { drops[steps] += 1; } mask <<= 1; } generate_move(loc, dir, steps+1, drops, out_ptn); } } // =================================================================== // Helper method implementations // =================================================================== static inline void action_list_prepend(action_list_t *list, const enum A_TYPE type, const int8_t loc, const uint8_t data0, const uint8_t data1) { action_node_t *new = malloc(sizeof(action_list_t)); // TODO: trap errno list->length++; new->loc = loc; new->type = type; new->data0 = data0; new->data1 = data1; new->next = list->head; list->head = new; }