/* pptdb, generate neural network training data from a playtak.com database dump Copyright (C) 2021, tslil clingman This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include int generate; uint64_t heights[16]; FILE *training_fh = NULL; float max_flats; uint8_t outcome_black; tak_state_p state; static void write_input(const int dx, const int dy, const uint8_t swap) { // Two numbers for flats remaining fprintf(training_fh, "%d,%.8f,%.8f,", state->ply & 1 ? 1 : -1, (float)(state->white_count & 127) / max_flats, (float)(state->black_count & 127) / max_flats); // Write the board layers float val; int col, row; row = (dy > 0) ? -1 : state->board_size; for (int i = 0; i < state->board_size; i++) { row += dy; col = (dx > 0) ? -1 : state->board_size; for (int j = 0; j < state->board_size; j++) { col += dx; const uint8_t k = (swap) ? THE_COORDS(state->board_size, row, col) : THE_COORDS(state->board_size, col, row); val = 0; if (COUNT_AT(state, k) > 0) { // Top layer of stacks is handled differently to indicate // stone type if (STONE_AT(state, k) == STONE_STANDING) { val = (state->colours[k] & 1) ? +0.25 : -0.25; } else if (STONE_AT(state, k) == STONE_CAPSTONE) { val = (state->colours[k] & 1) ? +1.00 : -1.00; } else { val = (state->colours[k] & 1) ? +0.50 : -0.50; } } fprintf(training_fh, "%.2f,", val); } } fprintf(training_fh, "%d,%d\n", outcome_black ? 1 : 0, outcome_black ? 0 : 1); } // Warning: performs _no_ checks on input whatsoever static enum ACT_RESULT parse_line(const char *pt, const ssize_t read) { ssize_t idx; enum ACT_RESULT r; int total_plies = 0; for (idx = 0; idx < read; idx++) { if (pt[idx] == ',') total_plies++; } for (idx = 0;;) { if (pt[idx] == 'P') { // P [A-F][1-6] [CF]?, idx += 2; enum STONE_VARIANT stone; const uint8_t col = pt[idx] - 'A', row = pt[idx + 1] - '1'; if (idx + 3 < read) { switch (pt[idx + 3]) { case 'W': { stone = STONE_STANDING; break; } case 'C': { stone = STONE_CAPSTONE; break; } default: { stone = STONE_FLAT; break; } } } else { stone = STONE_FLAT; } r = try_place(state, THE_COORDS(state->board_size, col, row), state->current_colour, stone); if (r != ACT_OK) return r; } else if (pt[idx] == 'M') { // M [A-F][1-6] [A-F][1-6]( [1-6])+, idx += 2; uint8_t drops[state->board_size]; const uint8_t s_col = pt[idx] - 'A', s_row = pt[idx + 1] - '1', d_col = pt[idx + 3] - 'A', d_row = pt[idx + 4] - '1'; idx += 4; enum MOVE_DIRECTION dir = M_RIGHT; if (s_col < d_col) dir = M_RIGHT; else if (s_col > d_col) dir = M_LEFT; else if (s_row < d_row) dir = M_UP; else if (s_row > d_row) dir = M_DOWN; uint8_t steps = 0; do { idx += 2; drops[steps++] = pt[idx] - '0'; } while (idx + 2 < read && pt[idx + 1] != ','); r = try_move(state, THE_COORDS(state->board_size, s_col, s_row), dir, steps, drops); if (r != ACT_OK) return r; if (generate == 0) { // Measure height of stacks exceeding 1 for (int k = 0; k < state->board_size * state->board_size; k++) { if (COUNT_AT(state, k) > 1) heights[COUNT_AT(state, k)] += 1; } } } // Generate training data, not too early in the game and not at // the end, under all eight symmetries of the board if (generate && state->ply > 7 && state->ply < total_plies && state->ply + 10 >= total_plies) { write_input(+1, +1, 1); write_input(+1, +1, 0); write_input(+1, -1, 1); write_input(+1, -1, 0); write_input(-1, +1, 1); write_input(-1, +1, 0); write_input(-1, -1, 1); write_input(-1, -1, 0); } // Parse next action while (idx < read && pt[idx++] != ',') ; if (idx >= read) return ACT_OK; next_ply(state); } return ACT_OK; } const char *license = "pptdb, generate neural network training data from a playtak.com database dump\n\ \n\ Copyright (C) 2021, tslil clingman\n\ \n\ This program comes with ABSOLUTELY NO WARRANTY; and is made available under the terms of the GNU GPL v3 license. This is free software, and you are welcome to redistribute it under certain conditions; see COPYING for details.\n"; int main(int argc, char **argv) { (void)(argc); enum ACT_RESULT r; enum WIN_TYPE win; uint32_t games = 0, overflow = 0, illegal = 0; uint32_t road_wins = 0, flat_wins = 0, road_turns = 0, flat_turns = 0, white_wins = 0, black_wins = 0; for (int k = 0; k < 16; k++) heights[k] = 0; size_t len = 0; ssize_t read = 0; FILE *playtak_fh = NULL; char *line = NULL, td_fn[65]; const uint8_t size = argv[1][0] - '0'; playtak_fh = fopen(argv[2], "r"); if (playtak_fh == NULL) exit(EXIT_FAILURE); if (argc > 3 && (!strncmp("generate", argv[3], 8))) { generate = 1; max_flats = (size == 5) ? 21.0 : 30.0; snprintf(td_fn, 64, "data/parsed-%d.csv", size); training_fh = fopen(td_fn, "w"); if (training_fh == NULL) exit(EXIT_FAILURE); } else generate = 0; state = new_tak_state(size); while ((read = getline(&line, &len, playtak_fh)) != -1) { // Reset everything reset_state(state, size); // Store the outcome of this game. Black win = 1 outcome_black = (line[read - 4] == '0'); // Parse the line r = parse_line(line, read - 4); // Adjust counts if we're not generating training data if (generate == 0) { if (r == ACT_ILLEGAL) { illegal++; printf("Illegal:\n%s", line); } else if (r == ACT_OVERFLOW) { printf("Overflow:\n%s", line); overflow++; } else { win = check_win(state); if (win == WIN_FLAT_BLACK || win == WIN_FLAT_WHITE || win == WIN_DRAW) { flat_wins++; flat_turns += state->ply / 2 + 1; } else { road_wins++; road_turns += state->ply / 2 + 1; } if (win == WIN_FLAT_BLACK || win == WIN_ROAD_BLACK) black_wins++; else if (win == WIN_FLAT_WHITE || win == WIN_ROAD_WHITE) white_wins++; } } games++; } fclose(playtak_fh); if (generate) fclose(training_fh); if (line) free(line); if (illegal || overflow) putchar('\n'); printf("Read %d games\n", games); if (generate == 0) { printf("Illegals: %d\nOverflows: %d\n\ Black wins: %.3f%%\n\ Road wins: %d\nFlat wins: %d\n\ Average turns to road win: %.3f\n\ Average turns to flat win: %.3f\n", illegal, overflow, (double)black_wins / (double)(black_wins + white_wins) * 100, road_wins, flat_wins, (double)(road_turns) / (double)(road_wins), (double)(flat_turns) / (double)(flat_wins)); for (int k = 2; k < 16; k++) { printf("Height %2d: %7ld\n", k, heights[k]); } } exit(EXIT_SUCCESS); }