aboutsummaryrefslogtreecommitdiff
path: root/src/selfplay.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/selfplay.c')
-rw-r--r--src/selfplay.c125
1 files changed, 88 insertions, 37 deletions
diff --git a/src/selfplay.c b/src/selfplay.c
index 42d82d2..f6736c0 100644
--- a/src/selfplay.c
+++ b/src/selfplay.c
@@ -30,25 +30,45 @@
#include "xorshift64.h"
#ifndef RECORD_PV
-#error "selfplay must be built with -DRECORD_PV (see the selfplay Makefile target)"
+#error "selfplay must be built with -DRECORD_PV"
#endif
-#define MAX_PLY 255
-#define MAX_CELLS (6 * 6)
+#ifndef CAND_A
+#error "selfplay must be built with -DCAND_A=<id>"
+#endif
-static tak_state_p state;
+#ifndef CAND_B
+#error "selfplay must be built with -DCAND_B=<id>"
+#endif
+
+#include CAND_A_WEIGHTS_5
+#include CAND_B_WEIGHTS_5
+#include CAND_A_WEIGHTS_6
+#include CAND_B_WEIGHTS_6
+
+// sorry not sorry
+#define STR2U(a, b) a##_##b
+#define STR4U(a, b, c, d) a##_##b##_##c##_##d
+#define STR4U_EXPAND(a, b, c, d) STR4U(a, b, c, d)
+#define C(variant, size, suffix) STR4U_EXPAND(cand, STR2U(CAND, variant), size, suffix)
+#define NETWORK(size, called) \
+ static inline float STR2U(size, called)(tak_state_p state) { \
+ static float cur_board[STR2U(size, INP_NUM)] NN_ALIGNED; \
+ nn_prepare_input(cur_board, state, C(called, size, embedding)); \
+ return eval_net(cur_board, STR2U(size, INP_NUM), &C(called, size, dense_weights)[0][0], \
+ C(called, size, dense_biases), C(called, size, logit_weights), C(called, size, logit_bias), \
+ STR2U(size, DENSE_NUM), state->ply & 1 ? 1.0f : -1.0f); \
+ }
-static float row_head[MAX_PLY][2];
-static uint8_t row_cells[MAX_PLY][MAX_CELLS];
-static uint8_t row_term[MAX_PLY];
-static float row_terminal_value[MAX_PLY];
-static float row_parity_sign[MAX_PLY];
-static uint32_t row_game_id[MAX_PLY];
-static uint32_t n_rows;
+NETWORK(FIVE, A)
+NETWORK(FIVE, B)
+NETWORK(SIX, A)
+NETWORK(SIX, B)
-static uint32_t game_id_black;
-static uint32_t game_id_white;
+#define MAX_PLY 255
+#define MAX_CELLS (6 * 6)
+static tak_state_p state;
void negamax_display_progress(const uint8_t cur_depth,
const uint8_t init_depth,
@@ -58,30 +78,49 @@ void negamax_display_progress(const uint8_t cur_depth,
(void)(length);
}
-static float win_value(const enum WIN_TYPE w) {
+static int8_t win_value(const enum WIN_TYPE w) {
switch (w) {
case WIN_FLAT_BLACK:
case WIN_ROAD_BLACK:
- return 1.0f;
+ return 1;
case WIN_FLAT_WHITE:
case WIN_ROAD_WHITE:
- return -1.0f;
+ return -1;
default:
- return 0.0f;
+ return 0;
}
}
-static int play_game(const uint8_t min_ply) {
+static int play_game(const uint8_t min_ply, const nn_function fn_1,
+ const nn_function fn_2, const uint32_t cand_id_1,
+ const uint32_t cand_id_2,
+ const uint32_t game_id, char record[][16], uint8_t replay) {
+ float row_head[MAX_PLY][2];
+ uint8_t row_cells[MAX_PLY][MAX_CELLS];
+ uint8_t row_term[MAX_PLY];
+ float row_terminal_value[MAX_PLY];
+ float row_parity_sign[MAX_PLY];
+ uint32_t row_cand_id[MAX_PLY];
+ int8_t row_mover_sign[MAX_PLY];
+ uint32_t n_rows;
+
reset_state(state, state->board_size);
n_rows = 0;
char ptn[16];
for (uint32_t ply = 0; ply + negamax_search_depth < MAX_PLY; ply++) {
if (ply < min_ply) {
- uint32_t n_actions;
- action_t *actions = actions_generate(state, &n_actions);
- action_to_ptn(actions[rand32() % n_actions], ptn);
+ if (replay == 0) {
+ uint32_t n_actions;
+ action_t *actions = actions_generate(state, &n_actions);
+ action_to_ptn(actions[rand32() % n_actions], ptn);
+ strcpy(record[ply],ptn);
+ } else {
+ strcpy(ptn, record[ply]);
+ }
} else {
+ const nn_function fn = (state->current_colour & 1) ? fn_1 : fn_2;
+ negamax_set_evaluator(fn);
negamax_generate(state);
strcpy(ptn, negamax_ptn);
@@ -92,7 +131,8 @@ static int play_game(const uint8_t min_ply) {
row_term[n_rows] = leaf->terminal;
row_terminal_value[n_rows] = leaf->terminal_value;
row_parity_sign[n_rows] = leaf->parity_sign;
- row_game_id[n_rows] = state->current_colour == C_BLACK ? game_id_black : game_id_white;
+ row_cand_id[n_rows] = (state->current_colour&1) ? cand_id_1 : cand_id_2;
+ row_mover_sign[n_rows] = (state->current_colour&1) ? 1 : -1;
n_rows++;
}
@@ -113,25 +153,25 @@ static int play_game(const uint8_t min_ply) {
const uint8_t n_cells = state->board_size * state->board_size;
// final outcome of the played game, from black's perspective
- const float R = win_value(state->won);
+ const int8_t R = win_value(state->won);
for (uint32_t k = 0; k < n_rows; k++) {
- fprintf(stdout, "%.8f,%.8f,", row_head[k][0], row_head[k][1]);
+ fprintf(stdout, "%.1f,%.8f,%.8f,", row_parity_sign[k], row_head[k][0], row_head[k][1]);
for (uint8_t c = 0; c < n_cells; c++)
- fprintf(stdout, "%u,", (unsigned)row_cells[k][c]);
- fprintf(stdout, "%u,%.0f,%u,%.0f,%.0f\n", row_game_id[k], R,
- (unsigned)row_term[k], row_terminal_value[k], row_parity_sign[k]);
+ fprintf(stdout, "%u,", row_cells[k][c]);
+ fprintf(stdout, "%i,%u,%.0f,%u,%u,%i\n", R, row_term[k], row_terminal_value[k], game_id, row_cand_id[k],
+ row_mover_sign[k] * R);
}
return (int)n_rows;
}
const char *usage =
- "selfplay, one game of asymmetric self-play training data.\n\n\
+ "selfplay, one game of training data between candidates.\n\n\
Usage: selfplay <size> <depth> <min_ply>\n\n\
-Plays a single game of <size>x<size> self-play, with <min_ply> moves made randomly. Only the deterministic plies are written to stdout, one CSV row per recorded search, from ply <min_ply> onwards:\n\n\
-\tmover_flats,opponent_flats,<cell ids...>,game_id,R,terminal,leaf_R,parity_sign\n\n\
-nn_train.py runs many of these in parallel to generate training data.\n\n\
+Plays a single game of <size>x<size>, with <min_ply> moves made randomly. Only the deterministic plies are written to stdout, one CSV row per recorded search, from ply <min_ply> onwards:\n\n\
+\tparity_sign,mover_flats,opponent_flats,<cell ids...>,R,terminal,leaf_R,game_id,cand_id,cand_R\n\n\
+R is the outcome from black's perspective, cand_R the outcome from the recorded candidate's perspective. nn_train.py runs many of these in parallel to generate training data.\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.";
@@ -160,16 +200,27 @@ int main(int argc, char **argv) {
// Processes launched in the same second must not share a seed
xors = ((uint64_t)time(NULL) ^ ((uint64_t)getpid() << 32)) *
- 0x9E3779B97F4A7C15ULL +
- 0xC0FFEE;
-
- game_id_black = rand32();
- game_id_white = rand32();
+ 0x9E3779B97F4A7C15ULL +
+ 0xC0FFEE;
state = new_tak_state(size);
negamax_init(size);
- play_game(min_ply);
+ char record[min_ply][16];
+
+ play_game(min_ply,
+ size == 5 ? FIVE_A : SIX_A, size == 5 ? FIVE_B : SIX_B,
+ CAND_A,
+ CAND_B,
+ rand32(),
+ record, 0);
+ play_game(min_ply,
+ size == 5 ? FIVE_B : SIX_B, size == 5 ? FIVE_A : SIX_A,
+ CAND_B,
+ CAND_A,
+ rand32(),
+ record, 1);
+
negamax_free();
free_tak_state(state);