shithub: moonfish

Download patch

ref: 7fa65cc83cb6fc98f2cee5caf7ae5a14c6203e46
parent: f00468ec15edee73bc7d70444c705e516251f7c4
author: zamfofex <zamfofex@twdb.moe>
date: Sun Apr 28 21:04:49 EDT 2024

add opening book support

--- a/.build.yml
+++ b/.build.yml
@@ -24,7 +24,7 @@
       ./minify.sh
   - strip: |
       cd moonfish
-      strip --strip-all moonfish play lichess analyse battle ribbon chat
+      strip --strip-all moonfish play lichess analyse battle ribbon chat book
       llvm-strip --strip-all moonfish.wasm
   - deploy: |
       set +x
@@ -42,6 +42,7 @@
           -F moonfish.sh=@moonfish.sh \
           -F moonfish.exe=@moonfish.exe \
           -F moonfish.wasm=@moonfish.wasm \
+          -F book.txt=@book.txt \
           https://neocities.org/api/upload
       fi
 artifacts:
@@ -52,3 +53,4 @@
   - moonfish/play
   - moonfish/lichess
   - moonfish/analyse
+  - moonfish/book
--- a/.gitignore
+++ b/.gitignore
@@ -15,6 +15,7 @@
 !/chess.c
 !/search.c
 !/main.c
+!/book.txt
 !/tools
 !/tools/tools.h
 !/tools/ugi.h
@@ -31,3 +32,4 @@
 !/tools/https.h
 !/tools/https.c
 !/tools/perft.c
+!/tools/book.c
--- a/README.md
+++ b/README.md
@@ -84,9 +84,15 @@
 make chat
 ~~~
 
-All such programs may also be compiled by using the default target, `all`. Note the the Lichess integration requires BearSSL and cJSON. (The other programs require no external libraries.)
+The opening book utility may also be compiled:
 
 ~~~
+make book
+~~~
+
+All such programs may also be compiled by using the default target, `all`. Note the the Lichess integration requires LibreSSL (for libtls) and cJSON. (The other programs require no external libraries.)
+
+~~~
 make
 ~~~
 
@@ -96,6 +102,20 @@
 moonfish is a UCI bot, which means you may select it and use it with any UCI program (though see “limitations” above). You may invoke `./moonfish` to start its UCI interface.
 
 However, note that moonfish comes with its own UCI TUIs, called “play” and “analyse”. You may use them with any UCI bot you’d like!
+
+using “book”
+---
+
+Opening book support for moonfish is handled with the “book” utility. You specify a opening book file name to it as well as the command for a UCI bot, and it will intercept that bot’s communication with the GUI to make it play certain openings.
+
+The format for opening book files is very simple: One opening per line with move names in either SAN or UCI format separated by spaces. (Move numbers are no allowed.) Empty lines are ignores, `#` is used for comments. Check out the `book.txt` file for an example.
+
+Invalid opening book files will be rejected early.
+
+~~~
+# (have moonfish play openings from the given file)
+./book --file=book.txt ./moonfish
+~~~
 
 using “play” and “analyse”
 ---
