/* geminict, a Gemini CGI interface to the ct library Copyright (C) 2021, tslil clingman This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #define SQUARE_W 5 #define SQUARE_H 3 #define STR_FLT_BLK "⛀" #define STR_FLT_WHT "⛂" #define STR_STN_BLK "⫾" #define STR_STN_WHT "❚" #define STR_CAP_BLK "♕" #define STR_CAP_WHT "♛" #define STR_HFL_BLK "▭" #define STR_HFL_WHT "▬" #define STR_CHF_BLK "▿" #define STR_CHF_WHT "▾" 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((colour == C_BLACK) ? STR_CHF_BLK : STR_CHF_WHT, stdout); } else { if (top) { switch (stone) { case STONE_FLAT: { fputs((colour == C_BLACK) ? STR_FLT_BLK : STR_FLT_WHT, stdout); break; } case STONE_STANDING: { fputs((colour == C_BLACK) ? STR_STN_BLK : STR_STN_WHT, stdout); break; } case STONE_CAPSTONE: { fputs((colour == C_BLACK) ? STR_CAP_BLK : STR_CAP_WHT, stdout); break; } } } else { fputs((colour == C_BLACK) ? STR_HFL_BLK : STR_HFL_WHT, 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 < board_size * (SQUARE_H + 1) + 2; k++) { print_board_line(k); } putchar('\n'); } static void print_info(void) { printf("Turn: %2d, %s%s\n", ply / 2 + 1, (ply & 1) ? "Black" : "White", (ply < 2) ? " (counter-play start)" : ""); printf("Flats/Caps remaining: %02d/%d, %02d/%d\n", white_count & 127, white_count >> 7, black_count & 127, black_count >> 7); } static char *gamelog = NULL; static void print_everything(void) { print_board(); print_info(); puts(gamelog); } static int 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' ? if (gamelog == NULL) return EXIT_FAILURE; 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); // TODO: trap errno strcat(gamelog, prepend); strcat(gamelog, line); return EXIT_SUCCESS; } 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'); } enum TURN_RESULT { T_ERR, T_OK, T_WIN }; static enum TURN_RESULT handle_turn(char *line) { // Track win state uint8_t new_win = (won == 0xFF); switch (do_ptn(line)) { // Errors case ACT_INVALID_PTN: { puts("Invalid PTN."); return T_ERR; } case ACT_ILLEGAL: { puts("Illegal action."); return T_ERR; } case ACT_OVERFLOW: { puts("Move would cause internal overflow, select another."); return T_ERR; } // 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; } } return T_WIN; } if (!new_win) return T_ERR; break; } // Valid, append to game log case ACT_OK: { append_to_gamelog(line, 0); break; } } return T_OK; } static void new_game(uint8_t size) { reset_state(size); if (gamelog) gamelog = realloc(gamelog, sizeof(char)); else gamelog = malloc(sizeof(char)); gamelog[0] = 0; } static float num_check; // Set up output function for negamax inline void negamax_display_progress(const uint8_t cur_depth, const uint8_t init_depth, const uint32_t length) { (void)(cur_depth); (void)(init_depth); (void)(length); num_check += 1; } static int negamax_turn(void) { if (won == 0xFF) { // Run the minimax num_check = 0; float minimax = negamax_generate(); putchar('\n'); // Failed to find a non-losing move? if (minimax <= -infty) puts("Opponent concedes!"); printf("ct1986 says: %s (minmax %.2f, checked %.1e)\n\n", negamax_ptn, minimax * 100.0, num_check); return handle_turn(negamax_ptn); } else { return EXIT_FAILURE; } } int main(int argc, char **argv) { if (argc != 2) { puts("Usage: PTN1.[PTN2.][PTN3.] etc"); return EXIT_FAILURE; }; negamax_search_depth = 5; new_game(5); negamax_init(5); enum TURN_RESULT tr; char* line = argv[1]; // Some maximum length we're willing to parse uint32_t len = strnlen(line, 65535); if (!line || len < 2 || len == 65535) { puts("Malformed input."); return EXIT_FAILURE; } // strip quotes if (line[0]=='\'') { line++; len--; } else { puts("Malformed input."); return EXIT_FAILURE; } if (line[len-1]=='\'') { line[len-1]=0; len--; } else { puts("Malformed input."); return EXIT_FAILURE; } uint32_t start = 0, end = 0; while (start < len) { // Find first separator while (end < len && line[end] != '.') end++; // If still on line if (end < len) { // Mark the split line[end] = 0; // Try the first piece we found tr = handle_turn(line + start); if (tr == T_ERR) { printf("Error on: %s\n", line + start); print_everything(); return EXIT_FAILURE; } else if (tr == T_WIN) return EXIT_SUCCESS; start = ++end; } else { puts("Malformed input."); return EXIT_FAILURE; } } negamax_turn(); print_everything(); free(gamelog); negamax_free(); return EXIT_SUCCESS; }