/*
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 255
static tak_state_p state;
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
// ===================================================================
static int play_game(const nn_function current_fn, const nn_function cand_fn,
const enum COLOUR current_fn_colour,
const uint8_t random_opening_length) {
reset_state(state, state->board_size);
char ptn[16];
for (uint32_t ply = 0; ply + negamax_search_depth < MAX_PLY; ply++) {
if (ply < random_opening_length) {
uint32_t n_actions;
action_t *actions = actions_generate(state, &n_actions);
action_to_ptn(actions[rand32() % n_actions], ptn);
} else {
const nn_function fn =
(state->current_colour == current_fn_colour) ? current_fn : cand_fn;
negamax_set_evaluator(fn);
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;
}
}
void do_game(const uint8_t size, const uint8_t random_opening_length, const enum COLOUR current_fn_colour) {
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 int winner = play_game(current_fn, cand_fn, current_fn_colour, random_opening_length);
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);
}
// ===================================================================
// 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 x game, with a random side choice and both sides using random moves at the start of the game. The result format is:\n\n\
\t \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 uint8_t random_opening_length = (uint8_t)atoi(argv[2]);
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;
}
xors = ((uint64_t)time(NULL) ^ ((uint64_t)getpid() << 32)) *
0x9E3779B97F4A7C15ULL +
0xC0FFEE;
do_game(size, random_opening_length, C_BLACK);
do_game(size, random_opening_length, C_WHITE);
return EXIT_SUCCESS;
}