shithub: moonfish

Download patch

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
--