#include "tps.h" // =================================================================== // Load TPS string // =================================================================== #define TPS_ASSERT_MORE \ { \ if (*tps == 0) \ return TPS_INVALID; \ } enum TPS_RESULT load_tps(tak_state_p state, char *tps) { // TODO: Ensure NULL termination? if (tps == NULL) return TPS_INVALID; uint8_t prefix = 0; // Check if we're likely of the form [TPS "blah"] if (!strncmp(tps, "[TPS \"", 6)) { prefix = 1; // Now we can worry about just the TPS part tps += 6; } // Reset everything reset_state(state, state->board_size); // Parse squares, NOTE: We assume that board_size matches TPS size. int col = 0, row = state->board_size - 1, skip, parsing = 1; while (parsing) { switch (*tps) { case ' ': { // we're done parsing = 0; tps++; TPS_ASSERT_MORE; break; } case 'x': { // empty squares tps++; TPS_ASSERT_MORE; skip = 0; if (*tps >= '2' && *tps <= '0' + state->board_size) { skip = *tps - '1'; tps++; TPS_ASSERT_MORE; } else if (*tps != ',' && *tps != '/' && *tps != ' ') { return TPS_INVALID; } col += skip; if (col >= state->board_size + 1) return TPS_INVALID; break; } case '/': { // next row if (col + 1 != state->board_size) return TPS_INVALID; row--; col = 0; if (row < 0) return TPS_INVALID; tps++; TPS_ASSERT_MORE; break; } case ',': { // next column col++; if (col >= state->board_size) return TPS_INVALID; tps++; TPS_ASSERT_MORE; break; } default: { const int l = THE_COORDS(state->board_size, col, row); uint8_t num_read = 0, reading = 1; // Read in a stack of colours, optionally terminated by an S // or C to change the top stone type while (reading) { switch (*tps) { // Reading a stone colour case '2': { // check next letter to make sure we have the material tps++; TPS_ASSERT_MORE; if (*tps == 'C') { if (state->black_count & 128) state->black_count &= 127; else return TPS_INVALID; } else if (state->black_count & 127) { state->black_count--; } else return TPS_INVALID; state->colours[l] <<= 1; state->celldat[l] += NUM_INC; state->colours[l] |= 1; num_read++; break; } case '1': { tps++; TPS_ASSERT_MORE; if (*tps == 'C') { if (state->white_count & 128) state->white_count &= 127; else return TPS_INVALID; } else if (state->white_count & 127) { state->white_count--; } else return TPS_INVALID; state->colours[l] <<= 1; state->celldat[l] += NUM_INC; num_read++; break; } case 'S': { // Have we already read a stone type? if (STONE_AT(state, l) != STONE_FLAT) return TPS_INVALID; state->celldat[l] |= STONE_STANDING; tps++; TPS_ASSERT_MORE; break; } case 'C': { if (STONE_AT(state, l) != STONE_FLAT) return TPS_INVALID; state->celldat[l] |= STONE_CAPSTONE; tps++; TPS_ASSERT_MORE; break; } case ',': // fall-through case '/': { // done here reading = 0; break; } default: return TPS_INVALID; } if (num_read > 0xF) return TPS_INVALID; } } } } // Now it's time to parse the ply number. First, the active player if (*tps != '1' && *tps != '2') return TPS_INVALID; state->ply += *tps - '1'; tps++; TPS_ASSERT_MORE; // Space if (*tps != ' ') return TPS_INVALID; tps++; TPS_ASSERT_MORE; // Turn number, atoi doesn't detect errors so let's do it ourselves uint8_t p = 0; do { p *= 10; if (*tps >= '0' && *tps <= '9') { p += *tps - '0'; } else return TPS_INVALID; tps++; } while ((prefix && *tps && *tps != '"') || (!prefix && *tps)); if (p == 0) return TPS_INVALID; state->ply += 2 * (p - 1); state->current_colour = (state->ply & 1) ? C_BLACK : C_WHITE; if (state->ply < 2) state->current_colour = C_BLACK - state->current_colour; if (prefix) { tps++; TPS_ASSERT_MORE; if (*tps != ']') return TPS_INVALID; tps++; if (*tps != 0) return TPS_INVALID; } return TPS_OK; } // =================================================================== // Generate TPS string // =================================================================== void generate_tps(tak_state_p state, char *out_tps) { strcpy(out_tps, "[TPS \""); out_tps += 6; for (int8_t row = state->board_size - 1; row >= 0; row--) { for (int8_t col = 0; col < state->board_size; col++) { const int8_t l = THE_COORDS(state->board_size, col, row); const uint8_t count = COUNT_AT(state, l); if (count) { colour_stack_t c = state->colours[l], s = 1 << (count - 1); for (int k = 0; k < count; k++, s >>= 1, out_tps++) { if (c & s) *out_tps = '2'; else *out_tps = '1'; } switch (STONE_AT(state, l)) { case STONE_CAPSTONE: { *out_tps = 'C'; out_tps++; break; } case STONE_STANDING: { *out_tps = 'S'; out_tps++; break; } default: break; } } else { int8_t skip = 1; while (col < state->board_size && COUNT_AT(state, l + skip) == 0) { skip++; col++; } *out_tps = 'x'; out_tps++; if (skip > 1) { *out_tps = '0' + skip; out_tps++; } } if (col + 1 < state->board_size) { *out_tps = ','; out_tps++; } } if (row > 0) { *out_tps = '/'; out_tps++; } } *out_tps = ' '; out_tps++; *out_tps = '1' + (state->ply & 1); out_tps++; *out_tps = ' '; out_tps++; out_tps += sprintf(out_tps, "%d", state->ply / 2 + 1); strcpy(out_tps, "\"]"); }