aboutsummaryrefslogtreecommitdiff
path: root/src/geminict.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/geminict.c')
-rw-r--r--src/geminict.c346
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;
+}