diff options
Diffstat (limited to 'include/actions.c')
| -rw-r--r-- | include/actions.c | 508 |
1 files changed, 276 insertions, 232 deletions
diff --git a/include/actions.c b/include/actions.c index 795c135..6303822 100644 --- a/include/actions.c +++ b/include/actions.c @@ -16,37 +16,42 @@ */ #include "actions.h" +#include "tak.h" + +#include <stdio.h> + +static uint8_t al_board_size; // =================================================================== // Helper method declarations // =================================================================== -#define DANGER_MIN(a,b) (((a)<(b))?(a):(b)) +#define DANGER_MIN(a, b) (((a) < (b)) ? (a) : (b)) #define CLR_STONE NUM_MASK -static inline void -list_append(action_list_t *list, const enum A_TYPE type, - const int8_t loc, const uint8_t data0, - const uint8_t data1); +static inline void list_append(action_list_t *list, const enum A_TYPE type, + const int8_t loc, const uint8_t data0, + const uint8_t data1); + +static inline void list_prepend(action_list_t *list, const enum A_TYPE type, + const int8_t loc, const uint8_t data0, + const uint8_t data1); -static inline void -list_prepend(action_list_t *list, const enum A_TYPE type, - const int8_t loc, const uint8_t data0, - const uint8_t data1); +static inline void inline_next_ply(tak_state_p state); -static inline void -inline_next_ply(void); +static inline void inline_prev_ply(tak_state_p state); -static inline void -inline_prev_ply(void); +static inline uint8_t check_no_overflow(tak_state_p state, const uint8_t loc, + const int8_t delta, const uint8_t num, + const uint8_t steps, + const uint8_t gaps); // =================================================================== // Exported method implementations // =================================================================== -int action_move_to_front(const action_t action, - action_list_t *list) { +int action_move_to_front(const action_t action, action_list_t *list) { action_node_t *n = list->head; // TODO: what if it's not in the list? @@ -64,7 +69,6 @@ int action_move_to_front(const action_t action, return EXIT_FAILURE; } - void action_list_free(action_list_t *list) { if (list) { action_node_t *n = list->head, *nn; @@ -80,7 +84,8 @@ void action_list_free(action_list_t *list) { // Keep track of move offsets int8_t move_deltas[4]; -void action_list_init(void) { +void action_list_init(const uint8_t board_size) { + al_board_size = board_size; move_deltas[0] = +board_size; move_deltas[1] = -board_size; move_deltas[2] = -1; @@ -89,7 +94,7 @@ void action_list_init(void) { // We bias place over move by prepending place actions and appending // move actions to the generated list -action_list_t *action_list_generate(void) { +action_list_t *action_list_generate(tak_state_p state) { action_list_t *list = malloc(sizeof(struct action_list_s)); // TODO: trap errno @@ -103,278 +108,297 @@ action_list_t *action_list_generate(void) { * well save on the conditional. */ - const uint8_t material = (ply & 1) ? black_count : white_count, - flat = material & 0x7F, - cap = ((ply >= 2) && (material & 0x80)), - standing = ((ply >= 2) && flat); + const uint8_t material = + (state->ply & 1) ? state->black_count : state->white_count, + flat = material & 0x7F, + cap = ((state->ply >= 2) && (material & 0x80)), + standing = ((state->ply >= 2) && flat); // Step across the board - for (int row = 0; row < board_size; row++) { - for (int col = 0; col < board_size; col++) { + for (int row = 0; row < state->board_size; row++) { + for (int col = 0; col < state->board_size; col++) { // We'll need these at various points: the location of this // square and the maximum number of stones we could pick up - const int loc = THE_COORDS(col, row); - const uint8_t count = DANGER_MIN(COUNT_AT(loc), board_size); + const int loc = THE_COORDS(al_board_size, col, row); + const uint8_t count = DANGER_MIN(COUNT_AT(state, loc), al_board_size); // Only try moves after CPS and if the colour is correct if (count) { - if (ply >= 2 && ((colours[loc] & 1) == current_colour)) { - - // 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 (int d = 0; d < 4; d++){ - const int delta = move_deltas[d]; - const int stop = end_stops[d]; - end_stops[d] = 0; - for (int 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. - * - * We exploit the `gaps' bijection here and elsewhere - * between ordered {integer partitions of n with s summands} - * and {binary strings of length n-1 with s-1 set bits}. - */ - 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++) { - uint8_t gaps = - ((1<<(board_size - 2)) - 1) >> (board_size-steps-1); - // For 5x5 this givess 0b0000[0XXX] where steps-1 of - // those X's are 1s (starting with LSB) 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! - const uint8_t crush = - (steps == end_stops[dir]) && crushes[dir]; - // Store the move - - list_append(list, 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)))); - } - } - } - } + if (state->ply >= 2 && + ((state->colours[loc] & 1) == state->current_colour)) { + + // 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(al_board_size - row - 1, count); + end_stops[1] = DANGER_MIN(row, count); + end_stops[2] = DANGER_MIN(col, count); + end_stops[3] = DANGER_MIN(al_board_size - col - 1, count); + + // Now we check for caps and walls + const uint8_t cap_top = STONE_AT(state, loc) == STONE_CAPSTONE; + for (int d = 0; d < 4; d++) { + const int delta = move_deltas[d]; + const int stop = end_stops[d]; + end_stops[d] = 0; + for (int k = 1; k <= stop; k++) { + const enum STONE_VARIANT stone = STONE_AT(state, loc + k * delta); + if (stone == STONE_STANDING) { + if (cap_top) { + crushes[d] = k; + 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 in the + * range 1 ≤ # summands ≤ min(end_stops[dir], num) -- we write + * summands as steps. + * + * We exploit the `gaps' bijection here and elsewhere between ordered + * {integer partitions of n with s summands} and {binary strings of + * length n-1 with s-1 set bits}. + */ + for (enum MOVE_DIRECTION dir = M_UP; dir <= M_RIGHT; dir++) { + const int8_t delta = move_deltas[dir]; + for (uint8_t num = 1; num <= count; num++) { + for (uint8_t steps = 1; steps <= end_stops[dir] && steps <= num; + steps++) { + uint8_t gaps = ((1 << (al_board_size - 2)) - 1) >> + (al_board_size - steps - 1); + // For 5x5 this gives 0b0000[0XXX] where steps-1 of those X's + // are 1s (starting with LSB) 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, or if it would cause any stack to + * grow beyond height 15. + */ + const uint8_t no_overflow = + check_no_overflow(state, loc, delta, num, steps, gaps); + const uint8_t last_drop_check = + (num > 1) ? (gaps & (1 << (num - 2))) : 1; + const uint8_t can_and_must_crush = + crushes[dir] == steps && last_drop_check; + const uint8_t crush_check = + can_and_must_crush || crushes[dir] != steps; + if (no_overflow && crush_check) { + // Store the move + list_append(list, A_MOVE, loc, + (can_and_must_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)))); + } + } + } + } } // end of if (count) { ... } else if (material) { - // Empty square, generate placements - if (flat) { - list_prepend(list, A_PLACE, loc, STONE_FLAT, 0); - if (standing) - list_prepend(list, A_PLACE, loc, STONE_STANDING,0); - } - if (cap) - list_prepend(list, A_PLACE, loc, STONE_CAPSTONE, 0); + // Empty square, generate placements + if (flat) { + list_prepend(list, A_PLACE, loc, STONE_FLAT, 0); + if (standing) + list_prepend(list, A_PLACE, loc, STONE_STANDING, 0); + } + if (cap) + list_prepend(list, A_PLACE, loc, STONE_CAPSTONE, 0); } } } return list; } -void action_take(const action_t action) { +void action_take(tak_state_p state, const action_t action) { const int8_t loc = A_GET_LOC(action); if (A_GET_TYPE(action) == A_PLACE) { - const uint8_t black = (current_colour == C_BLACK); + const uint8_t black = (state->current_colour == C_BLACK); switch (A_GET_DATA0(action)) { - 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; - } + case STONE_FLAT: { + if (black) + state->black_count--; + else + state->white_count--; + state->colours[loc] = state->current_colour; + state->celldat[loc] = NUM_INC | STONE_FLAT; + break; + } + case STONE_STANDING: { + if (black) + state->black_count--; + else + state->white_count--; + state->colours[loc] = state->current_colour; + state->celldat[loc] = NUM_INC | STONE_STANDING; + break; + } + default: { + if (black) + state->black_count &= 0x7F; + else + state->white_count &= 0x7F; + state->colours[loc] = state->current_colour; + state->celldat[loc] = NUM_INC | STONE_CAPSTONE; + break; + } } } else { /* - * See the discussion around line 135 for an explanation of the - * encoding. Here we are 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. + * See the discussion around line 160 for an explanation of the encoding. + * Here we are 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. */ const uint8_t gaps = A_GET_DATA0(action) & 0x7F, - num = A_GET_DATA1(action) & 0x0F, // unpack - dir = A_GET_DATA1(action) >> 4; + num = A_GET_DATA1(action) & 0x0F, // unpack + dir = A_GET_DATA1(action) >> 4; int8_t delta = move_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; + 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 + state->celldat[loc + steps * delta] &= CLR_STONE; // necessary for crushing + state->celldat[loc + steps * delta] |= STONE_AT(state, loc); + state->celldat[loc] &= CLR_STONE; + state->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--; + state->colours[loc + steps * delta] <<= total; + state->colours[loc + steps * delta] |= + state->colours[loc] & ((1 << total) - 1); + state->colours[loc] >>= total; + state->celldat[loc + steps * delta] += total * NUM_INC; + state->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; + state->colours[loc + delta] <<= total; + state->colours[loc + delta] |= state->colours[loc] & ((1 << total) - 1); + state->colours[loc] >>= total; + state->celldat[loc + delta] += total * NUM_INC; + state->celldat[loc] -= total * NUM_INC; } // Next ply - inline_next_ply(); + inline_next_ply(state); } -void action_undo(const action_t action) { +void action_undo(tak_state_p state, const action_t action) { // Previous ply - inline_prev_ply(); + inline_prev_ply(state); const int8_t loc = A_GET_LOC(action); if (A_GET_TYPE(action) == A_PLACE) { - const uint8_t black = (current_colour == C_BLACK); - celldat[loc] = 0; + const uint8_t black = (state->current_colour == C_BLACK); + state->celldat[loc] = 0; if (A_GET_DATA0(action) == STONE_CAPSTONE) { - if (black) black_count |= 0x80; - else white_count |= 0x80; + if (black) + state->black_count |= 0x80; + else + state->white_count |= 0x80; } else { - if (black) black_count++; - else white_count++; + if (black) + state->black_count++; + else + state->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 = A_GET_DATA0(action) & 0x7F, - crush = A_GET_DATA0(action) & 0x80, - num = A_GET_DATA1(action) & 0x0F, - dir = A_GET_DATA1(action) >> 4; + crush = A_GET_DATA0(action) & 0x80, + num = A_GET_DATA1(action) & 0x0F, + dir = A_GET_DATA1(action) >> 4; const int8_t delta = move_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++; + state->colours[loc] <<= total; + state->colours[loc] |= + state->colours[loc + steps * delta] & ((1 << total) - 1); + state->colours[loc + steps * delta] >>= total; + state->celldat[loc] += total * NUM_INC; + state->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; + state->colours[loc] <<= total; + state->colours[loc] |= + state->colours[loc + steps * delta] & ((1 << total) - 1); + state->colours[loc + steps * delta] >>= total; - celldat[loc] += total*NUM_INC; + state->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; + state->celldat[loc] |= STONE_AT(state, loc + steps * delta); + state->celldat[loc + steps * delta] -= total * NUM_INC; + state->celldat[loc + steps * delta] &= CLR_STONE; if (crush) { - celldat[loc+steps*delta] |= STONE_STANDING; + state->celldat[loc + steps * delta] |= STONE_STANDING; } else { - celldat[loc+steps*delta] |= STONE_FLAT; // should be optimised out + state->celldat[loc + steps * delta] |= + STONE_FLAT; // should be optimised out } } } -void action_to_ptn(const action_t action, char* out_ptn) { +void action_to_ptn(const action_t action, char *out_ptn) { const int8_t loc = A_GET_LOC(action); if (A_GET_TYPE(action) == A_PLACE) { - generate_place(loc, A_GET_DATA0(action), out_ptn); + generate_place(al_board_size, loc, A_GET_DATA0(action), out_ptn); } else { const uint8_t gaps = A_GET_DATA0(action) & 0x7F, - num = A_GET_DATA1(action) & 0x0F, // unpack - dir = A_GET_DATA1(action) >> 4; + num = A_GET_DATA1(action) & 0x0F, // unpack + dir = A_GET_DATA1(action) >> 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 drops[al_board_size]; // we only ever need al_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; + drops[0] = 1; + mask = 1; for (uint8_t d = 1; d < num; d++) { if (gaps & mask) { - steps++; - drops[steps] = 1; // (**) no bounds check + steps++; + drops[steps] = 1; // (**) no bounds check } else { - drops[steps] += 1; + drops[steps] += 1; } mask <<= 1; } - generate_move(loc, dir, steps+1, drops, out_ptn); + generate_move(al_board_size, loc, dir, steps + 1, drops, out_ptn); } } @@ -382,10 +406,9 @@ void action_to_ptn(const action_t action, char* out_ptn) { // Helper method implementations // =================================================================== -static inline void -list_append(action_list_t *list, const enum A_TYPE type, - const int8_t loc, const uint8_t data0, - const uint8_t data1) { +static inline void list_append(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_node_t)); // TODO: trap errno @@ -403,10 +426,9 @@ list_append(action_list_t *list, const enum A_TYPE type, list->length++; } -static inline void -list_prepend(action_list_t *list, const enum A_TYPE type, - const int8_t loc, const uint8_t data0, - const uint8_t data1) { +static inline void 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 @@ -421,24 +443,46 @@ list_prepend(action_list_t *list, const enum A_TYPE type, list->length++; } -static inline void -inline_next_ply(void) { - ply++; - if (ply == 2) { - current_colour = C_WHITE; +static inline void inline_next_ply(tak_state_p state) { + state->ply++; + if (state->ply == 2) { + state->current_colour = C_WHITE; } else { - if (current_colour == C_BLACK) current_colour = C_WHITE; - else current_colour = C_BLACK; + if (state->current_colour == C_BLACK) + state->current_colour = C_WHITE; + else + state->current_colour = C_BLACK; } } -static inline void -inline_prev_ply(void) { - if (ply>0) ply--; - if (ply == 1) { - current_colour = C_WHITE; +static inline void inline_prev_ply(tak_state_p state) { + if (state->ply > 0) + state->ply--; + if (state->ply == 1) { + state->current_colour = C_WHITE; } else { - if (current_colour == C_BLACK) current_colour = C_WHITE; - else current_colour = C_BLACK; + if (state->current_colour == C_BLACK) + state->current_colour = C_WHITE; + else + state->current_colour = C_BLACK; + } +} + +static inline uint8_t check_no_overflow(tak_state_p state, const uint8_t loc, + const int8_t delta, const uint8_t num, + const uint8_t steps, + const uint8_t gaps) { + uint8_t total = 1, gap_bit = 1 << (num - 2), step = steps; + for (uint8_t d = 1; d < num; d++, total++, gap_bit >>= 1) { + if (gaps & gap_bit) { + if (COUNT_AT(state, loc + step * delta) + total > 15) + return 0; + step--; + total = 0; + } } + if (COUNT_AT(state, loc + delta) + total > 15) + return 0; + + return 1; } |
