aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortslil clingman <tslil@posteo.de>2021-02-02 19:23:54 -0500
committertslil <tslil@posteo.de>2026-08-28 19:37:41 +0100
commit40c6b1dafab4de169bac8a799e7953e85061218e (patch)
tree99d86e8f90e8aa19b35875ea8ba624c50c467857
parent11956b2e940f5e1839efab187d898092e819a766 (diff)
TEI interface working!
-rw-r--r--.gitignore1
-rw-r--r--Makefile10
-rw-r--r--include/tak.c61
-rw-r--r--include/tak.h26
-rw-r--r--include/tps.c217
-rw-r--r--include/tps.h11
-rwxr-xr-xresources/extract.sh2
-rw-r--r--src/ct1986.c14
-rw-r--r--src/ctaklm.c32
-rw-r--r--src/pptdb.c6
-rw-r--r--src/tei.c163
11 files changed, 475 insertions, 68 deletions
diff --git a/.gitignore b/.gitignore
index fae0106..e76b6db 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,4 +3,5 @@ data/
ct1986
ctaklm
pptdb
+tei
buildroot*
diff --git a/Makefile b/Makefile
index 41dedee..89c22fa 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,3 @@
-STRIP=strip
-
IDIR=include
DEFINES=-DDETERMINISTIC
CFLAGS=-O3 -Wall -Wextra -Wpedantic -std=c99 -D_DEFAULT_SOURCE $(DEFINES) -I$(IDIR)
@@ -20,16 +18,18 @@ CROSS_STRIP=$(BUILDROOT_DIR)/output/host/bin/arm-linux-strip
ctaklm: src/ctaklm.o $(OBJS)
$(CC) $(CFLAGS) src/ctaklm.o $(OBJS) -o ctaklm
- $(STRIP) ctaklm
pptdb: src/pptdb.o $(CTAK_OBJS)
$(CC) $(CFLAGS) src/pptdb.o $(CTAK_OBJS) -o pptdb
+tei: src/tei.o $(OBJS)
+ $(CC) $(CFLAGS) src/tei.o $(OBJS) -o tei
+
clean:
- rm -f ctaklm ct1986 pptdb
+ rm -f ctaklm ct1986 pptdb tei
rm -f src/*.o include/*.o
-native: clean ctaklm pptdb
+native: clean ctaklm pptdb tei
$(CROSS_CC):
./resources/do_buildroot.sh $(BUILDROOT_DIR)
diff --git a/include/tak.c b/include/tak.c
index c9c761c..783bea8 100644
--- a/include/tak.c
+++ b/include/tak.c
@@ -77,7 +77,7 @@ next_ply(void) {
// Placing stones
// ===================================================================
-enum E_RESULT
+enum ACT_RESULT
try_place(const int8_t location, const enum COLOUR colour,
const enum STONE_VARIANT stone)
{
@@ -135,7 +135,7 @@ push_stones(const int8_t location, const uint8_t count,
| ((celldat[location] + ((count << NUM_SHIFT))) & NUM_MASK);
}
-enum E_RESULT
+enum ACT_RESULT
try_move(const int8_t location, const enum MOVE_DIRECTION direction,
const uint8_t steps, const uint8_t drops[5]) {
// Game is over?
@@ -380,14 +380,17 @@ check_win(void) {
// PTN place parser
// ===================================================================
-#define ASSERT_NONEMPTY { if (ptn == 0 || *ptn == 0) return PTN_INVALID; }
-#define ASSERT_MORE { if (*ptn == 0) return PTN_INVALID; }
+#define NULL 0
-enum E_RESULT
-parse_place(const uint8_t board_size, char *ptn,
- uint8_t *out_location, enum STONE_VARIANT *out_stone) {
+#define ASSERT_NONEMPTY { \
+ if (ptn == NULL || *ptn == 0) return PTN_INVALID; \
+ }
+
+#define ASSERT_MORE { if (*ptn == 0) return PTN_INVALID; }
- if (board_size < 5 || board_size > 6) return PTN_INVALID;
+enum PTN_RESULT
+parse_place(char *ptn, uint8_t *out_location,
+ enum STONE_VARIANT *out_stone) {
ASSERT_NONEMPTY;
@@ -410,20 +413,18 @@ parse_place(const uint8_t board_size, char *ptn,
if (*(++ptn) > 0) return PTN_INVALID;
- return PTN_VALID;
+ return PTN_OK;
}
// ===================================================================
// PTN move parser
// ===================================================================
-enum E_RESULT
-parse_move(const uint8_t board_size, char *ptn,
- uint8_t *out_location, enum MOVE_DIRECTION *out_direction,
+enum PTN_RESULT
+parse_move(char *ptn, uint8_t *out_location,
+ enum MOVE_DIRECTION *out_direction,
uint8_t *out_steps, uint8_t out_drops[5]) {
- if (board_size < 5 || board_size > 6) return PTN_INVALID;
-
ASSERT_NONEMPTY;
uint8_t picked_up = 1;
@@ -462,7 +463,7 @@ parse_move(const uint8_t board_size, char *ptn,
if (*ptn == 0) {
*out_steps = 1;
out_drops[0] = picked_up;
- return PTN_VALID;
+ return PTN_OK;
}
// Parse the drops in each subsequent square
@@ -486,7 +487,7 @@ parse_move(const uint8_t board_size, char *ptn,
// Mismatch between number of stones picked up and total dropped
if ( total != picked_up ) return PTN_INVALID;
- return PTN_VALID;
+ return PTN_OK;
}
// ===================================================================
@@ -531,7 +532,6 @@ generate_move(const uint8_t in_location,
case M_RIGHT: { *out_ptn = '>'; break; }
}; out_ptn++;
-
for (uint8_t k = 0; (total > 1) && (k < in_steps); k++) {
*out_ptn = '0' + in_drops[k]; out_ptn++;
}
@@ -557,12 +557,13 @@ is_not_placement(char *ptn) {
}
}
-enum E_RESULT
+enum ACT_RESULT
do_ptn(char *ptn) {
// Game over?
if (won < 0xFF) return GAME_END;
- enum E_RESULT res;
+ enum PTN_RESULT ptn_res;
+ enum ACT_RESULT act_res;
uint8_t location;
// Placing or moving?
@@ -570,24 +571,27 @@ do_ptn(char *ptn) {
uint8_t steps, drops[5];
enum MOVE_DIRECTION direction;
// Parse it as a move
- res = parse_move(board_size, ptn, &location,
- &direction, &steps, drops);
+ ptn_res = parse_move(ptn, &location, &direction, &steps, drops);
// If valid PTN, try to do it
- if (res == PTN_VALID) {
+ if (ptn_res == PTN_OK) {
if (ply < 2) return ACT_ILLEGAL;
- res = try_move(location, direction, steps, drops);
+ act_res = try_move(location, direction, steps, drops);
+ } else {
+ return ACT_INVALID_PTN;
}
} else {
// It was not a move
enum STONE_VARIANT stone;
// Was it a valid placement?
- res = parse_place(board_size, ptn, &location, &stone);
+ ptn_res = parse_place(ptn, &location, &stone);
// If so, try it
- if (res == PTN_VALID)
- res = try_place(location, current_colour, stone);
+ if (ptn_res == PTN_OK)
+ act_res = try_place(location, current_colour, stone);
+ else
+ return ACT_INVALID_PTN;
}
// A valid ply occured
- if (res == ACT_OK) {
+ if (act_res == ACT_OK) {
// Don't bother checking that the game was won early on, could be
// more conservative here :)
if (ply >= board_size) {
@@ -601,6 +605,5 @@ do_ptn(char *ptn) {
// Only step if the game isn't over yet
next_ply();
}
-
- return res;
+ return act_res;
}
diff --git a/include/tak.h b/include/tak.h
index 8b40430..63dffe1 100644
--- a/include/tak.h
+++ b/include/tak.h
@@ -24,8 +24,10 @@
// Types
// ===================================================================
-enum E_RESULT {ACT_OK, ACT_ILLEGAL, ACT_OVERFLOW,
- PTN_VALID, PTN_INVALID, GAME_END };
+enum ACT_RESULT { ACT_OK, ACT_ILLEGAL, ACT_OVERFLOW, ACT_INVALID_PTN, GAME_END };
+
+enum PTN_RESULT { PTN_OK, PTN_INVALID };
+enum TPS_RESULT { TPS_OK, TPS_INVALID };
enum COLOUR { C_WHITE, C_BLACK };
enum STONE_VARIANT { STONE_FLAT, STONE_STANDING, STONE_CAPSTONE };
@@ -49,6 +51,8 @@ typedef uint16_t colour_stack_t;
// Variables
// ===================================================================
+// NOTE: We only support one capstone per player and 5s or 6s games.
+
extern enum WIN_TYPE won;
extern uint8_t board_size;
extern data_t celldat[36];
@@ -66,11 +70,11 @@ extern uint8_t white_count, black_count, ply;
void reset_state(const uint8_t new_board_size);
void next_ply(void);
-enum E_RESULT
+enum ACT_RESULT
try_place(const int8_t location, const enum COLOUR colour,
const enum STONE_VARIANT stone);
-enum E_RESULT
+enum ACT_RESULT
try_move(const int8_t location, const enum MOVE_DIRECTION direction,
const uint8_t steps, const uint8_t drops[5]);
@@ -80,13 +84,13 @@ check_win(void);
// -------------------------------------------------------------------
// PTN related
-enum E_RESULT
-parse_place(const uint8_t board_size, char *ptn,
- uint8_t *out_location, enum STONE_VARIANT *out_stone);
+enum PTN_RESULT
+parse_place(char *in_ptn, uint8_t *out_location,
+ enum STONE_VARIANT *out_stone);
-enum E_RESULT
-parse_move(const uint8_t board_size, char *ptn,
- uint8_t *out_location, enum MOVE_DIRECTION *out_direction,
+enum PTN_RESULT
+parse_move(char *in_ptn, uint8_t *out_location,
+ enum MOVE_DIRECTION *out_direction,
uint8_t *out_steps, uint8_t out_drops[5]);
void
@@ -102,6 +106,6 @@ generate_move(const uint8_t in_location,
// -------------------------------------------------------------------
// Game driver
-enum E_RESULT
+enum ACT_RESULT
do_ptn(char *ptn);
#endif
diff --git a/include/tps.c b/include/tps.c
new file mode 100644
index 0000000..be25c4f
--- /dev/null
+++ b/include/tps.c
@@ -0,0 +1,217 @@
+#include "tps.h"
+
+// ===================================================================
+// Load TPS string
+// ===================================================================
+
+#define TPS_ASSERT_MORE { if (*tps == 0) return TPS_INVALID; }
+
+enum TPS_RESULT
+load_tps(char* tps) {
+ // TODO: Ensure NULL termination?
+ if (tps == NULL) return TPS_INVALID;
+
+ uint8_t prefix = 0;
+ // Check if we're likely of the form [TPS "blah"]
+ if (!strncmp(tps, "[TPS \"", 6)) {
+ prefix=1;
+ // Now we can worry about just the TPS part
+ tps += 6;
+ }
+
+ // Reset everything
+ reset_state(board_size);
+
+ // Parse squares, NOTE: We assume that board_size matches TPS size.
+ int col = 0, row = board_size-1, skip, parsing = 1;
+ while (parsing) {
+ switch (*tps) {
+ case ' ': {
+ // we're done
+ parsing = 0;
+ tps++; TPS_ASSERT_MORE;
+ break;
+ }
+ case 'x': {
+ // empty squares
+ tps++; TPS_ASSERT_MORE;
+ skip = 0;
+ if (*tps >= '2' && *tps <= '0'+board_size) {
+ skip = *tps - '1';
+ tps++; TPS_ASSERT_MORE;
+ } else if (*tps != ',' && *tps != '/' && *tps != ' ') {
+ return TPS_INVALID;
+ }
+ col += skip;
+ if (col >= board_size + 1) return TPS_INVALID;
+ break;
+ }
+ case '/': {
+ // next row
+ if (col + 1 != board_size) return TPS_INVALID;
+ row--; col = 0;
+ if (row < 0) return TPS_INVALID;
+ tps++; TPS_ASSERT_MORE;
+ break;
+ }
+ case ',': {
+ // next column
+ col++;
+ if (col >= board_size) return TPS_INVALID;
+ tps++; TPS_ASSERT_MORE;
+ break;
+ }
+ default: {
+ const int l = THE_COORDS(col, row);
+ uint8_t num_read = 0, reading = 1;
+ // Read in a stack of colours, optionally terminated by an S
+ // or C to change the top stone type
+ while (reading) {
+ switch (*tps) {
+ // Reading a stone colour
+ case '2': {
+ // check next letter to make sure we have the material
+ tps++; TPS_ASSERT_MORE;
+ if (*tps == 'C') {
+ if (black_count & 128) black_count &= 127;
+ else return TPS_INVALID;
+ } else if (black_count & 127) {
+ black_count--;
+ } else return TPS_INVALID;
+ colours[l] <<= 1;
+ celldat[l] += NUM_INC;
+ colours[l] |= 1;
+ num_read++;
+ break;
+ }
+ case '1': {
+ tps++; TPS_ASSERT_MORE;
+ if (*tps == 'C') {
+ if (white_count & 128) white_count &= 127;
+ else return TPS_INVALID;
+ } else if (white_count & 127) {
+ white_count--;
+ } else return TPS_INVALID;
+ colours[l] <<= 1;
+ celldat[l] += NUM_INC;
+ num_read++;
+ break;
+ }
+ case 'S': {
+ // Have we already read a stone type?
+ if (STONE_AT(l) != STONE_FLAT) return TPS_INVALID;
+ celldat[l] |= STONE_STANDING;
+ tps++; TPS_ASSERT_MORE;
+ break;
+ }
+ case 'C': {
+ if (STONE_AT(l) != STONE_FLAT) return TPS_INVALID;
+ celldat[l] |= STONE_CAPSTONE;
+ tps++; TPS_ASSERT_MORE;
+ break;
+ }
+ case ',': // fall-through
+ case '/': {
+ // done here
+ reading=0;
+ break;
+ }
+ default: return TPS_INVALID;
+ }
+ if (num_read > 0xF) return TPS_INVALID;
+ }
+ }
+ }
+ }
+
+ // Now it's time to parse the ply number. First, the active player
+ if (*tps != '1' && *tps != '2') return TPS_INVALID;
+ ply += *tps - '1';
+ tps++; TPS_ASSERT_MORE;
+
+ // Space
+ if (*tps != ' ') return TPS_INVALID;
+ tps++; TPS_ASSERT_MORE;
+
+ // Turn number, atoi doesn't detect errors so let's do it ourselves
+ uint8_t p = 0;
+ do {
+ p *= 10;
+ if (*tps >= '0' && *tps <= '9') {
+ p += *tps - '0';
+ } else return TPS_INVALID;
+ tps++;
+ } while ( (prefix && *tps && *tps != '"') || (!prefix && *tps) );
+ if (p == 0) return TPS_INVALID;
+ ply += 2*(p - 1);
+
+ current_colour = (ply & 1) ? C_BLACK : C_WHITE;
+ if (ply < 2) current_colour = C_BLACK - current_colour;
+
+ if (prefix) {
+ tps++; TPS_ASSERT_MORE;
+ if (*tps != ']' ) return TPS_INVALID;
+
+ tps++;
+ if (*tps != 0) return TPS_INVALID;
+ }
+
+ return TPS_OK;
+}
+
+// ===================================================================
+// Generate TPS string
+// ===================================================================
+
+void
+generate_tps(char *out_tps) {
+ strcpy(out_tps, "[TPS \"");
+ out_tps += 6;
+
+ for (int8_t row = board_size - 1; row >= 0; row--) {
+ for (int8_t col = 0; col < board_size; col++) {
+ const int8_t l = THE_COORDS(col, row);
+ const uint8_t count = COUNT_AT(l);
+ if (count) {
+ colour_stack_t c = colours[l], s = 1<<(count - 1);
+ for (int k=0; k<count; k++, s >>=1, out_tps++) {
+ if (c & s) *out_tps = '2';
+ else *out_tps = '1';
+ }
+ switch (STONE_AT(l)) {
+ case STONE_CAPSTONE: {
+ *out_tps = 'C'; out_tps++; break;
+ }
+ case STONE_STANDING: {
+ *out_tps = 'S'; out_tps++; break;
+ }
+ default: break;
+ }
+ } else {
+ int8_t skip = 1;
+ while (col < board_size && COUNT_AT(l+skip) == 0) {
+ skip++;
+ col++;
+ }
+ *out_tps = 'x'; out_tps++;
+ if (skip > 1) {
+ *out_tps = '0'+skip; out_tps++;
+ }
+ }
+ if (col + 1 < board_size) {
+ *out_tps = ','; out_tps++;
+ }
+ }
+ if (row > 0) {
+ *out_tps = '/'; out_tps++;
+ }
+ }
+
+ *out_tps = ' '; out_tps++;
+ *out_tps = '1' + (ply & 1); out_tps++;
+ *out_tps = ' '; out_tps++;
+
+ out_tps += sprintf(out_tps, "%d", ply/2 + 1);
+
+ strcpy(out_tps, "\"]");
+}
diff --git a/include/tps.h b/include/tps.h
new file mode 100644
index 0000000..70cf1de
--- /dev/null
+++ b/include/tps.h
@@ -0,0 +1,11 @@
+#include <tak.h>
+
+#include <string.h> // for strnlen, strncmp, and strcpy
+#include <stdio.h> // for sprintf (in a single place! grrr)
+
+// NOTE: We assume that board_size matches TPS size.
+enum TPS_RESULT
+load_tps(char* in_tps);
+
+void
+generate_tps(char *out_tps);
diff --git a/resources/extract.sh b/resources/extract.sh
index e9cd62a..fb2c30d 100755
--- a/resources/extract.sh
+++ b/resources/extract.sh
@@ -2,7 +2,7 @@
db_file=games_anon.db
-chosen_players="AaaarghBot Tiltak_Bot Taktician"
+chosen_players="AaaarghBot Tiltak_Bot TakticianBot"
query() {
query="(size == $1) and (result != '1-0') and (result != '0-1') and (result != '0-0') and (result != '1/2-1/2')"
diff --git a/src/ct1986.c b/src/ct1986.c
index 2c2d875..aeff13a 100644
--- a/src/ct1986.c
+++ b/src/ct1986.c
@@ -84,9 +84,17 @@ handle_turn(char *line) {
uint8_t new_win = (won == 0xFF);
switch (do_ptn(line)) {
// Errors
- case PTN_INVALID: { lcd_put_line(L_SCROLL, "Invalid PTN."); break; }
- case ACT_ILLEGAL: { lcd_put_line(L_SCROLL, "Illegal ply."); break; }
- case ACT_OVERFLOW: { lcd_put_line(L_SCROLL, "Overflow."); break; }
+ case ACT_INVALID_PTN: {
+ lcd_put_line(L_SCROLL, "Invalid PTN.");
+ break;
+ }
+ case ACT_ILLEGAL: {
+ lcd_put_line(L_SCROLL, "Illegal ply.");
+ break; }
+ case ACT_OVERFLOW: {
+ lcd_put_line(L_SCROLL, "Overflow.");
+ break;
+ }
// Game has ended
case GAME_END: {
// Did it end this turn?
diff --git a/src/ctaklm.c b/src/ctaklm.c
index d790769..0948fa7 100644
--- a/src/ctaklm.c
+++ b/src/ctaklm.c
@@ -22,6 +22,7 @@
#include <string.h>
#include <tak.h>
+#include <tps.h>
#include <negamax.h>
static const char *blk = "\033[41m", *wht = "\033[44m";
@@ -220,7 +221,7 @@ handle_turn(char *line) {
uint8_t new_win = (won == 0xFF);
switch (do_ptn(line)) {
// Errors
- case PTN_INVALID: { puts("Invalid PTN."); return -1; break; }
+ case ACT_INVALID_PTN: { 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.");
@@ -244,7 +245,6 @@ handle_turn(char *line) {
break;
}
// Valid, append to game log
- case PTN_VALID:
case ACT_OK: {
append_to_gamelog(line, 0);
if (auto_board) print_board();
@@ -274,7 +274,7 @@ load_ptn(const char* fn) {
if (fh == NULL) return EXIT_FAILURE;
int space1, space2;
- enum E_RESULT r;
+ enum ACT_RESULT r;
ssize_t read;
size_t alloc_size;
@@ -315,7 +315,6 @@ load_ptn(const char* fn) {
if (line) free(line);
fclose(fh);
-
return EXIT_SUCCESS;
}
@@ -348,16 +347,12 @@ negamax_turn(void) {
float minimax = negamax_generate();
putchar('\n');
// Failed to find a move?
- if (minimax <= -infty) {
- puts("Opponent failed to find a move!");
- return EXIT_FAILURE;
- } else {
- printf("Result: %s (%.2f, checked %.1e)\n",
- negamax_ptn,
- minimax*100.0,
- num_check);
- return handle_turn(negamax_ptn);
- }
+ if (minimax <= -infty) puts("Opponent concedes!");
+ printf("Result: %s (%.2f, checked %.1e)\n",
+ negamax_ptn,
+ minimax*100.0,
+ num_check);
+ return handle_turn(negamax_ptn);
} else {
return EXIT_FAILURE;
}
@@ -366,8 +361,9 @@ negamax_turn(void) {
static int
input_is_not_turn(const char *line) {
if (!strcmp(line,"help")) {
- puts("Valid commands: auto (board|info), board, depth [0-9], eval, help,\
-info, load, log, new, play (b|w), self-play, square <col><row>, <PTN>.");
+ puts("Valid commands: auto (board|info), board, depth [0-9], eval,\
+ help, info, load <file.ptn>, log, new, play (b|w), self-play, square\
+ <col><row>, tps, <PTN>.");
} else if (!strcmp(line,"board")) {
print_board();
} else if (!strcmp(line,"info")) {
@@ -385,6 +381,10 @@ info, load, log, new, play (b|w), self-play, square <col><row>, <PTN>.");
puts(gamelog);
} else if (!strcmp(line,"new")) {
new_game(5);
+ } else if (!strcmp(line,"tps")) {
+ char buf[1000];
+ generate_tps(buf);
+ puts(buf);
} else if (!strcmp(line,"self-play")) {
while (negamax_turn() == 0);
} else if (!strncmp(line,"depth",5)) {
diff --git a/src/pptdb.c b/src/pptdb.c
index e3fd687..bb94987 100644
--- a/src/pptdb.c
+++ b/src/pptdb.c
@@ -75,10 +75,10 @@ write_input(const int dx, const int dy, const uint8_t swap) {
}
// Warning: performs _no_ checks on input whatsoever
-static enum E_RESULT
+static enum ACT_RESULT
parse_line(const char *pt, const ssize_t read) {
ssize_t idx;
- enum E_RESULT r;
+ enum ACT_RESULT r;
int total_plies = 0;
for (idx=0;idx<read;idx++) {
@@ -159,7 +159,7 @@ This program comes with ABSOLUTELY NO WARRANTY; and is made available under the
int main(int argc, char **argv) {
(void)(argc);
- enum E_RESULT r;
+ enum ACT_RESULT r;
enum WIN_TYPE win;
uint32_t games = 0, overflow=0, illegal = 0;
uint32_t road_wins=0, flat_wins=0, road_turns=0, flat_turns=0,
diff --git a/src/tei.c b/src/tei.c
new file mode 100644
index 0000000..93c67ce
--- /dev/null
+++ b/src/tei.c
@@ -0,0 +1,163 @@
+/*
+ ctaktei, a TEI interface to the ctak library & its computer opponent
+
+ 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 <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <tak.h>
+#include <tps.h>
+#include <negamax.h>
+
+// 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);
+}
+
+enum TEI_RETURN { TEI_QUIT, TEI_OK, TEI_FAILURE };
+
+// expects ``(startpos|tps <tps>) moves <ptn>''
+static enum TEI_RETURN
+parse_position_string(char *line) {
+ // Find the word ``moves''
+ char *beg = strstr(line, "moves");
+
+ if (!strncasecmp(line, "tps", 3)) {
+ // Split the string on the space before moves
+ if (beg) *(beg-1) = 0;
+ // so that we can load it as a TPS description
+ load_tps(line + 4);
+ } else if (!strncasecmp(line, "startpos", 8)) {
+ reset_state(board_size);
+ } else {
+ return TEI_FAILURE;
+ }
+
+ if (beg == NULL) return TEI_OK;
+
+ // parse the PTN sequence
+ strtok(beg, " ");
+ char *ptn = strtok(NULL, " ");
+ while (ptn != NULL) {
+ if (do_ptn(ptn) != ACT_OK) return TEI_FAILURE;
+ ptn = strtok(NULL, " ");
+ }
+
+ return TEI_OK;
+}
+
+static enum TEI_RETURN
+handle_tei(char *line) {
+ if (!strcmp(line, "quit")) {
+ return TEI_QUIT;
+ } if (!strcmp(line, "isready")) {
+ puts("readyok");
+ } else if (!strncmp(line, "setoption Depth value ", 22)) {
+ negamax_search_depth = atoi(line + 23);
+ } else if (!strncmp(line, "go", 2)) {
+ // TODO: for now we ignore all of the parameters
+ float minimax = negamax_generate();
+ enum ACT_RESULT r = do_ptn(negamax_ptn);
+ if (r != ACT_OK && r != GAME_END) return TEI_FAILURE;
+ printf("info score cp %f pv %s\nbestmove %s\n",
+ minimax, negamax_ptn, negamax_ptn);
+ } else if (!strncmp(line, "position", 8)) {
+ return parse_position_string(line + 9);
+ } else if (!strncmp(line, "teinewgame", 10)) {
+ if (line[11] != '5') return TEI_FAILURE;
+ const uint8_t size = line[11] - '0';
+ if (size != board_size) {
+ negamax_free();
+ reset_state(size);
+ negamax_init(size);
+ } else {
+ reset_state(size);
+ }
+ }
+ fflush(stdout);
+ return TEI_OK;
+}
+
+const char* license = "ctaktei, a TEI interface to the ctak library & its computer opponent\n\
+\n\
+Copyright (C) 2021, tslil clingman\n\
+\n\
+This program comes with ABSOLUTELY NO WARRANTY; and is made available under the terms of the GNU GPL v3 license. This is free software, and you are welcome to redistribute it under certain conditions; see COPYING for details.\n";
+
+int main(int argc, char **argv) {
+ (void)(argc);
+ (void)(argv);
+
+ puts(license);
+
+ char *line = NULL;
+ ssize_t read = -1;
+ size_t alloc_size;
+
+ // Wait for tei
+ while ((read = getline(&line, &alloc_size, stdin))) {
+ if (read > 0 && !strncmp("tei", line, 3)) {
+ break;
+ } else return EXIT_FAILURE;
+ }
+
+ if (line) free(line);
+ line = NULL;
+
+ // Identify ourselves, and send the options
+ puts("id name ct1986");
+ puts("id author tslil clingman");
+ puts("option name Depth type spin default 4 min 2 max 6");
+ puts("teiok");
+ fflush(stdout);
+
+ // Set default option
+ negamax_search_depth = 4;
+ negamax_init(5);
+ reset_state(5);
+
+ for (int playing = 1; playing;) {
+ if ((read = getline(&line, &alloc_size, stdin)) > 0) {
+ line[read-1] = 0;
+ switch (handle_tei(line)) {
+ case TEI_FAILURE: return EXIT_FAILURE;
+ case TEI_QUIT: playing = 0; // fall-through
+ case TEI_OK: {
+ if (line) {
+ free(line);
+ line = NULL;
+ }
+ break;
+ }
+ }
+ } else {
+ break;
+ }
+ }
+
+ if (line) free(line);
+ negamax_free();
+
+ return EXIT_SUCCESS;
+}