diff options
| -rw-r--r-- | include/ct1975.c | 135 | ||||
| -rw-r--r-- | include/ct1975.h | 11 | ||||
| -rw-r--r-- | src/ctaklm.c | 23 |
3 files changed, 158 insertions, 11 deletions
diff --git a/include/ct1975.c b/include/ct1975.c index 760fcd1..61f1fee 100644 --- a/include/ct1975.c +++ b/include/ct1975.c @@ -1,13 +1,17 @@ #include "ct1975.h" -float flattened[CONV_NUM+2]; -float dense1[DENSE1_NUM]; -float dense2[DENSE2_NUM]; +// =================================================================== +// Implementation of a small convolutional neural network +// =================================================================== + +static float flattened[CONV_NUM+2]; +static float dense1[DENSE1_NUM]; +static float dense2[DENSE2_NUM]; #define RELU(x) ((x) = ((x)<0)?0:(x)) float -evaluate_black_win(void) { +ct1975_evaluate_black_win(void) { /* ------------------ * * Convolution layer * * ------------------ */ @@ -87,3 +91,126 @@ evaluate_black_win(void) { return output; } + +// =================================================================== +// Select best move according the CNN above +// =================================================================== + +char ct1975_ptn[9]; +float ct1975_optimal; + +#define BETTER(t,o) ((current_colour == C_BLACK && (t) > (o)) || (current_colour == C_WHITE && (t) < (o))) + +void +ct1975_generate_ptn(void display_progress(void)) { + float this = 0; + enum E_RESULT r; + uint16_t colours_backup[board_size]; + uint8_t celldat_backup[board_size], drops[board_size]; + const uint8_t white_count_backup = white_count, + black_count_backup = black_count; + + // 1.0 is a `certain' black win, 0.0 is a `certain' white win. + ct1975_optimal = (current_colour == C_BLACK) ? 0.0 : 1.0; + + // Step across the board + for (uint8_t row = 0; row < board_size; row++) { + for (uint8_t col = 0; col < board_size; col++) { + // Try all valid actions for this square. Is it empty? + const uint8_t loc = THE_COORDS(col, row); + const uint8_t count = COUNT_AT(loc); + // Only try moves after CPS + if (count && ply>2 && ((colours[loc] & 1) == current_colour)) { + // There are stones, can we move them? + // For every direction. I'm not a huge fan of looping + // through enums, but it's better than manually unrolling + // this. Sufficiently smart compilers? + for (enum MOVE_DIRECTION dir = M_UP; dir <= M_RIGHT; dir++) { + // Back-up the row/column of the board + if (dir == M_UP || dir == M_DOWN) { + for (uint8_t k = 0; k < board_size; k++) { + colours_backup[k] = colours[THE_COORDS(k, row)]; + celldat_backup[k] = celldat[THE_COORDS(k, row)]; + } + } else { + for (uint8_t k = 0; k < board_size; k++) { + colours_backup[k] = colours[THE_COORDS(col, k)]; + celldat_backup[k] = celldat[THE_COORDS(col, k)]; + } + } + // We don't do anything terribly efficient or smart here, + // just try everything... + + // For every number of steps + for (uint8_t steps = 1; steps < board_size && steps <= count; steps++) { + uint8_t idx, carry; + for (idx = 0; idx < steps; idx++) drops[idx]=0; + idx = 0; + while (idx < steps) { + // Increment the drop sequence + carry = 0; + drops[idx]++; + do { + if (carry) { drops[++idx]++; carry = 0;} + if (drops[idx] > count || drops[idx] > board_size) { + drops[idx] = 1; carry = 1; + } + } while (carry && idx < steps); + // If carry is still set here we're done + if (carry == 0) { + // Try it, and note that try_move will never return + // GAME_END. It does not check for winners. We don't + // presently do that either, trust in the magic + // numbers :) + r = try_move(loc, dir, steps, drops); + if (r == ACT_OK) { + this = ct1975_evaluate_black_win(); + if (BETTER(this, ct1975_optimal)) { + // Update the chosen action + ct1975_optimal = this; + generate_move(board_size, loc, dir, steps, drops, ct1975_ptn); + } + // Reset the board data + if (dir == M_UP || dir == M_DOWN) { + for (uint8_t k = 0; k < board_size; k++) { + colours[THE_COORDS(k, row)] = colours_backup[k]; + celldat[THE_COORDS(k, row)] = celldat_backup[k]; + } + } else { + for (uint8_t k = 0; k < board_size; k++) { + colours[THE_COORDS(col, k)] = colours_backup[k]; + celldat[THE_COORDS(col, k)] = celldat_backup[k]; + } + } + } + } + } + } + } + } else if (count == 0) { + // Empty square, try the three placements. Again, looping + // through enums, sigh. + for (enum STONE_VARIANT stone = STONE_FLAT; + stone <= STONE_CAPSTONE; stone++) { + // try_place will never check for winning, and we don't do + // that either here + r = try_place(loc, current_colour, stone); + // Legal placement, evaluate it + if (r == ACT_OK) { + this = ct1975_evaluate_black_win(); + if (ply < 3 && BETTER(this, ct1975_optimal)) { + // Update the chosen action + ct1975_optimal = this; + generate_place(board_size, loc, stone, ct1975_ptn); + } + // Reset the state + celldat[loc] = 0; + white_count = white_count_backup; + black_count = black_count_backup; + } + } + } + display_progress(); + } + } +} diff --git a/include/ct1975.h b/include/ct1975.h index 79c20d2..a1e2be3 100644 --- a/include/ct1975.h +++ b/include/ct1975.h @@ -3,4 +3,13 @@ #include "weights.h" float -evaluate_black_win(void); +ct1975_evaluate_black_win(void); + +extern char ct1975_ptn[9]; +extern float ct1975_optimal; + +// Generate PTN of the ````best'''' action and store it in ct1975_ptn, +// along with its evaluation in ct1975_optimal. The display_progress +// argument is a call-back, called on every new square. +void +ct1975_generate_ptn(void display_progress(void)); diff --git a/src/ctaklm.c b/src/ctaklm.c index 5df23c9..87fc30f 100644 --- a/src/ctaklm.c +++ b/src/ctaklm.c @@ -153,7 +153,7 @@ print_info(void) { wht,white_count >> 7,rst, blk,black_count >> 7,rst); printf("Valuation: %s%.6f%s\n", - blk, evaluate_black_win(), rst); + blk, ct1975_evaluate_black_win(), rst); } char *gamelog = 0; @@ -203,7 +203,7 @@ handle_turn(char *line) { case ACT_ILLEGAL: { puts("Illegal action."); return -1; break; } case ACT_OVERFLOW: { puts("Move would cause internal overflow, select another."); - return -1; + return EXIT_FAILURE; break; } // Game has ended @@ -220,7 +220,7 @@ handle_turn(char *line) { } } puts("Game over, enter `new' to play again."); - if (! new_win) return -1; + if (! new_win) return EXIT_FAILURE; break; } // Valid, append to game log @@ -233,7 +233,7 @@ handle_turn(char *line) { } } - return 0; + return EXIT_SUCCESS; } static void @@ -298,6 +298,9 @@ load_ptn(const char* fn) { return EXIT_SUCCESS; } +static void +dot(void) { putchar('.'); } + int main(int argc, char **argv) { (void)(argc); @@ -314,7 +317,7 @@ main(int argc, char **argv) { } else if (!strncmp(line,"info",5)) { print_info(); } else if (!strncmp(line,"eval",5)) { - printf("Valuation: %.6f\n", evaluate_black_win()); + printf("Valuation: %.6f\n", ct1975_evaluate_black_win()); } else if (!strncmp(line,"log",3)) { puts(gamelog); } else if (!strncmp(line,"load",4)) { @@ -352,7 +355,15 @@ main(int argc, char **argv) { puts("Usage: new [56]."); } } else { - handle_turn(line); + int r = handle_turn(line); + if (r == EXIT_SUCCESS) { + fputs("Thinking [", stdout); + ct1975_generate_ptn(&dot); + printf("] Result: %s (%.3f)\n", + ct1975_ptn, + ct1975_optimal*100.0); + handle_turn(ct1975_ptn); + } } free(line); |
