#include #include #include #include #include static const char *blk = "\033[41m", *wht = "\033[44m"; static const char *und = "\033[4m", *rst = "\033[0m"; #define SQUARE_W 5 #define SQUARE_H 3 #define CHAR_FLT '#' #define CHAR_STN '/' #define CHAR_CAP '*' static void put_stone(const enum STONE_VARIANT stone, const enum COLOUR colour, const uint8_t top, const uint8_t beyond_carry_limit) { if (beyond_carry_limit) { fputs(und,stdout); } else { if (colour == C_BLACK) fputs(blk, stdout); else fputs(wht,stdout); } if (top) { switch (stone) { case STONE_FLAT: putchar(CHAR_FLT); break; case STONE_STANDING: putchar(CHAR_STN); break; case STONE_CAPSTONE: putchar(CHAR_CAP); break; } } else { putchar( (colour == C_BLACK) ? 'B' : 'W' ); } fputs(rst,stdout); } static void print_cell_line(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); uint8_t idx; for (uint8_t k = 0; k < SQUARE_W; k++) { idx = line + k*SQUARE_H; // Are we in the last column to be displayed? if ((k+1)*SQUARE_H > stack_size) { // If so, then if we can't fill it offset the starting line so // that it fills from the bottom up instead of the top down if (line < (k+1)*SQUARE_H - stack_size ) { // skip these lines idx = 0xFF; } else { // offset back idx -= (k+1)*SQUARE_H - stack_size; } } if (idx < stack_size) { put_stone(STONE_AT(location), (colours[location] & (1 << idx)) ? C_BLACK : C_WHITE, idx == 0, idx >= board_size); } else { putchar(' '); } } } // 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) { const uint8_t mod = line % (SQUARE_H + 1), row = board_size - line/(SQUARE_H + 1) - 1; // Print leader, either row number if half-way through square or // padding spaces otherwise if (mod == (SQUARE_H + 1)/2 ) printf("%d. ",row + 1); else fputs(" ",stdout); // Top and bottom of squares receive borders if (mod == 0) { for (uint8_t x = 0; x < board_size; x++) { putchar('+'); for (uint8_t k = 0; k < SQUARE_W; k++) putchar('-'); } 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++) { putchar('|'); print_cell_line(mod - 1 , x, row); } puts("|"); } else { // Bottom of board has column markers for (uint8_t x = 0; x < board_size; x++) { for (uint8_t k = 0; k <= SQUARE_W/2; k++) putchar(' '); printf("%c.",x+'a'); for (uint8_t k = 0; k < SQUARE_W-SQUARE_W/2-2; k++) putchar(' '); } putchar('\n'); } } } // Simple wrapper to print the whole board in one go static void print_board(void) { for (uint8_t k = 0; k 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); } puts(" <-- top"); } else { puts("(empty)"); } } else { printf("Requested square not on board (%dx%d).\n",board_size,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)" : ""); 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); } static int human; static char *gamelog = 0; static uint8_t auto_board = 0xFF, auto_info = 0xFF; static void append_to_gamelog(const char *line, const uint8_t win_line) { // I _could_ dynamically compute the size but ... don't let // `perfect' be the enemy of `good' ? char prepend[8]; 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 { strcpy(prepend, " "); } // Make room for this line gamelog = realloc(gamelog, strlen(gamelog) + strlen(prepend) + strlen(line) + 1); strcat(gamelog,prepend); strcat(gamelog,line); } static void end_game(char *line, char *win) { append_to_gamelog(line, 0); append_to_gamelog(win, 1); print_board(); puts("Game over:"); puts(gamelog); putchar('\n'); } static int handle_turn(char *line) { // Track win state uint8_t new_win = (won == 0xFF); switch (do_ptn(line)) { // Errors case PTN_INVALID: { puts("Invalid PTN."); return -1; break; } case ACT_ILLEGAL: { puts("Illegal action."); return -1; break; } case ACT_OVERFLOW: { puts("Move would cause internal overflow, select another."); return EXIT_FAILURE; break; } // Game has ended case GAME_END: { // Did it end this turn? if (new_win) { switch (won) { case WIN_DRAW: { end_game(line,"1/2-1/2"); break; } case WIN_FLAT_BLACK: { end_game(line,"0-F"); break; } case WIN_FLAT_WHITE: { end_game(line,"F-0"); break; } case WIN_ROAD_BLACK: { end_game(line,"0-R"); break; } case WIN_ROAD_WHITE: { end_game(line,"R-0"); break; } } } puts("Enter `new' to play again."); if (! new_win) return EXIT_FAILURE; break; } // Valid, append to game log case PTN_VALID: case ACT_OK: { append_to_gamelog(line, 0); if (auto_board) print_board(); if (auto_info) print_info(); break; } } return EXIT_SUCCESS; } static void new_game(uint8_t size) { reset_state(size); printf("New %dx%d game! negamax at search depth %d.\n", size, size, negamax_search_depth); if (gamelog) gamelog = realloc(gamelog, sizeof(char)); else gamelog = malloc(sizeof(char)); gamelog[0] = 0; } static int load_ptn(const char* fn) { FILE *fh = NULL; fh = fopen(fn, "r"); if (fh == NULL) return EXIT_FAILURE; int space1, space2; enum E_RESULT r; ssize_t read; size_t alloc_size; char *line = NULL; while ((read = getline(&line, &alloc_size, fh)) != -1) { if (read && line[0] <= '9' && line[0] >= '0') { // Trim trailing \n line[read-1] = 0; // Find first separator space1 = 0; while (space1 < read && line[space1++] != ' '); // If still on line if (space1 < read) { // Find next separator or end of line, either way mark the split space2 = space1; while (space2 < read && line[space2] != ' ') space2++; line[space2] = 0; // Try the first piece we found r = handle_turn(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); if (r) { printf("Error on: %s", line+space2+1); break; } } else { break; } } } } if (line) free(line); fclose(fh); return EXIT_SUCCESS; } static float sum_depth, num_check, progress; // Set up output function for negamax inline void negamax_display_progress(const uint8_t depth, const uint32_t length) { sum_depth += depth; num_check += 1; if (depth == negamax_search_depth) { progress++; printf("\x1B[1GComputing: %.0f/%d", progress,length); fflush(stdout); } } static int negamax_turn(void) { if (won == 0xFF) { fputs("Computing: 0%", stdout); fflush(stdout); // Run the minimax sum_depth = 0; num_check = 0; progress = 0; float minimax = negamax_generate(); printf(" [%d]\n", tt_num_cached); // Failed to find a move? if (minimax <= -infty) { puts("Opponent failed to find a move!"); return EXIT_FAILURE; } else { printf("Result: %s (%.2f, %.1e, %.2f)\n", negamax_ptn, minimax*100.0, num_check, sum_depth/num_check); return handle_turn(negamax_ptn); } } else { return EXIT_FAILURE; } } static int input_is_not_turn(const char *line) { if (!strcmp(line,"help")) { puts("Valid commands: auto (board|info), board, depth [0-9], eval, help,\ info, load, log, new, play (b|w), self-play, square , ."); } else if (!strcmp(line,"board")) { print_board(); } else if (!strcmp(line,"info")) { print_info(); } else if (!strcmp(line,"eval")) { float eval = cnn1986_evaluate_black_win()*100; if (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); } } else if (!strcmp(line,"log")) { puts(gamelog); } else if (!strcmp(line,"new")) { new_game(5); } else if (!strcmp(line,"self-play")) { while (negamax_turn() == 0); } else if (!strncmp(line,"depth",5)) { if (strnlen(line,7) == 7 && line[6] >= '0' && line[6] <= '9') { negamax_search_depth = line[6] - '0'; printf("New search depth: %d.\n", negamax_search_depth); } else { puts("Usage: depth [0-9]."); } } else if (!strncmp(line,"load",4)) { if (strnlen(line,6) >= 6) { if (load_ptn(line+5)) { printf("Errors in file %s\n",line); } } else { puts("Usage: load ."); } } else if (!strncmp(line,"auto",4)) { if (!strcmp(line,"auto board")) { auto_board = ~auto_board; printf("Automatic board display %s.\n", (auto_board) ? "Enabled" : "Disabled"); } else if (!strcmp(line,"auto info")) { auto_info = ~auto_info; printf("Automatic info display %s.\n", (auto_info) ? "Enabled" : "Disabled"); } else { puts("Usage: auto (board|info)."); } } 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'); } else { printf("Usage: square [a-%c][1-%c].\n",'`'+board_size,'0'+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); } else { puts("Usage: play (b|w)."); } } else { return EXIT_FAILURE; } return EXIT_SUCCESS; } int main(int argc, char **argv) { (void)(argc); (void)(argv); negamax_search_depth = 3; new_game(5); negamax_init(5); // Test harness if (argc > 1) { negamax_search_depth = 5; load_ptn("data/0.ptn"); negamax_turn(); return 0; } // Test harness char *line = NULL; ssize_t read = -1; size_t alloc_size; human = 0; for (int playing = 1; playing;) { while (human == 0) { fputs("ctaklm> ", stdout); fflush(stdout); 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) { human = 1; } } free(line); line = NULL; } else { playing = 0; human = 1; } } if (playing) { negamax_turn(); human = 0; } } free(line); free(gamelog); negamax_free(); return EXIT_SUCCESS; }