ref: 8e9a8fe80cf9ab81aec8ff29dae9dc59ffbd5110
parent: 27b1ee1189feeecd2eefffe866426151cbe0d09f
author: zamfofex <zamfofex@twdb.moe>
date: Thu Mar 28 14:31:16 EDT 2024
add “battle” and “ribbon” CLI programs
--- a/.gitignore
+++ b/.gitignore
@@ -22,6 +22,8 @@
!/tools/play.c
!/tools/analyse.c
!/tools/lichess.c
+!/tools/battle.c
+!/tools/ribbon.c
!/tools/ugi.c
!/tools/ugi-uci.c
!/tools/uci-ugi.c
--- a/README.md
+++ b/README.md
@@ -52,9 +52,9 @@
Conversely, you may also invoke your compiler by hand. (Feel free to replace `cc` with your compiler of choice.)
-Note: If your C implementation doesn’t support pthreads, but supports C11 threads, you can pass in `-Dmoonfish_c11_threads`.
+Note: If your C implementation doesn’t support pthreads, but supports C11 threads, you may pass in `-Dmoonfish_c11_threads`.
-Note: If your C implementation doesn’t support threads at all, you can pass in `-Dmoonfish_no_threads`.
+Note: If your C implementation doesn’t support threads at all, you may pass in `-Dmoonfish_no_threads`.
~~~
cc -ansi -O3 -pthread -D_POSIX_C_SOURCE=199309L -o moonfish chess.c search.c main.c
@@ -72,13 +72,28 @@
make ugi-uci uci-ugi
~~~
-usage
+The CLI tools, called “battle” and “ribbon” to (respectively) play a single game between two bots and to play a tournament between any given number of bots may also be compiled.
+
+~~~
+make battle ribbon
+~~~
+
+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.)
+
+~~~
+make
+~~~
+
+using moonfish
---
-moonfish is a UCI bot, which means you can select it and use it with any UCI program (though see “limitations” above). You can invoke `./moonfish` to start its UCI interface.
+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 can use them with any UCI engine you’d like!
+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 “play” and “analyse”
+---
+
To play against a UCI bot, use `./play` followed by the command of whichever bot you want to play against. The color of your pieces will be decided randomly by default.
~~~
@@ -108,7 +123,92 @@
./analyse 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.
+using “battle”
+---
+
+The “battle” CLI may be used to have two UCI or UGI bots play against each other. Each bot has to be specified between brackets (these ones: `[` and `]`).
+
+~~~
+./battle [ stockfish ] [ lc0 --preload ] > game.pgn
+~~~
+
+The bot specified first will play as white (or p1 for UGI) and the bot specified second will play as black (or p2 for UGI).
+
+The program will write a simplified PGN variant to stdout with the game as it happens. The moves will be in UCI/UGI format, rather than SAN.
+
+The program will also annouce to stderr the start and end of the game.
+
+If your bot’s command requires a `]` to be used as an argument, you may use double brackets instead, like `./battle [[ unusual ] --foo ]] [ stockfish ]`. You may use as many brackets as necessary, you just have to match them when closing.
+
+If your bot’s command starts with a dash, you may precede it by `--`, like `./battle [ -- -unusual ] [ stockfish ]`
+
+A FEN string may be passed in to the `battle` command, like `./battle --fen='...' [ stockfish ] [ lc0 ]`
+
+You may also pass in a time control, with time and increment both in milliseconds, like `./battle [ --time=6000+500 stockfish ] [ ./moonfish ]` Since each bot may have a different time control, this has to be specified within the backets for a specific bot. The default is 15 minutes with ten seconds of increment.
+
+You may also pass in `x` as the first character of the given time control to make it “fixed”, which means that it will be reset after every move. This can be used to set a fixed time per move, for example. For example, `./battle [ --time=1000+0 stockfish ] [ --time --time=x1000+0 ./moonfish ]` will set a countdown clock starting at one second for Stockfish, but allow one second for moonfish for every move.
+
+In order to use a UGI bot, you may also pass in `--protocol=ugi` to that bot, like `./battle [ --protocol=ugi some-bot ] [ stockfish ]`. If both bots are UGI, then the game is not assumed to be chess, and the bot playing as p1 will be queried for the status of the game.
+
+using “ribbon”
+---
+
+“ribbon” is a CLI for setting up a round‐robin tournament between multiple bots with the help of the “battle” program mentioned above and a makefile (currently requiring GNU Make).
+
+It will output a makefile to stdout, which may be used by GNU Make to start the tournament between the given bots.
+
+You may use the `-j` option of GNU Make to configure the number of concurrent games to play. Likewise, you may stop a given tournament with `ctrl-c` and continue it by simply running Make again.
+
+You may modify the makefile to prevent certain games from continuing to be played.
+
+Each bot’s command should be passed in as a single argument to `./ribbon`, including potential `--time=...` and `--protocol=...` options to be interpreted by “battle”.
+
+The file name of an opening book may be passed in, and the file should contain one FEN per line. Empty lines and comments starting with `#` are allowed.
+
+In addition, as a matter of convenience, you may specify `--time=...` to “ribbon” itself, which will be passed in verbatim to “battle”.
+
+~~~
+# prepare a tournament between Stockfish, Leela and moonfish:
+./ribbon stockfish lc0 ./moonfish > makefile
+
+# same as above, but with an argument passed in to Leela:
+./ribbon stockfish 'lc0 --preload' ./moonfish > makefile
+
+# same, but with the time control made explicit:
+./ribbon --time=18000+1000 stockfish 'lc0 --preload' ./moonfish > makefile
+
+# same, but with a different time control for Stockfish:
+# (mind the '--' separating "ribbon" options from the bot options for "battle")
+./ribbon --time=18000+1000 -- '--time=6000+0 stockfish' 'lc0 --preload' ./moonfish > makefile
+
+# start the tournament, with at most two games being played at any given time:
+make -j2
+
+# concatenate the resulting game PGNs into a single file.
+cat *.pgn > result.pgn
+~~~
+
+In addition, you may specify the “battle” command to “ribbon”, which must be done if “battle” is not installed on your system as `moonfish-battle`, like `./ribbon --battle=./battle stockfish lc0`
+
+~~~
+# prepare a tournament using the locally-built "battle":
+./ribbon --battle=./battle stockfish lc0 ./moonfish > makefile
+
+# set up a timeout of thirty seconds for each game:
+./ribbon --time=1000+0 --battle='timeout 30 moonfish-battle' stockfish lc0 ./moonfish > makefile
+
+# same as above, but with locally-built "battle":
+./ribbon --time=1000+0 --battle='timeout 30 ./battle' stockfish lc0 ./moonfish > makefile
+
+# pick up the tournament again on every failure (possibly due to timeout):
+while ! make
+do : ; done
+~~~
+
+using the UGI/UCI translators
+---
+
+`ugi-uci` may be used to let a UGI GUI communicate with a UCI bot, and conversely, `uci-ugi` may 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.
--- a/makefile
+++ b/makefile
@@ -15,7 +15,7 @@
.PHONY: all clean install uninstall
-all: moonfish play lichess analyse uci-ugi ugi-uci
+all: moonfish play lichess analyse battle ribbon uci-ugi ugi-uci
moonfish moonfish.exe moonfish.wasm: moonfish.h chess.c search.c main.c
$(moonfish_cc) -o $@ $(filter %.c,$^)
@@ -37,6 +37,8 @@
install -D -m 755 play $(BINDIR)/moonfish-play
install -D -m 755 lichess $(BINDIR)/moonfish-lichess
install -D -m 755 analyse $(BINDIR)/moonfish-analyse
+ install -D -m 755 battle $(BINDIR)/moonfish-battle
+ install -D -m 755 ribbon $(BINDIR)/moonfish-ribbon
install -D -m 755 ugi-uci $(BINDIR)/ugi-uci
install -D -m 755 uci-ugi $(BINDIR)/uci-ugi
--- /dev/null
+++ b/tools/battle.c
@@ -1,0 +1,480 @@
+/* moonfish is licensed under the AGPL (v3 or later) */
+/* copyright 2024 zamfofex */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "../moonfish.h"
+#include "tools.h"
+
+struct moonfish_bot
+{+ char *name;
+ FILE *in, *out;
+ long int time, increment;
+ int fixed_time;
+ int ugi;
+};
+
+struct moonfish_battle
+{+ struct moonfish_chess chess;
+ struct moonfish_bot white, black;
+ char *fen;
+ int ugi;
+ char **moves;
+ int move_count;
+};
+
+static void moonfish_usage0(char *argv0)
+{+ fprintf(stderr, "usage: %s <options>... '[' <options>... <cmd> <args>... ']' '[' <options>... <cmd> <args>... ']'\n", argv0);
+ fprintf(stderr, "options:\n");
+ fprintf(stderr, " --fen=<FEN> the starting position of the game\n");
+ fprintf(stderr, "bot options:\n");
+ fprintf(stderr, " --protocol=<protocol> either 'uci' or 'ugi' (default: 'uci')\n");
+ fprintf(stderr, " --time=<time-control> the time and increment for the bot in milliseconds\n");
+ fprintf(stderr, " --name=<name> name to be reported in the logs and in the output PGN\n");
+ exit(1);
+}
+
+static char *moonfish_option(char *name, char ***argv)
+{+ size_t length;
+
+ if (**argv == NULL) return NULL;
+
+ if (!strcmp(**argv, name))
+ {+ (*argv)++;
+ return *(*argv)++;
+ }
+
+ length = strlen(name);
+ if (!strncmp(**argv, name, length) && (**argv)[length] == '=')
+ return *(*argv)++ + length + 1;
+
+ return NULL;
+}
+
+static void moonfish_battle_options(struct moonfish_battle *battle, char ***argv)
+{+ char *arg;
+
+ for (;;)
+ {+ arg = moonfish_option("--fen", argv);+ if (arg != NULL)
+ {+ battle->fen = arg;
+ continue;
+ }
+
+ break;
+ }
+}
+
+static void moonfish_bot_options(char *argv0, struct moonfish_bot *bot, char ***argv)
+{+ char *arg;
+
+ for (;;)
+ {+ arg = moonfish_option("--time", argv);+ if (arg != NULL)
+ {+ bot->fixed_time = 0;
+ if (arg[0] == 'x')
+ {+ bot->fixed_time = 1;
+ arg++;
+ }
+
+ if (sscanf(arg, "%ld%ld", &bot->time, &bot->increment) != 2)
+ moonfish_usage0(argv0);
+
+ continue;
+ }
+
+ arg = moonfish_option("--protocol", argv);+ if (arg != NULL)
+ {+ if (!strcmp(arg, "uci"))
+ bot->ugi = 0;
+ else if (!strcmp(arg, "ugi"))
+ bot->ugi = 1;
+ else
+ moonfish_usage0(argv0);
+
+ continue;
+ }
+
+ arg = moonfish_option("--name", argv);+ if (arg != NULL)
+ {+ bot->name = arg;
+ continue;
+ }
+
+ break;
+ }
+}
+
+static void moonfish_bot_arguments(char *argv0, struct moonfish_bot *bot, char ***argv)
+{+ size_t i, j;
+ char **options, **command, *value;
+ char *line, *arg;
+ char *buffer;
+
+ if (**argv == NULL) moonfish_usage0(argv0);
+
+ for (i = 0 ; (**argv)[i] != '\0' ; i++)
+ if ((**argv)[i] != '[') moonfish_usage0(argv0);
+
+ if (i < 1) moonfish_usage0(argv0);
+
+ bot->name = NULL;
+
+ (*argv)++;
+ moonfish_bot_options(argv0, bot, argv);
+
+ if (**argv != NULL)
+ {+ if (!strcmp(**argv, "--"))
+ (*argv)++;
+ else if (***argv == '-')
+ moonfish_usage0(argv0);
+ }
+
+ options = NULL;
+
+ j = 0;
+ for (;;)
+ {+ options = realloc(options, (j + 2) * sizeof *options);
+ if (options == NULL)
+ {+ perror(argv0);
+ exit(1);
+ }
+
+ if (**argv == NULL) moonfish_usage0(argv0);
+ options[j++] = *(*argv)++;
+
+ if (strlen(**argv) != i) continue;
+ if (strspn(**argv, "]") != i) continue;
+
+ if (**argv == NULL) moonfish_usage0(argv0);
+ (*argv)++;
+ break;
+ }
+
+ if (j < 1) moonfish_usage0(argv0);
+
+ options[j] = NULL;
+
+ command = options;
+
+ while (strchr(*command, '=') != NULL)
+ command++;
+ if (*command != NULL && !strcmp(*command, "--"))
+ command++;
+
+ moonfish_spawn(argv0, command, &bot->in, &bot->out);
+
+ if (bot->ugi) fprintf(bot->in, "ugi\n");
+ else fprintf(bot->in, "uci\n");
+
+ for (;;)
+ {+ line = moonfish_next(bot->out);
+ if (line == NULL) exit(1);
+
+ arg = strtok_r(line, "\r\n\t ", &buffer);
+ if (arg == NULL) continue;
+
+ if (!strcmp(arg, bot->ugi ? "ugiok" : "uciok")) break;
+
+ if (bot->name != NULL) continue;
+
+ if (strcmp(arg, "id")) continue;
+
+ arg = strtok_r(NULL, "\r\n\t ", &buffer);
+ if (arg == NULL) continue;
+ if (strcmp(arg, "name")) continue;
+
+ arg = strtok_r(NULL, "\r\n", &buffer);
+ if (arg == NULL) continue;
+
+ bot->name = strdup(arg);
+ if (bot->name == NULL)
+ {+ perror(argv0);
+ exit(1);
+ }
+ }
+
+ if (bot->name == NULL) bot->name = command[0];
+
+ j = 0;
+ for (;;)
+ {+ value = strchr(options[j], '=');
+ if (value == NULL) break;
+ fprintf(bot->in, "setoption name %.*s value %s\n", (int) (value - options[j]), options[j], value + 1);
+ j++;
+ }
+
+ free(options);
+
+ fprintf(bot->in, "isready\n");
+ moonfish_wait(bot->out, "readyok");
+
+ if (bot->ugi) fprintf(bot->in, "uginewgame\n");
+ else fprintf(bot->in, "ucinewgame\n");
+}
+
+static char *moonfish_bot_play(char *argv0, struct moonfish_battle *battle, struct moonfish_bot *bot)
+{+ char *white, *black;
+ int i;
+ char *arg;
+ char *buffer;
+ struct moonfish_move move;
+
+ struct timespec t0, t1;
+
+ if (!bot->fixed_time && clock_gettime(CLOCK_MONOTONIC, &t0))
+ {+ perror(argv0);
+ exit(1);
+ }
+
+ fprintf(bot->in, "isready\n");
+ moonfish_wait(bot->out, "readyok");
+
+ if (battle->fen)
+ fprintf(bot->in, "position fen %s", battle->fen);
+ else
+ fprintf(bot->in, "position startpos");
+
+ if (battle->move_count > 0)
+ {+ fprintf(bot->in, " moves");
+ for (i = 0 ; i < battle->move_count ; i++)
+ fprintf(bot->in, " %s", battle->moves[i]);
+ }
+
+ fprintf(bot->in, "\n");
+
+ if (bot->ugi)
+ white = "p1",
+ black = "p2";
+ else
+ white = "w",
+ black = "b";
+
+ fprintf(bot->in, "go");
+ fprintf(bot->in, " %stime %ld", white, battle->white.time);
+ if (battle->white.increment > 0) fprintf(bot->in, " %sinc %ld", white, battle->white.increment);
+ fprintf(bot->in, " %stime %ld", black, battle->black.time);
+ if (battle->black.increment > 0) fprintf(bot->in, " %sinc %ld", black, battle->black.increment);
+ fprintf(bot->in, "\n");
+
+ arg = moonfish_wait(bot->out, "bestmove");
+ arg = strtok_r(arg, "\r\n\t ", &buffer);
+ if (arg == NULL)
+ {+ fprintf(stderr, "%s: invalid 'bestmove'\n", argv0);
+ exit(1);
+ }
+ arg = strdup(arg);
+ if (arg == NULL)
+ {+ perror(argv0);
+ exit(1);
+ }
+
+ battle->moves = realloc(battle->moves, (battle->move_count + 1) * sizeof *battle->moves);
+ if (battle->moves == NULL)
+ {+ perror(argv0);
+ exit(1);
+ }
+
+ battle->moves[battle->move_count++] = arg;
+ if (!battle->ugi)
+ {+ moonfish_from_uci(&battle->chess, &move, arg);
+ moonfish_play(&battle->chess, &move);
+ }
+
+ if (!bot->fixed_time)
+ {+ if (clock_gettime(CLOCK_MONOTONIC, &t1))
+ {+ perror(argv0);
+ exit(1);
+ }
+
+ bot->time += bot->increment;
+ bot->time -= (t1.tv_sec - t0.tv_sec) * 1000;
+ bot->time -= (t1.tv_nsec - t0.tv_nsec) / 1000000;
+ }
+
+ return arg;
+}
+
+static int moonfish_battle_play(char *argv0, struct moonfish_battle *battle)
+{+ int white;
+ char *arg;
+ char *buffer;
+
+ if (battle->ugi)
+ {+ fprintf(battle->white.in, "isready\n");
+ moonfish_wait(battle->white.out, "readyok");
+
+ fprintf(battle->white.in, "query result\n");
+ arg = moonfish_wait(battle->white.out, "response");
+ arg = strtok_r(arg, "\r\n\t ", &buffer);
+ if (arg == NULL)
+ {+ fprintf(stderr, "%s: invalid 'response'\n", argv0);
+ exit(1);
+ }
+
+ if (!strcmp(arg, "p1win"))
+ {+ printf("1-0\n");+ return 0;
+ }
+ if (!strcmp(arg, "p2win"))
+ {+ printf("0-1\n");+ return 0;
+ }
+ if (!strcmp(arg, "draw"))
+ {+ printf("1/2-1/2\n");+ return 0;
+ }
+
+ fprintf(battle->white.in, "isready\n");
+ moonfish_wait(battle->white.out, "readyok");
+
+ fprintf(battle->white.in, "query p1turn\n");
+ arg = moonfish_wait(battle->white.out, "response");
+ arg = strtok_r(arg, "\r\n\t ", &buffer);
+ if (arg == NULL)
+ {+ fprintf(stderr, "%s: invalid 'response'\n", argv0);
+ exit(1);
+ }
+
+ if (!strcmp(arg, "true"))
+ white = 1;
+ else
+ white = 0;
+ }
+ else
+ {+ white = battle->chess.white;
+ if (moonfish_finished(&battle->chess))
+ {+ if (moonfish_check(&battle->chess))
+ {+ if (white) printf("0-1\n");+ else printf("1-0\n");+ }
+ else
+ {+ printf("1/2-1/2\n");+ }
+
+ return 0;
+ }
+ }
+
+ if (white)
+ {+ arg = moonfish_bot_play(argv0, battle, &battle->white);
+ if (battle->white.time < -125)
+ {+ printf("0-1 {timeout}\n");+ return 0;
+ }
+ if (battle->white.time < 10) battle->white.time = 10;
+ }
+ else
+ {+ arg = moonfish_bot_play(argv0, battle, &battle->black);
+ if (battle->black.time < -125)
+ {+ printf("1-0 {timeout}\n");+ return 0;
+ }
+ if (battle->black.time < 10) battle->black.time = 10;
+ }
+
+ printf("%s ", arg);+
+ return 1;
+}
+
+int main(int argc, char **argv)
+{+ static struct moonfish_battle battle;
+
+ char *argv0;
+
+ if (argc < 1) return 1;
+
+ battle.fen = NULL;
+
+ battle.white.time = 15 * 60000;
+ battle.white.increment = 10000;
+ battle.white.fixed_time = 0;
+ battle.white.ugi = 0;
+
+ battle.black.time = 15 * 60000;
+ battle.black.increment = 10000;
+ battle.black.fixed_time = 0;
+ battle.black.ugi = 0;
+
+ argv0 = *argv++;
+ moonfish_battle_options(&battle, &argv);
+
+ moonfish_bot_arguments(argv0, &battle.white, &argv);
+ moonfish_bot_arguments(argv0, &battle.black, &argv);
+
+ if (*argv != NULL) moonfish_usage0(argv0);
+
+ battle.ugi = 0;
+ if (battle.white.ugi && battle.black.ugi)
+ battle.ugi = 1;
+
+ if (!battle.ugi)
+ {+ moonfish_chess(&battle.chess);
+ if (battle.fen && moonfish_fen(&battle.chess, battle.fen)) moonfish_usage0(argv0);
+ }
+
+ battle.moves = NULL;
+ battle.move_count = 0;
+
+ fprintf(stderr, "starting %s vs %s\n", battle.white.name, battle.black.name);
+
+ printf("[White \"%s\"]\n", battle.white.name);+ printf("[Black \"%s\"]\n", battle.black.name);+ if (battle.fen != NULL) printf("[FEN \"%s\"]\n", battle.fen);+ while (moonfish_battle_play(argv0, &battle)) { }+ printf("\n");+
+ fprintf(stderr, "finished %s vs %s\n", battle.white.name, battle.black.name);
+ return 0;
+}
--- /dev/null
+++ b/tools/ribbon.c
@@ -1,0 +1,229 @@
+/* moonfish is licensed under the AGPL (v3 or later) */
+/* copyright 2024 zamfofex */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+static void moonfish_usage0(char *argv0)
+{+ fprintf(stderr, "usage: %s <options>... <cmds>...\n", argv0);
+ fprintf(stderr, "options:\n");
+ fprintf(stderr, " --openings=<file-name> an opening book for the tournament (one FEN per line)\n");
+ fprintf(stderr, " --battle=<command> the command to make the two bots play each other\n");
+ fprintf(stderr, " --time=<time-control> default time control for each bot\n");
+ exit(1);
+}
+
+static char *moonfish_option(char *name, char ***argv)
+{+ size_t length;
+
+ if (**argv == NULL) return NULL;
+
+ if (!strcmp(**argv, name))
+ {+ (*argv)++;
+ return *(*argv)++;
+ }
+
+ length = strlen(name);
+ if (!strncmp(**argv, name, length) && (**argv)[length] == '=')
+ return *(*argv)++ + length + 1;
+
+ return NULL;
+}
+
+static void moonfish_options(char *argv0, FILE **openings, char **battle, char **time, char ***argv)
+{+ char *arg;
+
+ for (;;)
+ {+ arg = moonfish_option("--openings", argv);+ if (arg != NULL)
+ {+ *openings = fopen(arg, "r");
+ if (*openings == NULL)
+ {+ perror(argv0);
+ exit(1);
+ }
+
+ continue;
+ }
+
+ arg = moonfish_option("--battle", argv);+ if (arg != NULL)
+ {+ *battle = arg;
+ continue;
+ }
+
+ arg = moonfish_option("--time", argv);+ if (arg != NULL)
+ {+ *time = arg;
+ continue;
+ }
+
+ break;
+ }
+}
+
+static int moonfish_brackets(char *command)
+{+ int count, max_count;
+
+ max_count = 0;
+ count = 0;
+
+ for (;;)
+ {+ if (*command == ']')
+ {+ count++;
+ }
+ else
+ {+ if (count > max_count) max_count = count;
+ count = 0;
+ }
+
+ if (*command++ == '\0') break;
+ }
+
+ return max_count + 1;
+}
+
+int main(int argc, char **argv)
+{+ static char line[2048];
+
+ char *argv0;
+ FILE *openings;
+ int bot_count;
+ int brackets;
+ int i, j, k;
+ int opening_count;
+ char *battle, *time;
+ char *arg, *buffer;
+
+ if (argc < 1) return 1;
+
+ argv0 = *argv++;
+
+ openings = NULL;
+ battle = "moonfish-battle";
+ time = NULL;
+
+ moonfish_options(argv0, &openings, &battle, &time, &argv);
+
+ if (argv[0] != NULL)
+ {+ if (!strcmp(argv[0], "--"))
+ argv++;
+ else if (**argv == '-')
+ moonfish_usage0(argv0);
+ }
+
+ printf("# makefile generated by '%s'\n\n", argv0);+ printf(".PHONY: all\n\n");+ printf("n = basename '$@' .pgn | cut -d- -f\n");+
+ /* todo: support BSD Make somehow... */
+ /* maybe even POSIX Make */
+ printf("n1 = $($(shell $n1))\n");+ printf("n2 = $($(shell $n2))\n");+ printf("n3 = $($(shell $n3))\n\n");+
+ bot_count = 0;
+
+ for (;;)
+ {+ if (argv[bot_count] == NULL) break;
+
+ brackets = moonfish_brackets(argv[bot_count]);
+
+ printf("bot%d = ", bot_count + 1);+ for (i = 0 ; i < brackets ; i++) printf("[");+ printf(" ");+
+ if (time != NULL) printf("--time=%s ", time);+
+ printf("%s", argv[bot_count]);+
+ printf(" ");+ for (i = 0 ; i < brackets ; i++) printf("]");+ printf("\n");+
+ bot_count++;
+ }
+
+ if (bot_count == 0) fprintf(stderr, "warning: no bots established for tournament\n");
+
+ printf("\n");+
+ if (openings == NULL)
+ {+ printf("all:");+ for (i = 0 ; i < bot_count ; i++)
+ for (j = 0 ; j < bot_count ; j++)
+ {+ if (i == j) continue;
+ printf(" \\\n\tbot%d-bot%d.pgn", i + 1, j + 1);+ }
+ printf("\n\n");+
+ printf(".DEFAULT:\n");+ printf("\t@%s $(n1) $(n2) > $@.on\n", battle);+ printf("\t@mv $@.on $@\n");+
+ return 0;
+ }
+
+ opening_count = 0;
+
+ for (;;)
+ {+ errno = 0;
+ if (fgets(line, sizeof line, openings) == NULL)
+ {+ if (errno == 0) break;
+ perror(argv0);
+ return 1;
+ }
+
+ arg = line + strspn(line, "\r\n\t ");
+ if (arg[0] == '#') continue;
+
+ arg = strtok_r(arg, "\r\n#", &buffer);
+ if (arg == NULL) continue;
+
+ printf("opening%d = ", opening_count + 1);+ printf("%s\n", arg);+
+ opening_count++;
+ }
+
+ if (opening_count == 0) fprintf(stderr, "warning: empty opening book\n");
+
+ printf("\n");+
+ printf("all:");+ for (i = 0 ; i < bot_count ; i++)
+ for (j = 0 ; j < bot_count ; j++)
+ {+ if (i == j) continue;
+ for (k = 0 ; k < opening_count ; k++)
+ printf(" \\\n\topening%d-bot%d-bot%d.pgn", k + 1, i + 1, j + 1);+ }
+ printf("\n\n");+
+ printf(".DEFAULT:\n");+ printf("\t@%s --fen='$(n1)' $(n2) $(n3) > $@.on\n", battle);+ printf("\t@mv $@.on $@\n");+
+ return 0;
+}
--
⑨