#include #include #include #include #include #include const char *blk = "\033[41m", *wht = "\033[44m"; const char *rev = "\033[7m", *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), 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, k+1 == stack_size, stack_size-k >= 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 remaining: %s%02d%s, %s%02d%s\n", wht,white_count & 127,rst, blk,black_count & 127,rst); printf("Caps remaining: %s%d%s, %s%d%s\n", wht,white_count >> 7,rst, blk,black_count >> 7,rst); printf("Valuation: %s%.6f%s\n", blk, ct1975_evaluate_black_win(), rst); } char *gamelog = 0; 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_DRAGON: { end_game(line,"R-R"); break; } 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("Game over, 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!\n",size,size); 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 void dot(void) { putchar('.'); } int main(int argc, char **argv) { (void)(argc); (void)(argv); new_game(5); char *line; while((line = linenoise("ctaklm> ")) != NULL) { if (!strncmp(line,"help",5)) { puts("Valid commands: auto (board|info), board, help, info,\ log, square, new [56], ."); } else if (!strncmp(line,"board",6)) { print_board(); } else if (!strncmp(line,"info",5)) { print_info(); } else if (!strncmp(line,"eval",5)) { printf("Valuation: %.6f\n", ct1975_evaluate_black_win()); } else if (!strncmp(line,"log",3)) { puts(gamelog); } else if (!strncmp(line,"load",4)) { if (strnlen(line,6) >= 6) { load_ptn(line+5); } else { puts("Usage: load ."); } } else if (!strncmp(line,"auto",4)) { if (!strncmp(line,"auto board",10)) { auto_board = ~auto_board; printf("Automatic board display %s.\n", (auto_board) ? "Enabled" : "Disabled"); } else if (!strncmp(line,"auto info",9)) { 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, 9) >= 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,"new",3)) { if (line[3] == 0) { new_game(5); } else if (strnlen(line,5) >= 5 && line[4] >= '5' && line[4] <= '6') { new_game(line[4]-'0'); } else { puts("Usage: new [56]."); } } else { 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); } return 0; }