shithub: moonfish

Download patch

ref: 99d918f5c373356844f36e0fd123f5b78acf2bb0
parent: 06a96d4d98f4308bf512bdb7fa6d87a29fbce4a9
author: zamfofex <zamfofex@twdb.moe>
date: Wed Jan 24 20:37:54 EST 2024

improve argument parsing for tools

--- a/README.md
+++ b/README.md
@@ -80,30 +80,30 @@
 
 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!
 
-To play against a UCI bot, use `./play` followed by a time control, then the command of whichever bot you want to play against. The color of your pieces will be decided randomly.
+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.
 
 ~~~
 # (start a game against Stockfish)
-./play 1+0 stockfish
+./play stockfish
 
 # (start a game against Leela)
-./play 5+2 lc0
+./play lc0
 
 # (start a game against moonfish)
-./play 15+10 ./moonfish
+./play ./moonfish
 ~~~
 
-To analyse a game with an UCI bot, use `./analyse` followed by a FEN string (or `initial` for the starting position) in a single argument (use shell quotation marks if necessary) followed by the command of whichever bot you want to use for analysis. (Though note that moonfish currently does not have analysis capabilities.)
+To analyse a game with an UCI bot, use `./analyse` followed by the command of whichever bot you want to use for analysis. (Though note that moonfish currently does not have analysis capabilities.)
 
 ~~~
 # (analyse a game using Stockfish)
-./analyse initial stockfish
+./analyse stockfish
 
 # (analyse a game using Leela)
-./analyse initial lc0
+./analyse lc0
 
 # (analyse a game using Leela, showing win/draw/loss evaluation)
