ref: 401af6dfdde75f7e37b5f4874e99776d9ea1de67
parent: bbf7408073c3175bba723758d3038c4c14a2ba71
author: zamfofex <zamfofex@twdb.moe>
date: Sun Jan 21 12:46:26 EST 2024
add UGI/UCI translators
--- a/.build.yml
+++ b/.build.yml
@@ -39,3 +39,5 @@
- moonfish/analyse
- moonfish/moonfish.sh
- moonfish/moonfish.exe
+ - moonfish/ugi-uci
+ - moonfish/uci-ugi
--- a/.gitignore
+++ b/.gitignore
@@ -17,7 +17,11 @@
!/main.c
!/tools
!/tools/tools.h
+!/tools/ugi.h
!/tools/utils.c
!/tools/play.c
!/tools/analyse.c
!/tools/lichess.c
+!/tools/ugi.c
+!/tools/ugi-uci.c
+!/tools/uci-ugi.c
--- a/README.md
+++ b/README.md
@@ -24,6 +24,7 @@
- cute custom UCI TUIs
- custom Lichess integration
- threaded search
+- custom UGI/UCI translators
limitations
---
@@ -32,7 +33,6 @@
- the bot will never underpromote
- the TUI will also prevent you from underpromoting
-- the TUI does not detect when the game has ended due to stalemate or checkmate
- no transposition table
- no support for `go infinite` or `go mate`
- no move name or FEN validation (may lead to potential exploits)
@@ -67,6 +67,12 @@
make play analyse
~~~
+The UGI/UCI translators may also be compiled:
+
+~~~
+make ugi-uci uci-ugi
+~~~
+
usage
---
@@ -99,6 +105,13 @@
# (analyse a game using Leela, showing win/draw/loss evaluation)
./analyse initial lc0 --show-wdl
~~~
+
+`ugi-uci` can be used to let a UGI GUI communicate with a UCI bot, and conversely, `uci-ugi` can be used to let a UCI GUI communicate with a UGI bot.
+
+Simply pass the command of the bot as arguments to either of these tools, and it’ll translate it to be used by the respective GUI.
+
+Note that if the GUI sends a `uci`/`gui` command to the bot that is the same as its protocol, no translation will happen, and the commands will be passed in verbatim between them.
+
compiling on Windows
---
--- a/chess.c
+++ b/chess.c
@@ -78,6 +78,17 @@
void moonfish_move(struct moonfish_chess *chess, struct moonfish_move *move, unsigned char from, unsigned char to)
{moonfish_create_move(chess, &move, from, to);
+ if (chess->board[from] % 16 == moonfish_pawn)
+ {+ if (chess->board[from] / 16 == 1)
+ {+ if (from > 80) move[-1].promotion = moonfish_white_queen;
+ }
+ else
+ {+ if (from < 40) move[-1].promotion = moonfish_black_queen;
+ }
+ }
}
static char moonfish_delta(struct moonfish_chess *chess, struct moonfish_move **moves, unsigned char from, unsigned char *to, signed char delta)
@@ -656,3 +667,41 @@
return valid ^ 1;
}
+
+#ifndef moonfish_mini
+
+int moonfish_finished(struct moonfish_chess *chess)
+{+ struct moonfish_move moves[32], *move;
+ int x, y;
+ int valid;
+
+ for (y = 0 ; y < 8 ; y++)
+ for (x = 0 ; x < 8 ; x++)
+ {+ moonfish_moves(chess, moves, (x + 1) + (y + 2) * 10);
+ for (move = moves ; move->piece != moonfish_outside ; move++)
+ {+ moonfish_play(chess, move);
+ valid = moonfish_validate(chess);
+ moonfish_unplay(chess, move);
+ if (valid) return 0;
+ }
+ }
+
+ return 1;
+}
+
+int moonfish_checkmate(struct moonfish_chess *chess)
+{+ if (!moonfish_check(chess)) return 0;
+ return moonfish_finished(chess);
+}
+
+int moonfish_stalemate(struct moonfish_chess *chess)
+{+ if (moonfish_check(chess)) return 0;
+ return moonfish_finished(chess);
+}
+
+#endif
--- a/makefile
+++ b/makefile
@@ -15,7 +15,7 @@
.PHONY: all clean install uninstall
-all: moonfish play lichess analyse
+all: moonfish play lichess analyse uci-ugi ugi-uci
moonfish moonfish.exe: moonfish.h chess.c search.c main.c
$(moonfish_cc) -o $@ $^
@@ -23,6 +23,12 @@
%: moonfish.h tools/tools.h tools/%.c tools/utils.c chess.c
$(or $($(@)_cc),$(tools_cc)) -o $@ $^ $($(@)_libs)
+ugi-uci: moonfish.h tools/tools.h tools/ugi.h tools/utils.c tools/ugi.c tools/ugi-uci.c chess.c
+ $(tools_cc) -o $@ $^
+
+uci-ugi: tools/tools.h tools/ugi.h tools/utils.c tools/ugi.c tools/uci-ugi.c
+ $(tools_cc) -o $@ $^
+
clean:
git clean -fdx
@@ -31,6 +37,8 @@
install -m 755 play $(BINDIR)/moonfish-play
install -m 755 lichess $(BINDIR)/moonfish-lichess
install -m 755 analyse $(BINDIR)/moonfish-analyse
+ install -m 755 ugi-uci $(BINDIR)/ugi-uci
+ install -m 755 uci-ugi $(BINDIR)/uci-ugi
uninstall:
- $(RM) $(BINDIR)/moonfish $(BINDIR)/moonfish-*
+ $(RM) $(BINDIR)/moonfish $(BINDIR)/moonfish-* $(BINDIR)/ugi-uci $(BINDIR)/uci-ugi
--- a/moonfish.h
+++ b/moonfish.h
@@ -88,6 +88,10 @@
int moonfish_fen(struct moonfish_chess *chess, char *fen);
int moonfish_best_move_depth(struct moonfish *ctx, struct moonfish_move *move, int depth);
+int moonfish_finished(struct moonfish_chess *chess);
+int moonfish_checkmate(struct moonfish_chess *chess);
+int moonfish_stalemate(struct moonfish_chess *chess);
+
#endif
#endif
--- a/tools/play.c
+++ b/tools/play.c
@@ -544,6 +544,7 @@
moonfish_play(&fancy->chess, &move);
moonfish_reset_time(fancy);
moonfish_fancy(fancy);
+ if (moonfish_finished(&fancy->chess)) break;
pthread_mutex_unlock(fancy->mutex);
@@ -555,6 +556,8 @@
name += strlen(name);
moonfish_reset_time(fancy);
moonfish_fancy(fancy);
+ if (moonfish_finished(&fancy->chess)) break;
+
pthread_mutex_unlock(fancy->mutex);
printf("\x1B[?1000h");--- /dev/null
+++ b/tools/uci-ugi.c
@@ -1,0 +1,109 @@
+/* moonfish is licensed under the AGPL (v3 or later) */
+/* copyright 2024 zamfofex */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "ugi.h"
+
+/* UCI GUI, UGI bot */
+
+char *moonfish_argv0;
+
+int main(int argc, char **argv)
+{+ if (argc < 2)
+ {+ if (argc > 0) fprintf(stderr, "usage: %s <command> <args>...\n", argv[0]);
+ return 1;
+ }
+
+ moonfish_argv0 = argv[0];
+ moonfish_convert_ugi(argv[0], argv + 1, "ugi", "uci");
+ return 0;
+}
+
+/* read from GUI, write to bot */
+void *moonfish_convert_in(void *data)
+{+ FILE *in;
+ char line[2048], *arg;
+ char *save;
+
+ in = data;
+
+ for (;;)
+ {+ if (fgets(line, sizeof line, stdin) == NULL) return NULL;
+ save = NULL;
+
+ arg = strtok_r(line, "\r\n\t ", &save);
+
+ if (arg == NULL) continue;
+
+ if (!strcmp(arg, "go"))
+ {+ fprintf(in, "go");
+ for (;;)
+ {+ arg = strtok_r(NULL, "\r\n\t ", &save);
+ if (arg == NULL) break;
+ fprintf(in, " ");
+ if (!strcmp(arg, "wtime")) fprintf(in, "p1time");
+ else if (!strcmp(arg, "btime")) fprintf(in, "p2time");
+ else if (!strcmp(arg, "winc")) fprintf(in, "p1inc");
+ else if (!strcmp(arg, "binc")) fprintf(in, "p2inc");
+ else fputs(arg, in);
+ }
+ fprintf(in, "\n");
+ }
+
+ fputs(arg, in);
+ for (;;)
+ {+ arg = strtok_r(NULL, "\r\n\t ", &save);
+ if (arg == NULL) break;
+ fprintf(in, " %s", arg);
+ }
+ fprintf(in, "\n");
+ }
+}
+
+
+/* read from bot, write to GUI */
+void *moonfish_convert_out(void *data)
+{+ FILE *out;
+ char line[2048], *arg;
+ char *save;
+
+ out = data;
+
+ for (;;)
+ {+ if (fgets(line, sizeof line, out) == NULL) exit(0);
+ save = NULL;
+
+ arg = strtok_r(line, "\r\n\t ", &save);
+
+ if (arg == NULL) continue;
+
+ if (!strcmp(arg, "ugiok"))
+ {+ printf("uciok\n");+ fflush(stdout);
+ continue;
+ }
+
+ fputs(arg, stdout);
+ for (;;)
+ {+ arg = strtok_r(NULL, "\r\n\t ", &save);
+ if (arg == NULL) break;
+ printf(" %s", arg);+ }
+ printf("\n");+ fflush(stdout);
+ }
+}
--- /dev/null
+++ b/tools/ugi-uci.c
@@ -1,0 +1,215 @@
+/* moonfish is licensed under the AGPL (v3 or later) */
+/* copyright 2024 zamfofex */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "../moonfish.h"
+#include "ugi.h"
+
+/* UGI GUI, UCI bot */
+
+char *moonfish_argv0;
+static struct moonfish_chess moonfish_ugi_chess;
+
+int main(int argc, char **argv)
+{+ if (argc < 2)
+ {+ if (argc > 0) fprintf(stderr, "usage: %s <command> <args>...\n", argv[0]);
+ return 1;
+ }
+
+ moonfish_chess(&moonfish_ugi_chess);
+ moonfish_argv0 = argv[0];
+ moonfish_convert_ugi(argv[0], argv + 1, "ugi", "uci");
+ return 0;
+}
+
+/* read from GUI, write to bot */
+void *moonfish_convert_in(void *data)
+{+ FILE *in;
+ char line[2048], line2[2048], *arg;
+ char *save;
+ struct moonfish_move move;
+
+ in = data;
+
+ for (;;)
+ {+ if (fgets(line, sizeof line, stdin) == NULL) return NULL;
+ save = NULL;
+ strcpy(line2, line);
+
+ arg = strtok_r(line, "\r\n\t ", &save);
+
+ if (arg == NULL) continue;
+
+ if (!strcmp(arg, "query"))
+ {+ arg = strtok_r(NULL, "\r\n\t ", &save);
+ if (arg == NULL)
+ {+ fprintf(stderr, "%s: malformed 'query' command\n", moonfish_argv0);
+ exit(1);
+ }
+
+ if (!strcmp(arg, "gameover"))
+ {+ if (moonfish_finished(&moonfish_ugi_chess))
+ printf("response true\n");+ else
+ printf("response false\n");+ continue;
+ }
+
+ if (!strcmp(arg, "p1turn"))
+ {+ if (moonfish_ugi_chess.white)
+ printf("response true\n");+ else
+ printf("response false\n");+ continue;
+ }
+
+ if (!strcmp(arg, "result"))
+ {+ if (!moonfish_finished(&moonfish_ugi_chess))
+ printf("response none\n");+ else if (!moonfish_checkmate(&moonfish_ugi_chess))
+ printf("response draw\n");+ else if (moonfish_ugi_chess.white)
+ printf("response p2win\n");+ else
+ printf("response p1win\n");+ continue;
+ }
+
+ fprintf(stderr, "%s: unknown 'query %s' command\n", moonfish_argv0, arg);
+ exit(1);
+ }
+
+ if (!strcmp(arg, "go"))
+ {+ fprintf(in, "go");
+ for (;;)
+ {+ arg = strtok_r(NULL, "\r\n\t ", &save);
+ if (arg == NULL) break;
+ fprintf(in, " ");
+ if (!strcmp(arg, "p1time")) fprintf(in, "wtime");
+ else if (!strcmp(arg, "p2time")) fprintf(in, "btime");
+ else if (!strcmp(arg, "p1inc")) fprintf(in, "winc");
+ else if (!strcmp(arg, "p2inc")) fprintf(in, "binc");
+ else fputs(arg, in);
+ }
+ fprintf(in, "\n");
+ continue;
+ }
+
+ if (!strcmp(arg, "position"))
+ {+ arg = strtok_r(NULL, "\r\n\t ", &save);
+ if (arg == NULL)
+ {+ fprintf(stderr, "%s: malformed 'position' command\n", moonfish_argv0);
+ exit(1);
+ }
+
+ if (!strcmp(arg, "startpos"))
+ {+ moonfish_chess(&moonfish_ugi_chess);
+ }
+ else if (!strcmp(arg, "fen"))
+ {+ arg = strtok_r(NULL, "\r\n", &save);
+ if (arg == NULL)
+ {+ fprintf(stderr, "%s: malformed 'position fen' command\n", moonfish_argv0);
+ exit(1);
+ }
+ moonfish_fen(&moonfish_ugi_chess, arg);
+
+ arg = strstr(arg, "moves");
+ if (arg != NULL)
+ {+ do arg--;
+ while (*arg == '\t' || *arg == ' ');
+ save = NULL;
+ strcpy(line, arg);
+ strtok_r(line, "\r\n", &save);
+ }
+ else
+ {+ save = NULL;
+ strtok_r("", "\r\n", &save);+ }
+ }
+ else
+ {+ fprintf(stderr, "%s: unknown 'position %s' command\n", moonfish_argv0, arg);
+ exit(1);
+ }
+
+ arg = strtok_r(NULL, "\r\n\t ", &save);
+ if (arg != NULL)
+ {+ if (strcmp(arg, "moves"))
+ {+ fprintf(stderr, "%s: unknown '%s' in 'position' command\n", moonfish_argv0, arg);
+ exit(1);
+ }
+
+ for (;;)
+ {+ arg = strtok_r(NULL, " ", &save);
+ if (arg == NULL) break;
+ moonfish_from_uci(&moonfish_ugi_chess, &move, arg);
+ moonfish_play(&moonfish_ugi_chess, &move);
+ }
+ }
+ }
+
+ fputs(line2, in);
+ }
+}
+
+
+/* read from bot, write to GUI */
+void *moonfish_convert_out(void *data)
+{+ FILE *out;
+ char line[2048], *arg;
+ char *save;
+
+ out = data;
+
+ for (;;)
+ {+ if (fgets(line, sizeof line, out) == NULL) exit(0);
+ save = NULL;
+
+ arg = strtok_r(line, "\r\n\t ", &save);
+
+ if (arg == NULL) continue;
+
+ if (!strcmp(arg, "uciok"))
+ {+ printf("ugiok\n");+ fflush(stdout);
+ continue;
+ }
+
+ fputs(arg, stdout);
+ for (;;)
+ {+ arg = strtok_r(NULL, "\r\n\t ", &save);
+ if (arg == NULL) break;
+ printf(" %s", arg);+ }
+ printf("\n");+ fflush(stdout);
+ }
+}
--- /dev/null
+++ b/tools/ugi.c
@@ -1,0 +1,88 @@
+/* moonfish is licensed under the AGPL (v3 or later) */
+/* copyright 2024 zamfofex */
+
+#include <string.h>
+#include <pthread.h>
+#include <stdlib.h>
+
+#include "tools.h"
+#include "ugi.h"
+
+static void *moonfish_pipe_in(void *data)
+{+ FILE *in;
+ char line[2048];
+ in = data;
+
+ for (;;)
+ {+ if (fgets(line, sizeof line, stdin) == NULL) return NULL;
+ fputs(line, in);
+ }
+}
+
+static void *moonfish_pipe_out(void *data)
+{+ FILE *out;
+ char line[2048];
+ out = data;
+
+ for (;;)
+ {+ if (fgets(line, sizeof line, out) == NULL) exit(0);
+ fputs(line, stdout);
+ fflush(stdout);
+ }
+}
+
+void moonfish_convert_ugi(char *argv0, char **argv, char *convert_arg, char *pipe_arg)
+{+ FILE *in, *out;
+ char line[2048], *arg;
+ int error;
+ pthread_t thread1, thread2;
+ void *(*start1)(void *data);
+ void *(*start2)(void *data);
+
+ moonfish_spawn(argv0, argv, &in, &out);
+
+ if (fgets(line, sizeof line, stdin) == NULL) return;
+
+ for (;;)
+ {+ arg = strtok(line, "\r\n\t ");
+ if (arg == NULL) continue;
+
+ if (!strcmp(arg, convert_arg))
+ {+ fprintf(in, "%s\n", pipe_arg);
+ start1 = &moonfish_convert_in;
+ start2 = &moonfish_convert_out;
+ break;
+ }
+
+ if (!strcmp(arg, pipe_arg))
+ {+ fprintf(in, "%s\n", pipe_arg);
+ start1 = &moonfish_pipe_in;
+ start2 = &moonfish_pipe_out;
+ break;
+ }
+
+ fprintf(stderr, "%s: unknown protocol\n", argv0);
+ exit(1);
+ }
+
+ error = 0;
+ if (error == 0) error = pthread_create(&thread1, NULL, start1, in);
+ if (error == 0) error = pthread_create(&thread2, NULL, start2, out);
+ if (error == 0) error = pthread_join(thread1, NULL);
+ if (error == 0) fclose(in);
+ if (error == 0) error = pthread_join(thread2, NULL);
+
+ if (error != 0)
+ {+ fprintf(stderr, "%s: %s\n", argv0, strerror(error));
+ exit(1);
+ }
+}
--- /dev/null
+++ b/tools/ugi.h
@@ -1,0 +1,11 @@
+/* moonfish is licensed under the AGPL (v3 or later) */
+/* copyright 2024 zamfofex */
+
+#ifndef MOONFISH_UGI
+#define MOONFISH_UGI
+
+void *moonfish_convert_in(void *data);
+void *moonfish_convert_out(void *data);
+void moonfish_convert_ugi(char *argv0, char **argv, char *convert_arg, char *pipe_arg);
+
+#endif
--
⑨