aboutsummaryrefslogtreecommitdiff
path: root/include/tak.c
diff options
context:
space:
mode:
Diffstat (limited to 'include/tak.c')
-rw-r--r--include/tak.c214
1 files changed, 117 insertions, 97 deletions
diff --git a/include/tak.c b/include/tak.c
index 406f8b5..e9f27ff 100644
--- a/include/tak.c
+++ b/include/tak.c
@@ -50,6 +50,7 @@ void reset_state(tak_state_p state, const uint8_t new_board_size) {
state->ply = 0;
state->won = 0xFF; // i may live to regret this hack
state->current_colour = C_BLACK;
+ state->hash = 0; // empty board, ply 0
for (uint8_t k = 0; k < NUM_SQUARES(new_board_size); k++) {
state->celldat[k] = 0;
@@ -154,7 +155,7 @@ enum ACT_RESULT try_move(tak_state_p state, const int8_t location,
if (avail == 0)
return ACT_ILLEGAL;
// Does the current player own the pile?
- if ((state->colours[location] & 1) != state->current_colour)
+ if (TOP_COLOUR_AT(state, location) != state->current_colour)
return ACT_ILLEGAL;
// Is the desired direction and count on the board?
int8_t delta = 0;
@@ -239,25 +240,40 @@ enum ACT_RESULT try_move(tak_state_p state, const int8_t location,
// Checking win
// ===================================================================
-// Check for the presence of a road connecting opposite sides
-static enum WIN_TYPE check_road_colour(tak_state_p state,
- const enum COLOUR colour) {
- int component[NUM_SQUARES(state->board_size)],
+// Sides of the board a connected component can touch
+#define SIDE_BOTTOM 0x1 // row 0
+#define SIDE_TOP 0x2 // last row
+#define SIDE_LEFT 0x4 // column 0
+#define SIDE_RIGHT 0x8 // last column
+#define SIDES_BOTTOM_TOP (SIDE_BOTTOM | SIDE_TOP)
+#define SIDES_LEFT_RIGHT (SIDE_LEFT | SIDE_RIGHT)
+
+// One bit per colour, set once a road for that colour has been found
+#define ROAD_WHITE (1u << C_WHITE)
+#define ROAD_BLACK (1u << C_BLACK)
+#define ROAD_BOTH (ROAD_WHITE | ROAD_BLACK)
+
+enum WIN_TYPE check_win(tak_state_p state) {
+ uint8_t component[NUM_SQUARES(state->board_size)],
touching[NUM_SQUARES(state->board_size)];
/*
- We're doing a poor version of a disjoint set data structure to
- track and merge connected components. The array `component' stores
- indices to the representative cells of each connected component. A
- cell is representative if component[cell] = cell. We track only
- whether representatives are touching sides, and do a 2-dimensional
- DP approach to forming these from the board.
+ We're doing a poor version of a disjoint set data structure to track and
+ merge connected components. The array `component' stores indices to the
+ representative cells of each connected component. A cell is representative
+ if component[cell] = cell. We track only whether representatives are
+ touching sides, and do a 2-dimensional DP approach to forming these from the
+ board.
Note: we don't track the rank/size of each tree, because we're not
- interested in good asymptotic complexity in the size of the board,
- merely good performance for a single board size in practice. For
- the same reason we also don't do path flattening/halving or
- anything.
+ interested in good asymptotic complexity in the size of the board, merely
+ good performance for a single board size in practice. For the same reason we
+ also don't do path flattening/halving or anything.
+
+ A cell is road-eligible only for the colour of its top stone, so black and
+ white components are disjoint and can share one component/touching pair,
+ letting us check both colours (and count top-flat stones) in a single pass.
+ The dragon rule needs the road status of both colours.
*/
for (int k = 0; k < NUM_SQUARES(state->board_size); k++) {
component[k] = k; // every square is in its own connected
@@ -265,97 +281,110 @@ static enum WIN_TYPE check_road_colour(tak_state_p state,
touching[k] = 0; // and not connected to any sides
}
- // touching is the bit mask for connectivity,
- // bottom | top | left | right
- // 1 2 4 8
+ uint8_t road = 0;
+
+ // flat count: +1 per top-flat black stone, -1 per top-flat white
+ int8_t total = 0;
+ uint8_t board_full = 1;
- int touch = 5;
+ int touch = SIDE_BOTTOM | SIDE_LEFT;
for (int row = 0; row < state->board_size; row++) {
for (int col = 0; col < state->board_size; col++) {
const int cur = THE_COORDS(state->board_size, col, row);
- if (COUNT_AT(state, cur) && (state->colours[cur] & 1) == colour &&
- STONE_AT(state, cur) != STONE_STANDING) {
+ const uint8_t count = COUNT_AT(state, cur);
+ if (count == 0) {
+ board_full = 0;
+ } else if (STONE_AT(state, cur) == STONE_FLAT) {
+ total += (TOP_COLOUR_AT(state, cur) == C_BLACK) ? +1 : -1;
+ }
- // do we have any neighbours to the left and below?
- const int left_neighbour =
- ((cur % state->board_size > 0) &&
- (COUNT_AT(state, cur - 1)) // wont ever be out of bounds
- && ((state->colours[cur - 1] & 1) == colour) &&
- (STONE_AT(state, cur - 1) != STONE_STANDING));
+ if (count && STONE_AT(state, cur) != STONE_STANDING) {
+ const uint8_t colour = TOP_COLOUR_AT(state, cur);
+ const uint8_t done = 1u << colour;
+ if ((road & done) == 0) {
- const int lowr_neighbour =
- ((cur >= state->board_size) &&
- (COUNT_AT(state, cur - state->board_size)) &&
- ((state->colours[cur - state->board_size] & 1) == colour) &&
- (STONE_AT(state, cur - state->board_size) != STONE_STANDING));
+ // do we have any neighbours to the left and below?
+ const int left_neighbour =
+ ((cur % state->board_size > 0) &&
+ (COUNT_AT(state, cur - 1)) // wont ever be out of bounds
+ && (TOP_COLOUR_AT(state, cur - 1) == colour) &&
+ (STONE_AT(state, cur - 1) != STONE_STANDING));
- // always take the component of the lower neighbour if
- // possible, failing that take the left neighbour, otherwise
- // we're not yet connected, so update our own component.
- if (lowr_neighbour) {
- // look up the root of the lower neighbour
- int root = cur - state->board_size;
- while (root != component[root])
- root = component[root];
- // join the set
- component[cur] = root;
- if (touch) {
- // something new
- touching[root] |= touch;
- // are we done?
- if ((touching[root] & 0x3) == 0x3 || (touching[root] & 0xC) == 0xC)
- return (colour == C_BLACK) ? WIN_ROAD_BLACK : WIN_ROAD_WHITE;
- }
- // if we also have a left neighbour then we should `merge'
- // sets, and here we assume that the left neighbour set is
- // always smaller (may not be) for the direction of merge
- if (left_neighbour) {
- int left_root = cur - 1;
- while (left_root != component[left_root])
- left_root = component[left_root];
- // merge
- const int left_touch = touching[left_root];
- if (left_touch) {
- touching[root] |= left_touch;
- if ((touching[root] & 0x3) == 0x3 ||
- (touching[root] & 0xC) == 0xC)
- return (colour == C_BLACK) ? WIN_ROAD_BLACK : WIN_ROAD_WHITE;
+ const int lowr_neighbour =
+ ((cur >= state->board_size) &&
+ (COUNT_AT(state, cur - state->board_size)) &&
+ (TOP_COLOUR_AT(state, cur - state->board_size) == colour) &&
+ (STONE_AT(state, cur - state->board_size) != STONE_STANDING));
+
+ // always take the component of the lower neighbour if
+ // possible, failing that take the left neighbour, otherwise
+ // we're not yet connected, so update our own component.
+ if (lowr_neighbour) {
+ // look up the root of the lower neighbour
+ int root = cur - state->board_size;
+ while (root != component[root])
+ root = component[root];
+ // join the set
+ component[cur] = root;
+ if (touch) {
+ // something new
+ touching[root] |= touch;
+ // are we done?
+ if ((touching[root] & SIDES_BOTTOM_TOP) == SIDES_BOTTOM_TOP ||
+ (touching[root] & SIDES_LEFT_RIGHT) == SIDES_LEFT_RIGHT)
+ road |= done;
}
- component[left_root] = root;
- }
- } else if (left_neighbour) {
- int root = cur - 1;
- while (root != component[root])
- root = component[root];
- component[cur] = root;
- if (touch) {
- touching[root] |= touch;
- if ((touching[root] & 0x3) == 0x3 || (touching[root] & 0xC) == 0xC)
- return (colour == C_BLACK) ? WIN_ROAD_BLACK : WIN_ROAD_WHITE;
+ // if we also have a left neighbour then we should `merge'
+ // sets, and here we assume that the left neighbour set is
+ // always smaller (may not be) for the direction of merge
+ if (left_neighbour) {
+ int left_root = cur - 1;
+ while (left_root != component[left_root])
+ left_root = component[left_root];
+ // merge
+ const int left_touch = touching[left_root];
+ if (left_touch) {
+ touching[root] |= left_touch;
+ if ((touching[root] & SIDES_BOTTOM_TOP) == SIDES_BOTTOM_TOP ||
+ (touching[root] & SIDES_LEFT_RIGHT) == SIDES_LEFT_RIGHT)
+ road |= done;
+ }
+ component[left_root] = root;
+ }
+ } else if (left_neighbour) {
+ int root = cur - 1;
+ while (root != component[root])
+ root = component[root];
+ component[cur] = root;
+ if (touch) {
+ touching[root] |= touch;
+ if ((touching[root] & SIDES_BOTTOM_TOP) == SIDES_BOTTOM_TOP ||
+ (touching[root] & SIDES_LEFT_RIGHT) == SIDES_LEFT_RIGHT)
+ road |= done;
+ }
+ } else if (touch) {
+ // we had no left or lower neighbour, so we're on our own
+ touching[cur] = touch;
}
- } else if (touch) {
- // we had no left or lower neighbour, so we're on our own
- touching[cur] = touch;
+ if (road == ROAD_BOTH)
+ break; // both colours decided, the scan is done
}
}
if (col + 2 == state->board_size)
- touch |= 8;
+ touch |= SIDE_RIGHT;
else
- touch &= 0x3;
+ touch &= (SIDE_BOTTOM | SIDE_TOP);
}
if (row + 2 == state->board_size)
- touch = 6;
+ touch = SIDE_TOP | SIDE_LEFT;
else
- touch = 4;
+ touch = SIDE_LEFT;
+ if (road == ROAD_BOTH)
+ break;
}
- return 0xFF;
-}
-enum WIN_TYPE check_win(tak_state_p state) {
- // Road?
- enum WIN_TYPE rb, rw;
- rb = check_road_colour(state, C_BLACK);
- rw = check_road_colour(state, C_WHITE);
+ const enum WIN_TYPE rw = (road & ROAD_WHITE) ? WIN_ROAD_WHITE : 0xFF,
+ rb = (road & ROAD_BLACK) ? WIN_ROAD_BLACK : 0xFF;
if (rb == WIN_ROAD_BLACK && rw == WIN_ROAD_WHITE) {
return (state->ply & 1) ? rb : rw; // Dragons
@@ -365,15 +394,6 @@ enum WIN_TYPE check_win(tak_state_p state) {
return rb;
}
- // Do we do a flat count?
- int8_t total = 0, board_full = 1;
- for (uint8_t k = 0; k < NUM_SQUARES(state->board_size); k++) {
- if (COUNT_AT(state, k) == 0) {
- board_full = 0;
- } else if (STONE_AT(state, k) == STONE_FLAT) {
- total += ((state->colours[k] & 1) == C_BLACK) ? +1 : -1;
- }
- }
if (state->black_count == 0 || state->white_count == 0 || board_full) {
// Decide based on count
if (total > 0)