/* match, one game of candidate weights against the candidate weights Copyright (C) 2026, 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 ctak. If not, see . */ #include #include #include #include #include #include "actions.h" #include "negamax.h" #include "nn.h" #include "tak.h" #include "xorshift64.h" // a little gymnastics to insert candidate neural networks #include "weights_5.h" #include "weights_6.h" #include "cand_weights_5.c" #include "cand_weights_6.c" static inline float five_cand(tak_state_p state) { static float cur_board[FIVE_INP_NUM] NN_ALIGNED; nn_prepare_input(cur_board, state, cand_five_embedding); return eval_net(cur_board, FIVE_INP_NUM, &cand_five_dense_weights[0][0], cand_five_dense_biases, cand_five_logit_weights, cand_five_logit_bias, FIVE_DENSE_NUM); } static inline float six_cand(tak_state_p state) { static float cur_board[SIX_INP_NUM] NN_ALIGNED; nn_prepare_input(cur_board, state, cand_six_embedding); return eval_net(cur_board, SIX_INP_NUM, &cand_six_dense_weights[0][0], cand_six_dense_biases, cand_six_logit_weights, cand_six_logit_bias, SIX_DENSE_NUM); } #define MAX_PLY 250 static tak_state_p state; // =================================================================== // negamax progress hook (silent) // =================================================================== void negamax_display_progress(const uint8_t cur_depth, const uint8_t init_depth, const uint32_t length) { (void)(cur_depth); (void)(init_depth); (void)(length); } // =================================================================== // Game driver // =================================================================== // Returns the winner's colour, or -1 for a draw / abort static int play_game(const nn_function current_fn, const nn_function cand_fn, const enum COLOUR current_fn_colour, const uint32_t eps10000) { reset_state(state, state->board_size); char ptn[16]; for (uint32_t ply = 0; ply < MAX_PLY; ply++) { // Whose evaluator is it? const nn_function fn = (state->current_colour == current_fn_colour) ? current_fn : cand_fn; negamax_set_evaluator(fn); if (rand32() % 10000 < eps10000) { // Epsilon: uniform random legal move uint32_t n_actions; action_t *actions = actions_generate(state, &n_actions); action_to_ptn(actions[rand32() % n_actions], ptn); } else { // Greedy: the side's best move negamax_generate(state); strcpy(ptn, negamax_ptn); } const enum ACT_RESULT r = do_ptn(state, ptn); if (r == GAME_END) break; if (r != ACT_OK) { fprintf(stderr, "match: unexpected result %d for ptn %s\n", r, ptn); return -2; } } switch (state->won) { case WIN_DRAW: return -1; case WIN_FLAT_BLACK: case WIN_ROAD_BLACK: return C_BLACK; case WIN_FLAT_WHITE: case WIN_ROAD_WHITE: return C_WHITE; default: // ply cap reached with no winner: a draw return -1; } } // =================================================================== // Main // =================================================================== const char *usage = "match, one Tak game of the current weights against the candidate.\n\ \n \ usage: match \n\ \n\ This binary is linked with both the include/weight_* files and the include/cand_weight_* files. It plays a single game, with a random side choice and both sides using epsilon-greedy move noise. The result format is:\n\ \n\ \n\ \n\ nn_train.py runs many of these in parallel to score the candidate against the current weights.\n\ \n\ Copyright (C) 2026, tslil clingman\n\ \n\ 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."; int main(int argc, char **argv) { if (argc != 4) { puts(usage); return EXIT_FAILURE; } const uint8_t size = (uint8_t)atoi(argv[1]); const uint32_t eps10000 = (uint32_t)(atof(argv[2]) * 10000.0 + 0.5); negamax_search_depth = (uint8_t)atoi(argv[3]); if (size != 5 && size != 6) { fprintf(stderr, "match: size must be 5 or 6\n"); return EXIT_FAILURE; } if (eps10000 >= 10000) { fprintf(stderr, "match: eps must be < 1\n"); return EXIT_FAILURE; } // Processes launched in the same second must not share a seed xors = ((uint64_t)time(NULL) ^ ((uint64_t)getpid() << 32)) * 0x9E3779B97F4A7C15ULL + 0xDEADBEEF; state = new_tak_state(size); negamax_init(size); const nn_function current_fn = (size == 5) ? &five_nn_fn : &six_nn_fn; const nn_function cand_fn = (size == 5) ? &five_cand : &six_cand; const enum COLOUR current_fn_colour = rand32() % 2 ? C_WHITE : C_BLACK; const int winner = play_game(current_fn, cand_fn, current_fn_colour, eps10000); const char *name; if (winner == -1) name = "draw"; else if (winner == (int)current_fn_colour) name = "current"; else name = "candidate"; printf("%s %u\n", name, state->ply); negamax_free(); free_tak_state(state); return EXIT_SUCCESS; }