aboutsummaryrefslogtreecommitdiff
path: root/src/ctlm.c
diff options
context:
space:
mode:
authortslil clingman <tslil@posteo.de>2023-01-15 21:31:00 +0100
committertslil <tslil@posteo.de>2026-08-28 19:37:41 +0100
commit0223a9bec5535fced1a7698b55fd42155d9b0446 (patch)
treee7a980454e65d88b56194eed733cabec29ef51b5 /src/ctlm.c
parentee216c008a188a9436fedb85c70ee5d1719733b1 (diff)
switch to explicit game state & important bug fix & clang format
Previously the code base assumed that there was a single, global game state which was the implicit target of all actions taken. Looking ahead at architectural improvements, this has now been (almost entirely) made explicit and functions take tak_state_p where necessary (and also where unnecessary). Two important fixes to actions.c were made: - Previously when generating the possible stack moves, stack height overflows (> 15) were not taken into account and this resulted in the tree search corrupting the board state. Now action search does not list all legal actions, rather the subset of these encodeable by the implementation. - The check for crushing on a stack move was incorrect (too strict), and this resulted in many legitimate moves being igonored. Finally, in other changes, weights have also been improved by training all games instead of some subset for chosen players, and clang-format was run on the codebase.
Diffstat (limited to 'src/ctlm.c')
-rw-r--r--src/ctlm.c240
1 files changed, 121 insertions, 119 deletions
diff --git a/src/ctlm.c b/src/ctlm.c
index a8bc194..fa27274 100644
--- a/src/ctlm.c
+++ b/src/ctlm.c
@@ -17,6 +17,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+#include "actions.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -47,13 +48,13 @@ static void put_stone(const enum STONE_VARIANT stone, const enum COLOUR colour,
}
if (top) {
switch (stone) {
- case STONE_FLAT:
+ case STONE_FLAT:
putchar(CHAR_FLT);
break;
- case STONE_STANDING:
+ case STONE_STANDING:
putchar(CHAR_STN);
break;
- case STONE_CAPSTONE:
+ case STONE_CAPSTONE:
putchar(CHAR_CAP);
break;
}
@@ -63,11 +64,11 @@ static void put_stone(const enum STONE_VARIANT stone, const enum COLOUR colour,
fputs(rst, stdout);
}
-static void print_cell_line(const uint8_t line, const uint8_t col,
- const uint8_t row) {
+static void print_cell_line(tak_state_p state, const uint8_t line,
+ const uint8_t col, const uint8_t row) {
- const uint8_t location = THE_COORDS(col, row),
- stack_size = COUNT_AT(location);
+ const uint8_t location = THE_COORDS(state->board_size, col, row),
+ stack_size = COUNT_AT(state, location);
uint8_t idx;
for (uint8_t k = 0; k < SQUARE_W; k++) {
@@ -85,9 +86,9 @@ static void print_cell_line(const uint8_t line, const uint8_t col,
}
}
if (idx < stack_size) {
- put_stone(STONE_AT(location),
- (colours[location] & (1 << idx)) ? C_BLACK : C_WHITE, idx == 0,
- idx >= board_size);
+ put_stone(STONE_AT(state, location),
+ (state->colours[location] & (1 << idx)) ? C_BLACK : C_WHITE,
+ idx == 0, idx >= state->board_size);
} else {
putchar(' ');
}
@@ -96,9 +97,9 @@ static void print_cell_line(const uint8_t line, const uint8_t col,
// It takes board_size*(SQUARE_H+1)+1 lines to print the board, they
// may be requested in any order and at any time
-static void print_board_line(const uint8_t line) {
+static void print_board_line(tak_state_p state, const uint8_t line) {
const uint8_t mod = line % (SQUARE_H + 1),
- row = board_size - line / (SQUARE_H + 1) - 1;
+ row = state->board_size - line / (SQUARE_H + 1) - 1;
// Print leader, either row number if half-way through square or
// padding spaces otherwise
@@ -109,7 +110,7 @@ static void print_board_line(const uint8_t line) {
// Top and bottom of squares receive borders
if (mod == 0) {
- for (uint8_t x = 0; x < board_size; x++) {
+ for (uint8_t x = 0; x < state->board_size; x++) {
putchar('+');
for (uint8_t k = 0; k < SQUARE_W; k++)
putchar('-');
@@ -117,15 +118,15 @@ static void print_board_line(const uint8_t line) {
puts("+");
} else {
// Interior of board should be filled by borders and pieces
- if (line < board_size * (SQUARE_H + 1)) {
- for (uint8_t x = 0; x < board_size; x++) {
+ if (line < state->board_size * (SQUARE_H + 1)) {
+ for (uint8_t x = 0; x < state->board_size; x++) {
putchar('|');
- print_cell_line(mod - 1, x, row);
+ print_cell_line(state, mod - 1, x, row);
}
puts("|");
} else {
// Bottom of board has column markers
- for (uint8_t x = 0; x < board_size; x++) {
+ for (uint8_t x = 0; x < state->board_size; x++) {
for (uint8_t k = 0; k <= SQUARE_W / 2; k++)
putchar(' ');
printf("%c.", x + 'a');
@@ -138,47 +139,54 @@ static void print_board_line(const uint8_t line) {
}
// Simple wrapper to print the whole board in one go
-static void print_board(void) {
- for (uint8_t k = 0; k < board_size * (SQUARE_H + 1) + 2; k++) {
- print_board_line(k);
+static void print_board(tak_state_p state) {
+ for (uint8_t k = 0; k < state->board_size * (SQUARE_H + 1) + 2; k++) {
+ print_board_line(state, k);
}
}
// Print the contents of a single square
-static void print_square(const uint8_t col, const uint8_t row) {
- if (row < board_size && col < board_size) {
+static void print_square(tak_state_p state, const uint8_t col,
+ const uint8_t row) {
+ if (row < state->board_size && col < state->board_size) {
printf("%c%c: ", 'a' + col, '1' + row);
- const uint8_t stack_size = COUNT_AT(THE_COORDS(col, row));
+ const uint8_t stack_size =
+ COUNT_AT(state, THE_COORDS(state->board_size, col, row));
if (stack_size > 0) {
uint8_t mask = 1 << (stack_size - 1);
for (uint8_t k = 0; k < stack_size; k++, mask >>= 1) {
- put_stone(STONE_AT(THE_COORDS(col, row)),
- (colours[THE_COORDS(col, row)] & mask) ? C_BLACK : C_WHITE,
- k + 1 == stack_size, stack_size - k - 1 >= board_size);
+ put_stone(
+ STONE_AT(state, THE_COORDS(state->board_size, col, row)),
+ (state->colours[THE_COORDS(state->board_size, col, row)] & mask)
+ ? C_BLACK
+ : C_WHITE,
+ k + 1 == stack_size, stack_size - k - 1 >= state->board_size);
}
puts(" <-- top");
} else {
puts("(empty)");
}
} else {
- printf("Requested square not on board (%dx%d).\n", board_size, board_size);
+ printf("Requested square not on board (%dx%d).\n", state->board_size,
+ state->board_size);
}
}
-static void print_info(void) {
- printf("Turn: %2d, %s%s%s%s\n", ply / 2 + 1, (ply & 1) ? blk : wht,
- (ply & 1) ? "Black" : "White", rst,
- (ply < 2) ? " (counter-play start)" : "");
+static void print_info(tak_state_p state) {
+ printf("Turn: %2d, %s%s%s%s\n", state->ply / 2 + 1,
+ (state->ply & 1) ? blk : wht, (state->ply & 1) ? "Black" : "White",
+ rst, (state->ply < 2) ? " (counter-play start)" : "");
printf("Flats/Caps remaining: %s%02d/%d%s, %s%02d/%d%s\n", wht,
- white_count & 127, white_count >> 7, rst, blk, black_count & 127,
- black_count >> 7, rst);
+ state->white_count & 127, state->white_count >> 7, rst, blk,
+ state->black_count & 127, state->black_count >> 7, rst);
}
static int human;
static char *gamelog = NULL;
static uint8_t auto_board = 0xFF, auto_info = 0xFF;
-static int append_to_gamelog(const char *line, const uint8_t win_line) {
+static int append_to_gamelog(tak_state_p state, const char *line,
+ const uint8_t win_line) {
// I _could_ dynamically compute the size but ... don't let
// `perfect' be the enemy of `good' ?
@@ -190,14 +198,15 @@ static int append_to_gamelog(const char *line, const uint8_t win_line) {
if (win_line) {
prepend[0] = '\n';
prepend[1] = 0;
- } else if (ply & 1) {
- snprintf(prepend, 7, "%s%d. ", (ply == 1) ? "" : "\n", ply / 2 + 1);
+ } else if (state->ply & 1) {
+ snprintf(prepend, 7, "%s%d. ", (state->ply == 1) ? "" : "\n",
+ state->ply / 2 + 1);
} else {
strcpy(prepend, " ");
}
// Make room for this line
gamelog =
- realloc(gamelog, strlen(gamelog) + strlen(prepend) + strlen(line) + 1);
+ realloc(gamelog, strlen(gamelog) + strlen(prepend) + strlen(line) + 1);
// TODO: trap errno
strcat(gamelog, prepend);
strcat(gamelog, line);
@@ -205,79 +214,79 @@ static int append_to_gamelog(const char *line, const uint8_t win_line) {
return EXIT_SUCCESS;
}
-static void end_game(char *line, char *win) {
- append_to_gamelog(line, 0);
- append_to_gamelog(win, 1);
- print_board();
+static void end_game(tak_state_p state, char *line, char *win) {
+ append_to_gamelog(state, line, 0);
+ append_to_gamelog(state, win, 1);
+ print_board(state);
puts("Game over:");
puts(gamelog);
putchar('\n');
}
-static int handle_turn(char *line) {
+static int handle_turn(tak_state_p state, char *line) {
// Track win state
- uint8_t new_win = (won == 0xFF);
- switch (do_ptn(line)) {
- // Errors
- case ACT_INVALID_PTN: {
- puts("Invalid PTN.");
- return EXIT_FAILURE;
- }
- case ACT_ILLEGAL: {
- puts("Illegal action.");
- return EXIT_FAILURE;
- }
- case ACT_OVERFLOW: {
- puts("Move would cause internal overflow, select another.");
- return EXIT_FAILURE;
- }
- // Game has ended
- case GAME_END: {
- // Did it end this turn?
- if (new_win) {
- switch (won) {
+ uint8_t new_win = (state->won == 0xFF);
+ switch (do_ptn(state, line)) {
+ // Errors
+ case ACT_INVALID_PTN: {
+ puts("Invalid PTN.");
+ return EXIT_FAILURE;
+ }
+ case ACT_ILLEGAL: {
+ puts("Illegal action.");
+ return EXIT_FAILURE;
+ }
+ case ACT_OVERFLOW: {
+ puts("Move would cause internal overflow, select another.");
+ return EXIT_FAILURE;
+ }
+ // Game has ended
+ case GAME_END: {
+ // Did it end this turn?
+ if (new_win) {
+ switch (state->won) {
case WIN_DRAW: {
- end_game(line, "1/2-1/2");
+ end_game(state, line, "1/2-1/2");
break;
}
case WIN_FLAT_BLACK: {
- end_game(line, "0-F");
+ end_game(state, line, "0-F");
break;
}
case WIN_FLAT_WHITE: {
- end_game(line, "F-0");
+ end_game(state, line, "F-0");
break;
}
case WIN_ROAD_BLACK: {
- end_game(line, "0-R");
+ end_game(state, line, "0-R");
break;
}
case WIN_ROAD_WHITE: {
- end_game(line, "R-0");
+ end_game(state, line, "R-0");
break;
}
- }
}
- puts("Enter `new' to play again.");
- if (!new_win)
- return EXIT_FAILURE;
- break;
- }
- // Valid, append to game log
- case ACT_OK: {
- append_to_gamelog(line, 0);
- if (auto_board)
- print_board();
- if (auto_info)
- print_info();
- break;
}
+ puts("Enter `new' to play again.");
+ if (!new_win)
+ return EXIT_FAILURE;
+ break;
+ }
+ // Valid, append to game log
+ case ACT_OK: {
+ append_to_gamelog(state, line, 0);
+ if (auto_board)
+ print_board(state);
+ if (auto_info)
+ print_info(state);
+ break;
+ }
}
return EXIT_SUCCESS;
}
-static void new_game(uint8_t size) {
- reset_state(size);
+static void new_game(tak_state_p state, uint8_t size) {
+ reset_state(state, size);
printf("New %dx%d game! negamax at search depth %d.\n", size, size,
negamax_search_depth);
if (gamelog)
@@ -287,7 +296,7 @@ static void new_game(uint8_t size) {
gamelog[0] = 0;
}
-static int load_ptn(const char *fn) {
+static int load_ptn(tak_state_p state, const char *fn) {
FILE *fh = NULL;
fh = fopen(fn, "r");
@@ -316,14 +325,14 @@ static int load_ptn(const char *fn) {
space2++;
line[space2] = 0;
// Try the first piece we found
- r = handle_turn(line + space1);
+ r = handle_turn(state, line + space1);
if (r) {
printf("Error on: %s\n", line + space1);
break;
}
// If there's a second piece, try it
if (space2 + 1 < read) {
- r = handle_turn(line + space2 + 1);
+ r = handle_turn(state, line + space2 + 1);
if (r) {
printf("Error on: %s", line + space2 + 1);
break;
@@ -362,37 +371,37 @@ inline void negamax_display_progress(const uint8_t cur_depth,
}
}
-static int negamax_turn(void) {
- if (won == 0xFF) {
+static int negamax_turn(tak_state_p state) {
+ if (state->won == 0xFF) {
// Run the minimax
num_check = 0;
progress = 0;
old_depth = 0;
- float minimax = negamax_generate();
+ float minimax = negamax_generate(state);
putchar('\n');
// Failed to find a non-losing move?
if (minimax <= -infty)
puts("Opponent concedes!");
printf("Result: %s (%.2f, checked %.1e)\n", negamax_ptn, minimax * 100.0,
num_check);
- return handle_turn(negamax_ptn);
+ return handle_turn(state, negamax_ptn);
} else {
return EXIT_FAILURE;
}
}
-static int input_is_not_turn(const char *line) {
+static int input_is_not_turn(tak_state_p state, const char *line) {
if (!strcmp(line, "help")) {
puts("Valid commands: auto (board|info), board, depth [0-9], eval, \
help, info, load <file.ptn>, log, new, play (b|w), self-play, square\
<col><row>, tps, <PTN>.");
} else if (!strcmp(line, "board")) {
- print_board();
+ print_board(state);
} else if (!strcmp(line, "info")) {
- print_info();
+ print_info(state);
} else if (!strcmp(line, "eval")) {
- float eval = nn1986_evaluate_black_win() * 100;
- if (ply & 1) {
+ float eval = nn1986_evaluate_black_win(state) * 100;
+ if (state->ply & 1) {
printf("Black heuristic chance: %s%.2f%s\n", blk, eval, rst);
} else {
printf("White heruistic chance: %s%.2f%s\n", wht, -eval, rst);
@@ -400,13 +409,13 @@ help, info, load <file.ptn>, log, new, play (b|w), self-play, square\
} else if (!strcmp(line, "log")) {
puts(gamelog);
} else if (!strcmp(line, "new")) {
- new_game(5);
+ new_game(state, 5);
} else if (!strcmp(line, "tps")) {
char buf[1000];
- generate_tps(buf);
+ generate_tps(state, buf);
puts(buf);
} else if (!strcmp(line, "self-play")) {
- while (negamax_turn() == 0)
+ while (negamax_turn(state) == 0)
;
} else if (!strncmp(line, "depth", 5)) {
if (strnlen(line, 7) == 7 && line[6] >= '0' && line[6] <= '9') {
@@ -417,7 +426,7 @@ help, info, load <file.ptn>, log, new, play (b|w), self-play, square\
}
} else if (!strncmp(line, "load", 4)) {
if (strnlen(line, 6) >= 6) {
- if (load_ptn(line + 5)) {
+ if (load_ptn(state, line + 5)) {
printf("Errors in file %s\n", line);
}
} else {
@@ -437,19 +446,19 @@ help, info, load <file.ptn>, log, new, play (b|w), self-play, square\
}
} else if (!strncmp(line, "square", 6)) {
if (strnlen(line, 10) == 9 && line[7] >= 'a' &&
- line[7] <= '`' + board_size && line[8] >= '1' &&
- line[8] <= '0' + board_size) {
- print_square(line[7] - 'a', line[8] - '1');
+ line[7] <= '`' + state->board_size && line[8] >= '1' &&
+ line[8] <= '0' + state->board_size) {
+ print_square(state, line[7] - 'a', line[8] - '1');
} else {
- printf("Usage: square [a-%c][1-%c].\n", '`' + board_size,
- '0' + board_size);
+ printf("Usage: square [a-%c][1-%c].\n", '`' + state->board_size,
+ '0' + state->board_size);
}
} else if (!strncmp(line, "play", 4)) {
if (strnlen(line, 7) == 6 && ((line[5] == 'b' || line[5] == 'B') ||
(line[5] == 'w' || line[5] == 'W'))) {
// 'b' is even :)
human = 1 - (line[5] & 1);
- new_game(5);
+ new_game(state, 5);
} else {
puts("Usage: play (b|w).");
}
@@ -471,19 +480,12 @@ int main(int argc, char **argv) {
puts(license);
+ tak_state_p state = new_tak_state(5);
+
negamax_search_depth = 5;
- new_game(5);
+ new_game(state, 5);
negamax_init(5);
- // Test harness
- if (argc > 1) {
- negamax_search_depth = 7;
- load_ptn("data/0.ptn");
- negamax_turn();
- return 0;
- }
- // Test harness
-
char *line = NULL;
ssize_t read = -1;
size_t alloc_size;
@@ -496,9 +498,9 @@ int main(int argc, char **argv) {
read = getline(&line, &alloc_size, stdin);
if (read > 0) {
line[read - 1] = 0;
- if (input_is_not_turn(line)) {
- int r = handle_turn(line);
- if (r == EXIT_SUCCESS && won == 0xFF) {
+ if (input_is_not_turn(state, line)) {
+ int r = handle_turn(state, line);
+ if (r == EXIT_SUCCESS && state->won == 0xFF) {
human = 1;
}
}
@@ -510,7 +512,7 @@ int main(int argc, char **argv) {
}
}
if (playing) {
- negamax_turn();
+ negamax_turn(state);
human = 0;
}
}