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;
+}
--
⑨