/*
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 "B"
#define STR_FLT_WHT "W"
#define STR_CAP_BLK "B̂"
#define STR_CAP_WHT "Ŵ"
#define STR_STN_BLK "ƃ"
#define STR_STN_WHT "w̄"
#define STR_HFL_BLK "B"
#define STR_HFL_WHT "W"
#define STR_CHF_BLK "b"
#define STR_CHF_WHT "w"
static tak_state_p state;
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(5, col, row),
stack_size = COUNT_AT(state, 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(state, location),
(state->colours[location] & (1 << idx)) ? C_BLACK : C_WHITE,
idx == 0, idx >= 5);
} else {
putchar(' ');
}
}
}
// It takes 5*(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 = 5 - 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 < 5; 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 < 5 * (SQUARE_H + 1)) {
for (uint8_t x = 0; x < 5; x++) {
putchar('|');
print_cell_line(mod - 1, x, row);
}
puts("|");
} else {
// Bottom of board has column markers
for (uint8_t x = 0; x < 5; 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 < 5 * (SQUARE_H + 1) + 2; k++) {
print_board_line(k);
}
putchar('\n');
}
static void print_info(void) {
printf("Turn: %2d, %s%s\n", state->ply / 2 + 1,
(state->ply & 1) ? "Black" : "White",
(state->ply < 2) ? " (counter-play start)" : "");
printf("Flats/Caps remaining: %02d/%d, %02d/%d\n", state->white_count & 127,
state->white_count >> 7, state->black_count & 127,
state->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 (state->ply & 1) {
snprintf(prepend, 7, "%s%d. ", (state->ply == 1) ? "" : "\n",
state->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 = (state->won == 0xFF);
switch (do_ptn(state, 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 (state->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(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 enum TURN_RESULT negamax_turn(void) {
if (state->won == 0xFF) {
// Run the minimax
num_check = 0;
float minimax = negamax_generate(state);
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 T_ERR;
}
}
int main(int argc, char **argv) {
if (argc != 2) {
puts("Usage: PTN1.[PTN2.][PTN3.] etc");
return EXIT_FAILURE;
};
state = new_tak_state(5);
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;
}
}
tr = negamax_turn();
if (tr == T_ERR) {
puts("This shouldn't happen, ct1986 encountered an error.");
print_everything();
return EXIT_FAILURE;
} else if (tr == T_WIN)
return EXIT_SUCCESS;
print_everything();
free(gamelog);
negamax_free();
return EXIT_SUCCESS;
}