diff options
| author | tslil clingman <tslil@posteo.de> | 2021-10-05 18:22:23 -0400 |
|---|---|---|
| committer | tslil <tslil@posteo.de> | 2026-08-28 19:37:41 +0100 |
| commit | cf175fc346f1b208766b1f55d3673a7b208f322a (patch) | |
| tree | 3dabfd672b73e7d0756d8259e59464d1f560aee6 /src/geminict.c | |
| parent | 640d404b3cf3324aca4424fc3da4806d4562d0e4 (diff) | |
Welcome geminict!
This is a special interface to negamax_cnn1986 which is designed to
generate output for use in a CGI tak interface to be used over gemini.
Also in this commit is a reformating of the various source files to
use the traditional tab width of 8 spaces.
Diffstat (limited to 'src/geminict.c')
| -rw-r--r-- | src/geminict.c | 346 |
1 files changed, 346 insertions, 0 deletions
diff --git a/src/geminict.c b/src/geminict.c new file mode 100644 index 0000000..2ac2eab --- /dev/null +++ b/src/geminict.c @@ -0,0 +1,346 @@ +/* + 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 <https://www.gnu.org/licenses/>. +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <negamax.h> +#include <tak.h> +#include <tps.h> + +#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 + const uint32_t len = strnlen(line, 65535); + if (!line || len == 0 || len == 65535) 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; +} |