-./analyse initial lc0 --show-wdl
+./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.
--- a/tools/analyse.c
+++ b/tools/analyse.c
@@ -642,6 +642,12 @@
 {
 	static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
 	static pthread_mutex_t read_mutex = PTHREAD_MUTEX_INITIALIZER;
+	static char *format = "<cmd> <args>...";
+	static struct moonfish_arg args[] =
+	{
+		{"F", "fen", "<FEN>", NULL, "the position to analyse"},
+		{NULL, NULL, NULL, NULL, NULL},
+	};
 	
 	struct moonfish_fancy *fancy;
 	pthread_t thread;
@@ -651,12 +657,12 @@
 	int x1, y1;
 	struct moonfish_move move;
 	int error;
+	char **command;
+	int command_count;
 	
-	if (argc < 3)
-	{
-		if (argc > 0) fprintf(stderr, "usage: %s <FEN> <command> <args>...\n", argv[0]);
-		return 1;
-	}
+	command = moonfish_args(args, format, argc, argv);
+	command_count = argc - (command - argv);
+	if (command_count < 1) moonfish_usage(args, format, argv[0]);
 	
 	if (tcgetattr(0, &moonfish_termios))
 	{
@@ -706,7 +712,7 @@
 	fancy->x = 0;
 	fancy->y = 0;
 	
-	moonfish_spawn(argv[0], argv + 2, &fancy->in, &fancy->out);
+	moonfish_spawn(argv[0], command, &fancy->in, &fancy->out);
 	
 	fancy->i = 0;
 	fancy->count = 1;
@@ -721,14 +727,14 @@
 	fancy->plies[0].score = 0;
 	
 	moonfish_chess(&fancy->plies[0].chess);
-	if (!strcmp(argv[1], "initial"))
+	if (args[0].value == NULL)
 	{
 		fancy->fen = NULL;
 	}
 	else
 	{
-		fancy->fen = argv[1];
-		moonfish_fen(&fancy->plies[0].chess, argv[1]);
+		fancy->fen = args[0].value;
+		moonfish_fen(&fancy->plies[0].chess, fancy->fen);
 	}
 	
 	fprintf(fancy->in, "uci\n");
--- a/tools/lichess.c
+++ b/tools/lichess.c
@@ -909,8 +909,14 @@
 int main(int argc, char **argv)
 {
 	static unsigned char buffer[BR_SSL_BUFSIZE_BIDI];
+	static char *format = "<cmd> <args>...";
+	static struct moonfish_arg args[] =
+	{
+		{"N", "host", "<name>", "lichess.org", "Lichess' host name (default: 'lichess.org')"},
+		{"P", "port", "<port>", "443", "Lichess' port (default: '443')"},
+		{NULL, NULL, NULL, NULL, NULL},
+	};
 	
-	char *name, *port;
 	char *token;
 	br_ssl_client_context ctx;
 	br_sslio_context io_ctx;
@@ -919,22 +925,12 @@
 	int i;
 	int error;
 	char **command;
+	int command_count;
 	
-	if (argc < 1) return 1;
+	command = moonfish_args(args, format, argc, argv);
+	command_count = argc - (command - argv);
+	if (command_count < 1) moonfish_usage(args, format, argv[0]);
 	
-	if (argc < 3)
-	{
-		if (argc > 0)
-		{
-			fprintf(stderr, "usage: %s [<host-name>] [<host-port>] [--] <command> <args>...\n", argv[0]);
-			fprintf(stderr, "note: '--' is only optional when both '<host-name>' and '<host-port>' are specified\n");
-		}
-		return 1;
-	}
-	
-	name = "lichess.org";
-	port = "443";
-	
 	token = getenv("lichess_token");
 	if (token == NULL || token[0] == 0)
 	{
@@ -951,30 +947,9 @@
 		}
 	}
 	
-	if (!strcmp(argv[1], "--"))
-	{
-		command = argv + 2;
-	}
-	else
-	{
-		name = argv[1];
-		if (!strcmp(argv[2], "--"))
-		{
-			command = argv + 3;
-		}
-		else
-		{
-			name = argv[1];
-			if (!strcmp(argv[3], "--"))
-				command = argv + 4;
-			else
-				command = argv + 3;
-		}
-	}
-	
 	if (signal(SIGCHLD, SIG_IGN) == SIG_ERR) return 1;
 	
-	moonfish_request(&ctx, &io_ctx, &min_ctx, buffer, sizeof buffer, argv[0], name, port, token, "GET /api/stream/event", "", 0, &fd);
+	moonfish_request(&ctx, &io_ctx, &min_ctx, buffer, sizeof buffer, argv[0], args[0].value, args[1].value, token, "GET /api/stream/event", "", 0, &fd);
 	br_sslio_flush(&io_ctx);
 	if (moonfish_response(&ctx.eng, &io_ctx, argv[0]))
 	{
@@ -982,7 +957,7 @@
 		return 1;
 	}
 	
-	moonfish_handle_events(&ctx.eng, &io_ctx, argv[0], name, port, token, command, moonfish_username(argv[0], name, port, token));
+	moonfish_handle_events(&ctx.eng, &io_ctx, argv[0], args[0].value, args[1].value, token, command, moonfish_username(argv[0], args[0].value, args[1].value, token));
 	
 	br_ssl_engine_close(&ctx.eng);
 	close(fd);
--- a/tools/play.c
+++ b/tools/play.c
@@ -311,6 +311,14 @@
 {
 	static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
 	static char names[4096] = "";
+	static char *format = "<cmd> <args>...";
+	static struct moonfish_arg args[] =
+	{
+		{"F", "fen", "<FEN>", NULL, "starting position for the game"},
+		{"T", "time", "<time-control>", "15+10", "time control in minutes with increment in seconds (default: '15+10')"},
+		{"C", "color", "<color>", "white", "which color you are going to play as"},
+		{NULL, NULL, NULL, NULL, NULL},
+	};
 	
 	FILE *in, *out;
 	struct moonfish_fancy *fancy;
@@ -324,17 +332,17 @@
 	char *name;
 	struct moonfish_move move;
 	int error;
+	char **command;
+	int command_count;
 	
+	command = moonfish_args(args, format, argc, argv);
+	command_count = argc - (command - argv);
+	if (command_count < 1) moonfish_usage(args, format, argv[0]);
+	
 	name = names;
 	
-	if (argc < 3)
-	{
-		if (argc > 0) fprintf(stderr, "usage: %s <time-control> <command> <args>...\n", argv[0]);
-		return 1;
-	}
+	moonfish_spawn(argv[0], command, &in, &out);
 	
-	moonfish_spawn(argv[0], argv + 2, &in, &out);
-	
 	if (tcgetattr(0, &moonfish_termios))
 	{
 		perror(argv[0]);
@@ -354,7 +362,6 @@
 	if (sigaction(SIGTERM, &action, NULL) || sigaction(SIGINT, &action, NULL) || sigaction(SIGQUIT, &action, NULL))
 	{
 		perror(argv[0]);
-		moonfish_exit();
 		return 1;
 	}
 	
@@ -376,18 +383,44 @@
 	
 	fancy->argv0 = argv[0];
 	fancy->mutex = &mutex;
-	fancy->white = time(NULL) % 2;
 	
+	moonfish_chess(&fancy->chess);
+	if (args[0].value != NULL) moonfish_fen(&fancy->chess, args[0].value);
+	
+	if (args[2].value != NULL)
+	{
+		if (!strcmp(args[2].value, "white"))
+		{
+			fancy->white = 1;
+		}
+		else if (!strcmp(args[2].value, "black"))
+		{
+			fancy->white = 0;
+		}
+		else
+		{
+			fprintf(stderr, "%s: unknown color '%s'\n", argv[0], args[2].value);
+			return 1;
+		}
+	}
+	else
+	{
+		if (args[0].value == NULL)
+			fancy->white = time(NULL) % 2;
+		else
+			fancy->white = fancy->chess.white;
+	}
+	
 	fancy->x = 0;
 	fancy->y = 0;
 	
-	fancy->their_name = argv[2];
+	fancy->their_name = command[0];
 	fancy->our_name = getlogin();
 	if (fancy->our_name == NULL) fancy->our_name = "you";
 	
-	if (sscanf(argv[1], "%d+%d", &fancy->our_time, &fancy->increment) != 2)
+	if (sscanf(args[1].value, "%d+%d", &fancy->our_time, &fancy->increment) != 2)
 	{
-		fprintf(stderr, "%s: unknown time control '%s'\n", argv[0], argv[1]);
+		fprintf(stderr, "%s: unknown time control '%s'\n", argv[0], args[1].value);
 		return 1;
 	}
 	
@@ -449,8 +482,6 @@
 	
 	if (scanf("[%d;%dR", &oy, &ox) != 2) return 1;
 	oy -= 11;
-	
-	moonfish_chess(&fancy->chess);
 	
 	error = pthread_create(&thread, NULL, &moonfish_start, fancy);
 	if (error != 0)
--- a/tools/tools.h
+++ b/tools/tools.h
@@ -6,8 +6,19 @@
 
 #include <stdio.h>
 
+struct moonfish_arg
+{
+	char *letter;
+	char *name;
+	char *format;
+	char *value;
+	char *description;
+};
+
 void moonfish_spawn(char *argv0, char **argv, FILE **in, FILE **out);
 char *moonfish_next(FILE *file);
 char *moonfish_wait(FILE *file, char *name);
+char **moonfish_args(struct moonfish_arg *args, char *rest_format, int argc, char **argv);
+void moonfish_usage(struct moonfish_arg *args, char *rest_format, char *argv0);
 
 #endif
--- a/tools/utils.c
+++ b/tools/utils.c
@@ -114,3 +114,207 @@
 			return strtok(NULL, "\r\n\t ");
 	}
 }
+
+static void moonfish_usage_to(struct moonfish_arg *args, char *rest_format, char *argv0, FILE *out)
+{
+	int i;
+	int col1, col2, n;
+	
+	if (argv0 == NULL) argv0 = "<program>";
+	if (rest_format == NULL) rest_format = "<args>...";
+	
+	col1 = 0;
+	col2 = 0;
+	
+	for (i = 0 ; args[i].letter != NULL || args[i].name != NULL ; i++)
+	{
+		n = 0;
+		if (args[i].letter != NULL)
+		{
+			n += 1 + strlen(args[i].letter);
+			if (args[i].format != NULL) n += strlen(args[i].format);
+		}
+		if (n > col1) col1 = n;
+		
+		n = 0;
+		if (args[i].name != NULL)
+		{
+			n += 2 + strlen(args[i].name);
+			if (args[i].format != NULL) n += 1 + strlen(args[i].format);
+		}
+		if (n > col2) col2 = n;
+	}
+	
+	fprintf(out, "usage: %s <options>... [--] %s\n", argv0, rest_format);
+	fprintf(out, "options:\n");
+	
+	for (i = 0 ; args[i].letter != NULL || args[i].name != NULL ; i++)
+	{
+		fprintf(out, "   ");
+		
+		n = 0;
+		if (args[i].letter != NULL)
+		{
+			n += 1 + strlen(args[i].letter);
+			fprintf(out, "-%s", args[i].letter);
+			if (args[i].format != NULL)
+			{
+				n += strlen(args[i].format);
+				fprintf(out, "\x1B[36m%s\x1B[0m", args[i].format);
+			}
+		}
+		
+		if (args[i].letter != NULL && args[i].name != NULL) fprintf(out, ", ");
+		else fprintf(out, "  ");
+		
+		while (n < col1)
+		{
+			fprintf(out, " ");
+			n++;
+		}
+		
+		n = 0;
+		if (args[i].name != NULL)
+		{
+			n += 2 + strlen(args[i].name);
+			fprintf(out, "--%s", args[i].name);
+			if (args[i].format != NULL)
+			{
+				n += 1 + strlen(args[i].format);
+				fprintf(out, "=\x1B[36m%s\x1B[0m", args[i].format);
+			}
+		}
+		
+		if (args[i].description != NULL)
+		{
+			while (n < col2)
+			{
+				fprintf(out, " ");
+				n++;
+			}
+			
+			fprintf(out, "   %s", args[i].description);
+		}
+		
+		fprintf(out, "\n");
+	}
+}
+
+static int moonfish_letter_arg(struct moonfish_arg *args, char *arg, int *argc, char ***argv)
+{
+	int i;
+	char *name;
+	size_t length;
+	
+	for (i = 0 ; args[i].letter != NULL || args[i].name != NULL ; i++)
+	{
+		name = args[i].letter;
+		if (name == NULL) continue;
+		length = strlen(name);
+		if (strncmp(arg, name, length)) continue;
+		
+		if (args[i].format == NULL)
+		{
+			arg += length;
+			if (arg[0] == 0) return 0;
+			args[i].value = "";
+			continue;
+		}
+		else
+		{
+			args[i].value = arg + length;
+			if (arg[length] == 0)
+			{
+				if (*argc <= 0) return 1;
+				(*argc)--;
+				(*argv)++;
+				args[i].value = (*argv)[0];
+				return 0;
+			}
+			if (arg[length] == '=')
+			{
+				args[i].value = arg + length + 1;
+				return 0;
+			}
+		}
+	}
+	
+	return 1;
+}
+
+static int moonfish_name_arg(struct moonfish_arg *args, char *arg, int *argc, char ***argv)
+{
+	int i;
+	char *name;
+	size_t length;
+	
+	for (i = 0 ; args[i].letter != NULL || args[i].name != NULL ; i++)
+	{
+		name = args[i].name;
+		if (name == NULL) continue;
+		length = strlen(name);
+		if (strncmp(arg, name, length)) continue;
+		
+		if (args[i].format == NULL)
+		{
+			if (arg[length] == 0) return 0;
+			if (arg[length] == '=') return 1;
+		}
+		else
+		{
+			if (arg[length] == 0)
+			{
+				if (*argc <= 0) return 1;
+				(*argc)--;
+				(*argv)++;
+				args[i].value = (*argv)[0];
+				return 0;
+			}
+			if (arg[length] == '=')
+			{
+				args[i].value = arg + length + 1;
+				return 0;
+			}
+		}
+	}
+	
+	return 1;
+}
+
+char **moonfish_args(struct moonfish_arg *args, char *rest_format, int argc, char **argv)
+{
+	char *arg, *argv0;
+	
+	if (argc <= 0) return argv;
+	argv0 = argv[0];
+	
+	for (;;)
+	{
+		argc--;
+		argv++;
+		if (argc <= 0) return argv;
+		
+		arg = *argv;
+		if (!strcmp(arg, "-")) return argv;
+		if (arg[0] != '-') return argv;
+		while (arg[0] == '-') arg++;
+		if (arg[0] == 0) return argv + 1;
+		
+		if (!strcmp(arg, "help") || !strcmp(arg, "h") || !strcmp(arg, "H"))
+		{
+			moonfish_usage_to(args, rest_format, argv0, stdout);
+			exit(0);
+		}
+		
+		if (moonfish_letter_arg(args, arg, &argc, &argv) == 0) continue;
+		if (moonfish_name_arg(args, arg, &argc, &argv) == 0) continue;
+		
+		moonfish_usage(args, rest_format, argv0);
+	}
+}
+
+void moonfish_usage(struct moonfish_arg *args, char *rest_format, char *argv0)
+{
+	moonfish_usage_to(args, rest_format, argv0, stderr);
+	exit(1);
+}
--