--- /dev/null
+++ b/book.txt
@@ -1,0 +1,152 @@
+# Albert Silver's "Silver Opening Suite"
+# <https://www.talkchess.com/forum3/viewtopic.php?t=32532>
+
+# Bird's Opening
+f4 d5 Nf3 Nf6 b3 g6 Bb2 Bg7 e3 O-O Be2 c5 O-O Nc6
+
+# Reti Opening
+Nf3 Nf6 g3 g6 Bg2 Bg7 O-O O-O d3 d6 e4
+
+# English, Four Knights
+c4 e5 Nc3 Nf6 Nf3 Nc6
+
+# Symmetrical English, Hedgehog
+c4 c5 Nf3 Nf6 g3 b6 Bg2 Bb7 Nc3 e6 O-O Be7 d4 cxd4 Qxd4 d6 Rd1 a6
+
+# Benko Gambit
+d4 Nf6 c4 c5 d5 b5 cxb5 a6 bxa6 g6 Nc3 Bxa6
+
+# Benoni, Modern
+d4 Nf6 c4 c5 d5 e6 Nc3 exd5 cxd5 d6 e4 g6
+
+# Dutch, Leningrad
+d4 f5 g3 Nf6 Bg2 g6 Nf3 Bg7 O-O O-O c4 d6 Nc3 Qe8 d5
+
+# Dutch, Stonewall
+d4 f5 g3 Nf6 Bg2 e6 c4 d5 Nf3 c6 O-O Bd6 b3 Qe7
+
+# Scandinavian
+e4 d5 exd5 Qxd5 Nc3 Qa5 d4 Nf6 Nf3 c6 Bc4
+
+# Alekhine, Nf3
+e4 Nf6 e5 Nd5 d4 d6 Nf3
+
+# Pirc, Classical
+e4 d6 d4 Nf6 Nc3 g6 Nf3 Bg7 Be2 O-O O-O c6
+
+# Pirc, Austrian attack
+e4 d6 d4 Nf6 Nc3 g6 f4 Bg7 Nf3 O-O
+
+# Caro-Kann, Advance
+e4 c6 d4 d5 e5 Bf5 Nf3 e6 Be2 c5
+
+# Caro-Kann, Panov
+e4 c6 d4 d5 exd5 cxd5 c4 Nf6 Nc3 e6 Nf3
+
+# Caro-Kann, Classical
+e4 c6 d4 d5 Nc3 dxe4 Nxe4 Bf5 Ng3 Bg6 h4 h6 Nf3 Nd7 h5 Bh7 Bd3 Bxd3 Qxd3
+
+# Sicilian, 2.c3
+e4 c5 c3 d5 exd5 Qxd5 d4 Nf6 Nf3 e6
+
+# Sicilian, Closed
+e4 c5 Nc3 Nc6 g3 g6 Bg2 Bg7 d3 d6 f4 e6 Nf3 Nge7 O-O O-O
+
+# Sicilian, Sveshnikov
+e4 c5 Nf3 Nc6 d4 cxd4 Nxd4 Nf6 Nc3 e5 Ndb5 d6 Bg5 a6 Na3 b5 Bxf6 gxf6 Nd5 f5
+
+# Sicilian, Maroczy Bind
+e4 c5 Nf3 Nc6 d4 cxd4 Nxd4 g6 c4 Nf6 Nc3 d6 Be2
+
+# Sicilian, Paulsen
+e4 c5 Nf3 e6 d4 cxd4 Nxd4 Nc6 Nc3 a6
+
+# Sicilian, Richter-Rauzer
+e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 Nc6 Bg5 e6 Qd2 a6 O-O-O h6
+
+# Sicilian, Dragon
+e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 g6 Be3 Bg7 f3 O-O Qd2 Nc6 Bc4 Bd7 O-O-O Ne5 Bb3 Rc8
+
+# Sicilian, Scheveningen
+e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 a6 Be2 e6 O-O Be7 f4 O-O
+
+# Sicilian, Najdorf
+e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 a6
+
+# French, Advance
+e4 e6 d4 d5 e5 c5 c3 Nc6 Nf3 Qb6 a3 c4
+
+# French, Tarrasch
+e4 e6 d4 d5 Nd2 Nf6 e5 Nfd7 c3 c5 Bd3 Nc6
+
+# French, Winawer
+e4 e6 d4 d5 Nc3 Bb4 e5 c5 a3 Bxc3+ bxc3 Ne7
+
+# Philidor's Defense
+e4 e5 Nf3 d6 d4 exd4 Nxd4 Nf6 Nc3 Be7
+
+# Petroff Defense
+e4 e5 Nf3 Nf6 Nxe5 d6 Nf3 Nxe4 d4 d5 Bd3
+
+# Scotch
+e4 e5 Nf3 Nc6 d4 exd4 Nxd4
+
+# Italian
+e4 e5 Nf3 Nc6 Bc4 Bc5 d3 Nf6 c3 d6 b4 Bb6 a4 a6
+
+# Ruy Lopez, Exchange var
+e4 e5 Nf3 Nc6 Bb5 a6 Bxc6 dxc6 d4 exd4 Qxd4 Qxd4 Nxd4
+
+# Spanish, Closed - Archangels
+e4 e5 Nf3 Nc6 Bb5 a6 Ba4 Nf6 O-O b5 Bb3 Bc5
+
+# Spanish, Open
+e4 e5 Nf3 Nc6 Bb5 a6 Ba4 Nf6 O-O Nxe4 d4 b5 Bb3 d5 dxe5 Be6
+
+# Spanish, Closed - Zaitsev
+e4 e5 Nf3 Nc6 Bb5 a6 Ba4 Nf6 O-O Be7 Re1 b5 Bb3 d6 c3 O-O h3 Bb7 d4 Re8 Nbd2
+
+# Colle system
+Nf3 Nf6 d4 d5 e3 e6 c3 c5 Bd3
+
+# Queen's Gambit, Slav
+d4 d5 c4 c6 Nf3 Nf6 Nc3 dxc4 a4 Bf5
+
+# Tarrasch Defense
+d4 d5 c4 e6 Nc3 c5 cxd5 exd5 Nf3 Nc6 g3 Nf6 Bg2 Be7 O-O O-O Bg5
+
+# Queen's Gambit, Semi-Slav
+d4 d5 c4 c6 Nf3 Nf6 Nc3 e6 e3 Nbd7 Bd3
+
+# Queen's Gambit declined, Tartakower
+d4 d5 c4 e6 Nc3 Nf6 Bg5 Be7 e3 O-O Nf3 h6 Bh4 b6
+
+# Gruenfeld, Fianchetto
+d4 Nf6 c4 g6 g3 Bg7 Bg2 O-O Nf3 d5 cxd5 Nxd5 O-O
+
+# Gruenfeld, Classical
+d4 Nf6 c4 g6 Nc3 d5 cxd5 Nxd5 e4 Nxc3 bxc3 Bg7 Bc4 c5 Ne2
+
+# Catalan, Closed
+d4 Nf6 c4 e6 Nf3 d5 g3 Be7 Bg2 O-O
+
+# Bogo-Indian
+d4 Nf6 c4 e6 Nf3 Bb4+ Bd2 Qe7 g3
+
+# Queen's Indian
+d4 Nf6 c4 e6 Nf3 b6 g3
+
+# Nimzo-Indian, 4.Qc2
+d4 Nf6 c4 e6 Nc3 Bb4 Qc2 O-O a3 Bxc3+ Qxc3
+
+# Nimzo-Indian, Rubinstein
+d4 Nf6 c4 e6 Nc3 Bb4 e3 O-O Bd3 d5 Nf3 c5 O-O
+
+# King's Indian, Fianchetto
+d4 Nf6 c4 g6 Nc3 Bg7 g3 O-O Bg2 d6 Nf3 Nbd7 O-O e5 e4
+
+# King's Indian, Saemisch
+d4 Nf6 c4 g6 Nc3 Bg7 e4 d6 f3 O-O Be3 e5 Nge2 c6
+
+# King's Indian, Classical
+d4 Nf6 c4 g6 Nc3 Bg7 e4 d6 Nf3 O-O Be2 e5 O-O Nc6 d5 Ne7 Ne1 Nd7 Be3 f5 f3 f4 Bf2 g5
--- a/makefile
+++ b/makefile
@@ -15,7 +15,7 @@
 
 .PHONY: all clean install uninstall
 
