aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortslil clingman <tslil@posteo.de>2021-01-21 01:18:01 -0500
committertslil <tslil@posteo.de>2026-08-28 19:37:41 +0100
commit19359dde885243e2359f7c560ae801efccee7294 (patch)
treeb27bc6f934e05b6494af8527b6b55bc554dc4c10
parent2ebcba8c471678942d0cbf496b79704df9bafff3 (diff)
parent9fa3291044ff8d9f0f2b9c9a01cd210bf318ab74 (diff)
Merge branch 'negamax'
-rw-r--r--include/minimax_cnn1986.h19
-rw-r--r--include/negamax_cnn1986.c (renamed from include/minimax_cnn1986.c)182
-rw-r--r--include/negamax_cnn1986.h18
-rw-r--r--include/tak.c2
-rw-r--r--src/ctaklm.c25
5 files changed, 123 insertions, 123 deletions
diff --git a/include/minimax_cnn1986.h b/include/minimax_cnn1986.h
deleted file mode 100644
index 3bc3301..0000000
--- a/include/minimax_cnn1986.h
+++ /dev/null
@@ -1,19 +0,0 @@
-#include <stdint.h>
-#include "tak.h"
-#include "weights.h"
-
-extern const float infty;
-extern char ct1986_ptn[9];
-extern inline void ct1986_display_progress(const uint8_t);
-
-float
-ct1986_evaluate_black_win(void);
-
-// Generate PTN of the ````best'''' action and store it in ct1986_ptn,
-// along with its value as the return. The ct1986_display_progress
-// function pointer is called on every new square.
-
-float ct1986_generate(const uint8_t max_depth);
-
-extern char ct1986_ptn[9];
-extern float ct1986_optimal;
diff --git a/include/minimax_cnn1986.c b/include/negamax_cnn1986.c
index fed30e2..fafb38c 100644
--- a/include/minimax_cnn1986.c
+++ b/include/negamax_cnn1986.c
@@ -1,4 +1,4 @@
-#include "minimax_cnn1986.h"
+#include "negamax_cnn1986.h"
// ===================================================================
// Globals
@@ -6,6 +6,7 @@
const float infty = 3.0;
char ct1986_ptn[9];
+uint8_t ct1986_search_depth = 3;
// ===================================================================
// Implementation of a small convolutional neural network
@@ -114,14 +115,17 @@ ct1986_evaluate_black_win(void) {
DOXORSHIFT;
output += fudge.f;
#endif
- if (output > 1.0) return 1.0;
- else if (output < 0.0) return 0.0;
-
- return output;
+ if (output > 1.0) {
+ return 1.0;
+ }
+ else if (output < 0.0) {
+ return -1.0;
+ }
+ return 2*output-1.0;
}
// ===================================================================
-// α-β minimax using the above evaluator
+// α-β negamax using the above evaluator
// ===================================================================
static void
@@ -135,63 +139,61 @@ previous_ply(void) {
}
}
+static inline void
+push_stones(const int8_t location, const uint8_t count,
+ const uint8_t new_colours,
+ const enum STONE_VARIANT top_stone) {
+ colours[location] = (colours[location] << count) | new_colours;
+ celldat[location] = top_stone
+ | ((celldat[location] + ((count << NUM_SHIFT))) & NUM_MASK);
+}
+
static float val;
static enum WIN_TYPE w;
-#define WIN_EVALUATE_OR_RECURSE(store) { \
+// UP DOWN LEFT RIGHT
+static const int8_t deltas[4] = { +5, -5, -1, +1};
+
+#define WIN_EVALUATE_OR_RECURSE(store,reset) { \
w = 0xFF; \
if (ply >= 2*5 - 2) w = check_win(); \
if (w < 0xFF) { \
- /* Somebody won, assign weights accordingly */ \
- if (min == 0) { \
- if (w == WIN_ROAD_BLACK || w == WIN_FLAT_BLACK) \
- val = infty; \
- else val = -infty; \
- } else { \
- if (w == WIN_ROAD_WHITE || w == WIN_FLAT_WHITE) \
- val = -infty; \
- else val = infty; \
- } \
- } else if (cur_depth == max_depth) { \
+ /* Somebody won, assign weights accordingly. Note in particular
+ that draws are only worth ∞/2 ;)
+ */ \
+ if (w == WIN_ROAD_BLACK || w == WIN_FLAT_BLACK) \
+ val = infty; \
+ else if (w == WIN_DRAW) val = infty/2.0; \
+ else val = -infty; \
+ val *= colour; \
+ } else if (cur_depth == ct1986_search_depth) { \
/* We're at the bottom, evaluate */ \
- val = ct1986_evaluate_black_win(); \
- if ((ply & 1) == 0) val = 1.0 - 2*val; \
- if (min) val *= -1; \
+ val = colour * ct1986_evaluate_black_win(); \
} else { \
/* We're not at the bottom, recurse first */ \
next_ply(); \
- val = ct1986_minimax(cur_depth + 1, max_depth, 1-min, alpha, beta); \
+ val = -ct1986_negamax(cur_depth + 1, -beta, -alpha, -colour); \
previous_ply(); \
} \
- /* Update the optimal value */ \
- if (((min > 0) && (val <= optimal)) \
- || ((min == 0) && (val >= optimal))) { \
- optimal = val; \
- if (cur_depth == 0) (store); \
- } \
- /* Update alpha and beta */ \
- if (min) { \
- if (optimal < beta) beta = optimal; \
- } else { \
- if (optimal > alpha) alpha = optimal; \
+ { reset }; \
+ /* Prune */ \
+ if (val >= beta) return beta; \
+ /* Update the optimal value, which alpha carries */ \
+ if (val > alpha) { \
+ alpha = val; \
+ if (cur_depth == 0) { store }; \
} \
}
-// UP DOWN LEFT RIGHT
-static const int8_t deltas[4] = { +5, -5, -1, +1};
-
float
-ct1986_minimax(const uint8_t cur_depth, const uint8_t max_depth,
- const uint8_t min, float alpha, float beta) {
+ct1986_negamax(const uint8_t cur_depth, float alpha, float beta,
+ const float colour) {
const uint8_t black = (ply & 1),
material = (black) ? black_count : white_count,
flat = material & 127,
cap = (ply > 2 && (material & 128)),
standing = (ply > 2 && (material & 127));
- // 1.0 is a `certain' black win, -1.0 is a `certain' white win.
- float optimal = (min) ? infty : -infty;
-
// Step across the board
for (uint8_t row = 0; row < 5; row++) {
for (uint8_t col = 0; col < 5; col++) {
@@ -263,56 +265,55 @@ ct1986_minimax(const uint8_t cur_depth, const uint8_t max_depth,
steps++) {
gaps = 0b00000111 >> (4-steps);
do {
+ // Ensure legal move if we have to crush
+ const uint8_t last_drop_check = (num > 1) ? (gaps & 1<<(num - 2)) : 1;
+ if (end_stops[dir][1] || last_drop_check) {
// Translate to a drop sequence
- drops[0] = 1; mask = 1; idx = 0;
- for (uint8_t d = 0; d + 1 < num; d++) {
- if (gaps & mask) {
- idx++;
- drops[idx] = 1; // (*) we don't need to bounds check
- } else {
- drops[idx] += 1;
+ drops[0] = 1; mask = 1; idx = 0;
+ for (uint8_t d = 0; d + 1 < num; d++) {
+ if (gaps & mask) {
+ idx++;
+ drops[idx] = 1; // (*) we don't need to bounds check
+ } else {
+ drops[idx] += 1;
+ }
+ mask <<= 1;
}
- mask <<= 1;
- }
- // TODO: Work out what this should be before partition
-
- // Ensure legal move if we have to crush
- if (end_stops[dir][1] || drops[steps-1] <= 1) {
- // Try it, and manually check for win if it's valid
+ // Do it, and manually check for win if it's valid
uint8_t j = num;
for (uint8_t k = 0; k < steps; k++) {
- // Dear future me, i'm sorry
j -= drops[k];
- colours[loc+(k+1)*deltas[dir]] = (colours[loc+(k+1)*deltas[dir]] << drops[k])
- | ((colours[loc] >> j) & (0xFFFF >> (0x10 - drops[k])));
- celldat[loc+(k+1)*deltas[dir]] = (k == steps - 1) ? STONE_AT(loc) : STONE_FLAT
- | ((celldat[loc+(k+1)*deltas[dir]] + ((drops[k] << NUM_SHIFT))) & NUM_MASK);
+ push_stones(loc+(k+1)*deltas[dir],
+ drops[k],
+ (colours[loc] >> j) & (0xFFFF >> (0x10 - drops[k])),
+ (k == steps - 1) ? STONE_AT(loc) : STONE_FLAT);
}
// Then we drop them from the source
colours[loc] >>= num;
const uint8_t dec_count = celldat[loc] - (num << NUM_SHIFT);
celldat[loc] = dec_count & NUM_MASK;
+
// First check for wins, if we're at the bottom
// evaluate, otherwise recurse
WIN_EVALUATE_OR_RECURSE({
// If we did update the optimal value, store
// this move
generate_move(loc, dir, steps, drops, ct1986_ptn);
+ },{
+ // Reset the board data after recursing or
+ // before returning
+ if (dir <= M_DOWN) {
+ for (uint8_t y = 0; y < 5; y++) {
+ colours[THE_COORDS(col, y)] = colours_backup[y];
+ celldat[THE_COORDS(col, y)] = celldat_backup[y];
+ }
+ } else {
+ for (uint8_t x = 0; x < 5; x++) {
+ colours[THE_COORDS(x, row)] = colours_backup[x];
+ celldat[THE_COORDS(x, row)] = celldat_backup[x];
+ }
+ }
});
- // Reset the board data
- if (dir <= M_DOWN) {
- for (uint8_t y = 0; y < 5; y++) {
- colours[THE_COORDS(col, y)] = colours_backup[y];
- celldat[THE_COORDS(col, y)] = celldat_backup[y];
- }
- } else {
- for (uint8_t x = 0; x < 5; x++) {
- colours[THE_COORDS(x, row)] = colours_backup[x];
- celldat[THE_COORDS(x, row)] = celldat_backup[x];
- }
- }
- // Prune
- if (alpha >= beta) return optimal;
}
/*
* With thanks to
@@ -338,13 +339,12 @@ ct1986_minimax(const uint8_t cur_depth, const uint8_t max_depth,
WIN_EVALUATE_OR_RECURSE({
// If we did update the optimal value, store
generate_place(loc, STONE_FLAT, ct1986_ptn);
+ },{
+ // Reset the state
+ celldat[loc] = 0;
+ if (black) black_count++;
+ else white_count++;
});
- // Reset the state
- celldat[loc] = 0;
- if (black) black_count++;
- else white_count++;
- // Prune
- if (alpha >= beta) return optimal;
// Do the same for walls, can't happen without flats
if (standing) {
@@ -354,11 +354,11 @@ ct1986_minimax(const uint8_t cur_depth, const uint8_t max_depth,
celldat[loc] = NUM_INC | STONE_STANDING;
WIN_EVALUATE_OR_RECURSE({
generate_place(loc, STONE_STANDING, ct1986_ptn);
+ },{
+ celldat[loc] = 0;
+ if (black) black_count++;
+ else white_count++;
});
- celldat[loc] = 0;
- if (black) black_count++;
- else white_count++;
- if (alpha >= beta) return optimal;
}
}
@@ -370,20 +370,20 @@ ct1986_minimax(const uint8_t cur_depth, const uint8_t max_depth,
celldat[loc] = NUM_INC | STONE_CAPSTONE;
WIN_EVALUATE_OR_RECURSE({
generate_place(loc, STONE_CAPSTONE, ct1986_ptn);
+ },{
+ celldat[loc] = 0;
+ if (black) black_count |= 128;
+ else white_count |= 128;
});
- celldat[loc] = 0;
- if (black) black_count |= 128;
- else white_count |= 128;
- if (alpha >= beta) return optimal;
}
}
ct1986_display_progress(cur_depth);
}
}
- return optimal;
+ return alpha;
}
inline float
-ct1986_generate(const uint8_t max_depth) {
- return ct1986_minimax(0, 1+2*max_depth, (ply & 1) ? 0 : 1, -infty, infty);
+ct1986_generate(void) {
+ return ct1986_negamax(0, -infty, infty, (ply&1)?1.0:-1.0);
}
diff --git a/include/negamax_cnn1986.h b/include/negamax_cnn1986.h
new file mode 100644
index 0000000..411d877
--- /dev/null
+++ b/include/negamax_cnn1986.h
@@ -0,0 +1,18 @@
+#include <stdint.h>
+#include "tak.h"
+#include "weights.h"
+
+extern const float infty;
+extern char ct1986_ptn[9];
+extern uint8_t ct1986_search_depth;
+extern inline void ct1986_display_progress(const uint8_t);
+
+// Do negamax to depth ct1986_search_depth and return PTN of best move
+// in ct1986_ptn, along with its value as the return. The
+// ct1986_display_progress function is called on every new square at
+// the top level.
+float ct1986_generate(void);
+
+// Internal utility function
+float
+ct1986_evaluate_black_win(void);
diff --git a/include/tak.c b/include/tak.c
index a748555..e400578 100644
--- a/include/tak.c
+++ b/include/tak.c
@@ -109,7 +109,7 @@ try_place(const int8_t location, const enum COLOUR colour,
// Moving stacks
// ===================================================================
-static void
+static inline void
push_stones(const int8_t location, const uint8_t count,
const uint8_t new_colours,
const enum STONE_VARIANT top_stone) {
diff --git a/src/ctaklm.c b/src/ctaklm.c
index a7c299d..a1ca69d 100644
--- a/src/ctaklm.c
+++ b/src/ctaklm.c
@@ -3,7 +3,7 @@
#include <string.h>
#include <tak.h>
-#include <minimax_cnn1986.h>
+#include <negamax_cnn1986.h>
static const char *blk = "\033[41m", *wht = "\033[44m";
static const char *und = "\033[4m", *rst = "\033[0m";
@@ -151,9 +151,9 @@ print_info(void) {
blk, black_count & 127, black_count >> 7, rst);
}
+static int human;
static char *gamelog = 0;
static uint8_t auto_board = 0xFF, auto_info = 0xFF;
-static int human, search_depth;
static void
append_to_gamelog(const char *line, const uint8_t win_line) {
@@ -234,8 +234,8 @@ handle_turn(char *line) {
static void
new_game(uint8_t size) {
reset_state(size);
- printf("New %dx%d game! ct1986 at search_depth %d.\n",
- size, size, search_depth);
+ printf("New %dx%d game! ct1986 at search depth %d.\n",
+ size, size, ct1986_search_depth);
if (gamelog) gamelog = realloc(gamelog, sizeof(char));
else gamelog = malloc(sizeof(char));
gamelog[0] = 0;
@@ -308,7 +308,7 @@ ct1986_display_progress(const uint8_t depth) {
}
static int
-ct1986_turn(const uint8_t search_depth) {
+ct1986_turn(void) {
if (won == 0xFF) {
// Prepare progress bar
fputs("Computing [", stdout);
@@ -317,7 +317,7 @@ ct1986_turn(const uint8_t search_depth) {
fflush(stdout);
// Run the minimax
sum_depth = 0; num_check = 0;
- float minimax = ct1986_generate(search_depth);
+ float minimax = ct1986_generate();
fputs("\033[1C ", stdout);
// Failed to find a move?
if (minimax <= -infty) {
@@ -353,18 +353,19 @@ info, load, log, new, play (b|w), self-play, square <col><row>, <PTN>.");
blk, eval, rst);
} else {
printf("White heruistic chance: %s%.2f%s\n",
- wht, 100-eval, rst);
+ wht, -eval, rst);
}
} else if (!strcmp(line,"log")) {
puts(gamelog);
} else if (!strcmp(line,"new")) {
new_game(5);
} else if (!strcmp(line,"self-play")) {
- while (ct1986_turn(search_depth) == 0);
+ while (ct1986_turn() == 0);
} else if (!strncmp(line,"depth",5)) {
if (strnlen(line,7) == 7 && line[6] >= '0' && line[6] <= '9') {
- search_depth = line[6] - '0';
- printf("New search depth: %d.\n",search_depth);
+ ct1986_search_depth = line[6] - '0';
+ printf("New search depth: %d.\n",
+ ct1986_search_depth);
} else {
puts("Usage: depth [0-9].");
}
@@ -418,7 +419,7 @@ main(int argc, char **argv) {
(void)(argv);
human = 0;
- search_depth = 1;
+ ct1986_search_depth = 3;
new_game(5);
char *line = NULL;
@@ -446,7 +447,7 @@ main(int argc, char **argv) {
if (read == -1) {
playing = 0;
} else {
- ct1986_turn(search_depth);
+ ct1986_turn();
human = 0;
}
}