aboutsummaryrefslogtreecommitdiff
path: root/src/selfplay.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/selfplay.c')
-rw-r--r--src/selfplay.c74
1 files changed, 36 insertions, 38 deletions
diff --git a/src/selfplay.c b/src/selfplay.c
index 8cd11b9..e8ff506 100644
--- a/src/selfplay.c
+++ b/src/selfplay.c
@@ -25,7 +25,6 @@
#include "actions.h"
#include "negamax.h"
-#include "nn.h"
#include "pv.h"
#include "tak.h"
#include "xorshift64.h"
@@ -34,18 +33,21 @@
#error "selfplay must be built with -DRECORD_PV (see the selfplay Makefile target)"
#endif
-#define MAX_PLY 256
+#define MAX_PLY 255
#define MAX_CELLS (6 * 6)
static tak_state_p state;
-static uint32_t min_ply;
static float row_head[MAX_PLY][3];
static uint8_t row_cells[MAX_PLY][MAX_CELLS];
static uint8_t row_term[MAX_PLY];
static float row_terminal_value[MAX_PLY];
+static uint32_t row_game_id[MAX_PLY];
static uint32_t n_rows;
-static uint32_t game_id; // PID, tags every row so games can be grouped
+
+static uint32_t game_id_black;
+static uint32_t game_id_white;
+
void negamax_display_progress(const uint8_t cur_depth,
const uint8_t init_depth,
@@ -54,47 +56,45 @@ void negamax_display_progress(const uint8_t cur_depth,
(void)(init_depth);
(void)(length);
}
+
static float win_value(const enum WIN_TYPE w) {
switch (w) {
- case WIN_FLAT_BLACK:
- case WIN_ROAD_BLACK:
+ case WIN_FLAT_BLACK:
+ case WIN_ROAD_BLACK:
return 1.0f;
- case WIN_FLAT_WHITE:
- case WIN_ROAD_WHITE:
+ case WIN_FLAT_WHITE:
+ case WIN_ROAD_WHITE:
return -1.0f;
- default:
+ default:
return 0.0f;
}
}
-static int play_game(const uint32_t eps10000) {
+static int play_game(const uint8_t min_ply) {
reset_state(state, state->board_size);
n_rows = 0;
- const enum COLOUR eps_side = (rand32() & 1) ? C_BLACK : C_WHITE;
-
char ptn[16];
for (uint32_t ply = 0; ply < MAX_PLY; ply++) {
- negamax_generate(state);
-
- if (state->current_colour == eps_side && rand32() % 10000 < eps10000) {
+ if (ply < min_ply) {
uint32_t n_actions;
action_t *actions = actions_generate(state, &n_actions);
action_to_ptn(actions[rand32() % n_actions], ptn);
} else {
+ negamax_generate(state);
+
strcpy(ptn, negamax_ptn);
- }
- const pv_leaf_t *leaf = pv_get_leaf();
- if (leaf->current_colour != eps_side && ply >= min_ply &&
- n_rows < MAX_PLY) {
+ const pv_leaf_t *leaf = pv_get_leaf();
memcpy(row_head[n_rows], leaf->head, sizeof(row_head[n_rows]));
memcpy(row_cells[n_rows], leaf->cells, sizeof(row_cells[n_rows]));
row_term[n_rows] = leaf->terminal;
row_terminal_value[n_rows] = leaf->terminal_value;
+ row_game_id[n_rows] = state->current_colour == C_BLACK ? game_id_black : game_id_white;
n_rows++;
}
+
const enum ACT_RESULT r = do_ptn(state, ptn);
if (r == GAME_END)
break;
@@ -118,7 +118,7 @@ static int play_game(const uint32_t eps10000) {
row_head[k][2]);
for (uint8_t c = 0; c < n_cells; c++)
fprintf(stdout, "%u,", (unsigned)row_cells[k][c]);
- fprintf(stdout, "%u,%.0f,%u,%.0f\n", game_id, R,
+ fprintf(stdout, "%u,%.0f,%u,%.0f\n", row_game_id[k], R,
(unsigned)row_term[k], row_terminal_value[k]);
}
@@ -126,27 +126,23 @@ static int play_game(const uint32_t eps10000) {
}
const char *usage =
- "selfplay, one game of asymmetric self-play training data.\n\
-Usage: selfplay <size> <eps> <depth> <min_ply>\n\
-Plays a single asymmetric game of <size>x<size> self-play: one side plays the engine's best move at every ply (pure minimax) and the other side plays the engine's best move except that with probability eps it plays a uniform random legal move; the colour assignment of these two players is random. Only the deterministic side's plies are written to stdout, one CSV row per recorded search, from ply <min_ply> onwards:\n\
-\n\
-parity,white_flats,black_flats,<cell ids...>,game_id,R,terminal,leaf_R\n\
-nn_train.py runs many of these in parallel to generate training data.\n\
-\n\
-Copyright (C) 2026, tslil clingman\n\
-\n\
+ "selfplay, one game of asymmetric self-play training data.\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 \
+\tparity,white_flats,black_flats,<cell ids...>,game_id,R,terminal,leaf_R\n\n \
+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.";
int main(int argc, char **argv) {
- if (argc != 5) {
+ 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]);
- min_ply = (uint32_t)atoi(argv[4]);
+ negamax_search_depth = (uint8_t)atoi(argv[2]);
+ const uint8_t min_ply = (uint8_t)atoi(argv[3]);
if (size != 5 && size != 6) {
fprintf(stderr, "selfplay: size must be 5 or 6\n");
@@ -156,21 +152,23 @@ int main(int argc, char **argv) {
fprintf(stderr, "selfplay: depth must be <= %d\n", NEGAMAX_MAX_DEPTH);
return EXIT_FAILURE;
}
- if (eps10000 >= 10000) {
- fprintf(stderr, "selfplay: eps must be < 1\n");
+ if (min_ply >= MAX_PLY) {
+ fprintf(stderr, "selfplay: min_ply must be < %d\n", MAX_PLY);
return EXIT_FAILURE;
}
// Processes launched in the same second must not share a seed
- game_id = (uint32_t)getpid();
xors = ((uint64_t)time(NULL) ^ ((uint64_t)getpid() << 32)) *
0x9E3779B97F4A7C15ULL +
- 0xC0FFEEEE;
+ 0xC0FFEE;
+
+ game_id_black = rand32();
+ game_id_white = rand32();
state = new_tak_state(size);
negamax_init(size);
- play_game(eps10000);
+ play_game(min_ply);
negamax_free();
free_tak_state(state);