-all: moonfish play lichess analyse battle ribbon chat uci-ugi ugi-uci perft
+all: moonfish play lichess analyse battle ribbon chat uci-ugi ugi-uci perft book
 
 moonfish moonfish.exe moonfish.wasm: moonfish.h chess.c search.c main.c
 	$(moonfish_cc) -o $@ $(filter %.c,$^)
--- /dev/null
+++ b/tools/book.c
@@ -1,0 +1,273 @@
+/* moonfish is licensed under the AGPL (v3 or later) */
+/* copyright 2024 zamfofex */
+
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include "../moonfish.h"
+#include "tools.h"
+
+static pthread_mutex_t moonfish_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+static void *moonfish_book_start(void *data)
+{
+	FILE *out;
+	char line[2048];
+	out = data;
+	
+	for (;;)
+	{
+		if (fgets(line, sizeof line, out) == NULL) exit(0);
+		pthread_mutex_lock(&moonfish_mutex);
+		fputs(line, stdout);
+		fflush(stdout);
+		pthread_mutex_unlock(&moonfish_mutex);		
+	}
+}
+
+static int moonfish_compare_string(const void *a, const void *b)
+{
+	return strcmp(a, b);
+}
+
+int main(int argc, char **argv)
+{
+	static char line0[2048];
+	static char line[2048];
+	static char names[64][6];
+	static char *format = "<cmd> <args>...";
+	static struct moonfish_arg args[] =
+	{
+		{"F", "file", "<book>", NULL, "opening book"},
+		{NULL, NULL, NULL, NULL, NULL},
+	};
+	static char book[256][64][6];
+	static char choices[256][6];
+	static char unique_choices[256][6];
+	
+	char **command;
+	int command_count;
+	FILE *file;
+	int i, j, k;
+	struct moonfish_chess chess;
+	struct moonfish_move move;
+	char *arg;
+	FILE *in, *out;
+	int error;
+	pthread_t thread;
+	
+	command = moonfish_args(args, format, argc, argv);
+	if (args[0].value == NULL) moonfish_usage(args, format, argv[0]);
+	
+	command_count = argc - (command - argv);
+	if (command_count < 1) moonfish_usage(args, format, argv[0]);
+	
+	srand(time(NULL));
+	
+	i = 0;
+	
+	file = fopen(args[0].value, "r");
+	if (file == NULL)
+	{
+		perror(argv[0]);
+		return 1;
+	}
+	
+	for (;;)
+	{
+		errno = 0;
+		if (fgets(line, sizeof line, file) == NULL)
+		{
+			if (errno)
+			{
+				perror(argv[0]);
+				return 1;
+			}
+			
+			break;
+		}
+		
+		arg = strtok(line, "\r\n\t ");
+		if (arg == NULL || arg[0] == '#')
+			continue;
+		
+		if (i >= (int) (sizeof book / sizeof *book - 1))
+		{
+			fprintf(stderr, "%s: Too many openings in book, ignoring some.\n", argv[0]);
+			break;
+		}
+		
+		moonfish_chess(&chess);
+		
+		j = 0;
+		
+		for (;;)
+		{
+			if (j >= (int) (sizeof *book / sizeof **book - 1))
+			{
+				fprintf(stderr, "%s: Too many moves in opening, ignoring some.\n", argv[0]);
+				break;
+			}
+			
+			if (moonfish_from_san(&chess, &move, arg))
+			{
+				fprintf(stderr, "%s: Invalid move in opening: %s\n", argv[0], arg);
+				return 1;
+			}
+			
+			moonfish_play(&chess, &move);
+			moonfish_to_uci(book[i][j++], &move);
+			
+			arg = strtok(NULL, "\r\n\t ");
+			if (arg == NULL || arg[0] == '#')
+				break;
+		}
+		
+		book[i++][j][0] = 0;
+	}
+	
+	book[i][0][0] = 0;
+	
+	if (i == 0) fprintf(stderr, "%s: Empty book.\n", argv[0]);
+	
+	moonfish_spawn(argv[0], command, &in, &out, NULL);
+	
+	error = pthread_create(&thread, NULL, moonfish_book_start, out);
+	if (error != 0)
+	{
+		fprintf(stderr, "%s: %s\n", argv[0], strerror(error));
+		return 1;
+	}
+	
+	names[0][0] = 0;
+	
+	for (;;)
+	{
+		errno = 0;
+		if (fgets(line0, sizeof line0, stdin) == NULL)
+		{
+			if (errno)
+			{
+				perror(argv[0]);
+				return 1;
+			}
+			
+			break;
+		}
+		
+		strcpy(line, line0);
+		
+		arg = strtok(line, "\r\n\t ");
+		if (arg == NULL) continue;
+		
+		if (!strcmp(arg, "quit"))
+		{
+			pthread_mutex_lock(&moonfish_mutex);
+			fputs("quit\n", in);
+			fflush(stdout);
+			pthread_mutex_unlock(&moonfish_mutex);
+			break;
+		}
+		else if (!strcmp(arg, "position"))
+		{
+			names[0][0] = '-';
+			names[0][1] = 0;
+			
+			arg = strtok(NULL, "\r\n\t ");
+			if (arg != NULL && !strcmp(arg, "startpos"))
+			{
+				arg = strtok(NULL, "\r\n\t ");
+				if (arg == NULL)
+				{
+					names[0][0] = 0;
+				}
+				else if (!strcmp(arg, "moves"))
+				{
+					i = 0;
+					while ((arg = strtok(NULL, "\r\n\t ")) != NULL)
+					{
+						if (i >= (int) (sizeof names / sizeof *names - 1)) break;
+						if (strlen(arg) > 5) break;
+						strcpy(names[i++], arg);
+					}
+					
+					names[i][0] = 0;
+				}
+			}
+		}
+		else if (!strcmp(arg, "go"))
+		{
+			arg = strtok(NULL, "\r\n\t ");
+			if (arg == NULL || strcmp(arg, "infinite"))
+			{
+				i = 0;
+				k = 0;
+				for (;;)
+				{
+					if (book[i][0][0] == 0) break;
+					
+					j = 0;
+					for (;;)
+					{
+						if (names[j][0] == 0)
+						{
+							if (book[i][j][0] != 0)
+								strcpy(choices[k++], book[i][j]);
+							break;
+						}
+						
+						if (strcmp(names[j], book[i][j]))
+							break;
+						
+						j++;
+					}
+					
+					i++;
+				}
+				
+				if (k == 0)
+				{
+					pthread_mutex_lock(&moonfish_mutex);
+					fputs(line0, in);
+					fflush(stdout);
+					pthread_mutex_unlock(&moonfish_mutex);
+					continue;
+				}
+				
+				qsort(choices, k, sizeof *choices, &moonfish_compare_string);
+				
+				i = 1;
+				j = 1;
+				
+				strcpy(unique_choices[0], choices[0]);
+				for (;;)
+				{
+					if (j >= k) break;
+					if (strcmp(choices[j], choices[j - 1]))
+						strcpy(unique_choices[i++], choices[j]);
+					j++;
+				}
+				
+				pthread_mutex_lock(&moonfish_mutex);
+				printf("bestmove %s\n", unique_choices[rand() % i]);
+				fflush(stdout);
+				pthread_mutex_unlock(&moonfish_mutex);
+				
+				continue;
+			}
+		}
+		
+		pthread_mutex_lock(&moonfish_mutex);
+		fputs(line0, in);
+		fflush(stdout);
+		pthread_mutex_unlock(&moonfish_mutex);
+	}
+	
+	fclose(in);
+	pthread_join(thread, NULL);
+	return 0;
+}
--