shithub: moonfish

Download patch

ref: 5b14f83c1561de5a0c35657eb8c613675cbadaef
parent: ba0b9247668c22e10927c3918e3500a5ba0344a2
author: zamfofex <zamfofex@twdb.moe>
date: Sat Apr 6 12:27:34 EDT 2024

add IRC bot

--- a/.gitignore
+++ b/.gitignore
@@ -23,6 +23,7 @@
 !/tools/lichess.c
 !/tools/battle.c
 !/tools/ribbon.c
+!/tools/chat.c
 !/tools/ugi.c
 !/tools/ugi-uci.c
 !/tools/uci-ugi.c
--- a/chess.c
+++ b/chess.c
@@ -513,8 +513,52 @@
 	}
 }
 
+int moonfish_validate(struct moonfish_chess *chess)
+{
+	int x, y;
+	struct moonfish_move moves[32];
+	struct moonfish_move *move;
+	
+	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++)
+			if (move->captured % 16 == moonfish_king)
+				return 0;
+	}
+	
+	return 1;
+}
+
+int moonfish_check(struct moonfish_chess *chess)
+{
+	int valid;
+	struct moonfish_castle castle;
+	unsigned char passing;
+	
+	castle = chess->castle;
+	passing = chess->passing;
+	
+	chess->castle.white_oo = 0;
+	chess->castle.white_ooo = 0;
+	chess->castle.black_oo = 0;
+	chess->castle.black_ooo = 0;
+	
+	chess->white ^= 1;
+	valid = moonfish_validate(chess);
+	chess->white ^= 1;
+	
+	chess->castle = castle;
+	chess->passing = passing;
+	
+	return valid ^ 1;
+}
+
 #ifndef moonfish_mini
 
+#include <string.h>
+
 int moonfish_fen(struct moonfish_chess *chess, char *fen)
 {
 	int x, y;
@@ -606,13 +650,11 @@
 	return 0;
 }
 
-#endif
-
-int moonfish_validate(struct moonfish_chess *chess)
+int moonfish_finished(struct moonfish_chess *chess)
 {
+	struct moonfish_move moves[32], *move;
 	int x, y;
-	struct moonfish_move moves[32];
-	struct moonfish_move *move;
+	int valid;
 	
 	for (y = 0 ; y < 8 ; y++)
 	for (x = 0 ; x < 8 ; x++)
@@ -619,71 +661,332 @@
 	{
 		moonfish_moves(chess, moves, (x + 1) + (y + 2) * 10);
 		for (move = moves ; move->piece != moonfish_outside ; move++)
-			if (move->captured % 16 == moonfish_king)
-				return 0;
+		{
+			moonfish_play(chess, move);
+			valid = moonfish_validate(chess);
+			moonfish_unplay(chess, move);
+			if (valid) return 0;
+		}
 	}
 	
 	return 1;
 }
 
-int moonfish_check(struct moonfish_chess *chess)
+int moonfish_checkmate(struct moonfish_chess *chess)
 {
-	int valid;
-	struct moonfish_castle castle;
-	unsigned char passing;
-	
-	castle = chess->castle;
-	passing = chess->passing;
-	
-	chess->castle.white_oo = 0;
-	chess->castle.white_ooo = 0;
-	chess->castle.black_oo = 0;
-	chess->castle.black_ooo = 0;
-	
-	chess->white ^= 1;
-	valid = moonfish_validate(chess);
-	chess->white ^= 1;
-	
-	chess->castle = castle;
-	chess->passing = passing;
-	
-	return valid ^ 1;
+	if (!moonfish_check(chess)) return 0;
+	return moonfish_finished(chess);
 }
 
-#ifndef moonfish_mini
+int moonfish_stalemate(struct moonfish_chess *chess)
+{
+	if (moonfish_check(chess)) return 0;
+	return moonfish_finished(chess);
+}
 
-int moonfish_finished(struct moonfish_chess *chess)
+static int moonfish_match_move(struct moonfish_chess *chess, struct moonfish_move *result_move, unsigned char type, unsigned char promotion, int x0, int y0, int x1, int y1, int check, int captured)
 {
-	struct moonfish_move moves[32], *move;
+	int found;
 	int x, y;
+	struct moonfish_move moves[32];
+	struct moonfish_move *move;
+	int xi0, yi0, xi1, yi1;
 	int valid;
 	
-	for (y = 0 ; y < 8 ; y++)
-	for (x = 0 ; x < 8 ; x++)
+	xi0 = 0, xi1 = 8;
+	yi0 = 0, yi1 = 8;
+	
+	if (x0) xi0 = x0 - 1, xi1 = x0;
+	if (y0) yi0 = y0 - 1, yi1 = y0;
+	
+	found = 0;
+	
+	for (y = yi0 ; y < yi1 ; y++)
+	for (x = xi0 ; x < xi1 ; x++)
 	{
 		moonfish_moves(chess, moves, (x + 1) + (y + 2) * 10);
 		for (move = moves ; move->piece != moonfish_outside ; move++)
 		{
+			if (move->piece % 16 != type) continue;
+			if (captured && move->captured == moonfish_empty) continue;
+			if (promotion && promotion != move->promotion % 16) continue;
+			if (move->to % 10 != x1) continue;
+			if (move->to / 10 - 1 != y1) continue;
+			
 			moonfish_play(chess, move);
 			valid = moonfish_validate(chess);
+			if (valid && check) if (!moonfish_check(chess)) valid = 0;
+			if (valid && check == 2) if (!moonfish_checkmate(chess)) valid = 0;
 			moonfish_unplay(chess, move);
-			if (valid) return 0;
+			if (!valid) continue;
+			if (found) continue;
+			found = 1;
+			*result_move = *move;
 		}
 	}
 	
-	return 1;
+	if (!found) return 1;
+	return 0;
 }
 
-int moonfish_checkmate(struct moonfish_chess *chess)
+int moonfish_from_san(struct moonfish_chess *chess, struct moonfish_move *move, char *name)
 {
-	if (!moonfish_check(chess)) return 0;
-	return moonfish_finished(chess);
+	int count;
+	unsigned char type;
+	unsigned char promotion;
+	int check, capture;
+	int x0, y0;
+	int x1, y1;
+	char *name0, name_array[32], ch;
+	size_t length, i;
+	
+	length = strlen(name);
+	if (length >= sizeof name_array) return 1;
+	strcpy(name_array, name);
+	name = name_array;
+	
+	/* reverse the string (because it is easier to parse) */
+	for (i = length / 2 ; i < length ; i++)
+	{
+		ch = name[i];
+		name[i] = name[length - i - 1];
+		name[length - i - 1] = ch;
+	}
+	
+	check = 0;
+	if (*name == '+') check = 2;
+	else if (*name == '+') check = 1;
+	if (check) name++;
+	
+	count = 0;
+	for (name0 = name ; *name0 != 0 ; name0++)
+	{
+		if (*name0 == '-') continue;
+		if (*name0 == '_') continue;
+		if (*name0 == '0') { count++; continue; }
+		if (*name0 == 'O') { count++; continue; }
+		if (*name0 == 'o') { count++; continue; }
+		count = 0;
+		break;
+	}
+	
+	if (count > 0)
+	{
+		if (chess->white) y1 = 1;
+		else y1 = 8;
+		
+		if (count == 2) x1 = 7;
+		else if (count == 3) x1 = 3;
+		else return 1;
+		
+		return moonfish_match_move(chess, move, moonfish_king, 0, 5, y1, x1, y1, check, 0);
+	}
+	
+	x0 = 0;
+	y0 = 0;
+	x1 = 0;
+	y1 = 0;
+	check = 0;
+	
+	switch (*name++)
+	{
+	default:
+		promotion = 0;
+		name--;
+		break;
+	case 'K': case 'k':
+		promotion = moonfish_king;
+		break;
+	case 'Q': case 'q':
+		promotion = moonfish_queen;
+		break;
+	case 'R': case 'r':
+		promotion = moonfish_rook;
+		break;
+	case 'B': case 'b':
+		promotion = moonfish_bishop;
+		break;
+	case 'N': case 'n':
+		promotion = moonfish_knight;
+		break;
+	}
+	
+	if (promotion != 0 && *name == '=') name++;
+	
+	capture = 0;
+	if (*name >= '1' && *name <= '8') y1 = *name++ - '0';
+	if (*name >= 'a' && *name <= 'h') x1 = *name++ - 'a' + 1;
+	if (*name == 'x') capture = 1, name++;
+	if (*name >= '1' && *name <= '8') y0 = *name++ - '0';
+	if (*name >= 'a' && *name <= 'h') x0 = *name++ - 'a' + 1;
+	
+	if (x1 == 0) return 1;
+	if (y1 == 0) return 1;
+	
+	switch (*name++)
+	{
+	default:
+		type = moonfish_pawn;
+		if (x0 && y0) type = chess->board[x0 + (y0 + 1) * 10] % 16;
+		if (type == 0x0F) return 1;
+		name--;
+		break;
+	case 'K': case 'k':
+		type = moonfish_king;
+		break;
+	case 'Q': case 'q':
+		type = moonfish_queen;
+		break;
+	case 'R': case 'r':
+		type = moonfish_rook;
+		break;
+	case 'B': /* no lowercase here */
+		type = moonfish_bishop;
+		break;
+	case 'N': case 'n':
+		type = moonfish_knight;
+		break;
+	}
+	
+	if (*name != 0) return 1;
+	
+	return moonfish_match_move(chess, move, type, promotion, x0, y0, x1, y1, check, capture);
 }
 
-int moonfish_stalemate(struct moonfish_chess *chess)
+/* todo: improve this */
+void moonfish_to_fen(struct moonfish_chess *chess, char *fen)
 {
-	if (moonfish_check(chess)) return 0;
-	return moonfish_finished(chess);
+	int x, y;
+	unsigned char piece;
+	
+	for (y = 7 ; y >= 0 ; y--)
+	{
+		for (x = 0 ; x < 8 ; x++)
+		{
+			piece = chess->board[(x + 1) + (y + 2) * 10];
+			if (piece == moonfish_empty)
+			{
+				*fen++ = '1';
+				continue;
+			}
+			
+			switch (piece % 16)
+			{
+			default:
+				return;
+			case moonfish_pawn:
+				*fen = 'p';
+				break;
+			case moonfish_knight:
+				*fen = 'n';
+				break;
+			case moonfish_bishop:
+				*fen = 'b';
+				break;
+			case moonfish_rook:
+				*fen = 'r';
+				break;
+			case moonfish_queen:
+				*fen = 'q';
+				break;
+			case moonfish_king:
+				*fen = 'k';
+				break;
+			}
+			
+			if (piece / 16 == 1) *fen += 'A' - 'a';
+			fen++;
+		}
+		
+		*fen++ = '/';
+	}
+	
+	fen[-1] = 0;
+}
+
+void moonfish_to_san(struct moonfish_chess *chess, char *name, struct moonfish_move *move)
+{
+	static char names[] = "NBRQK";
+	
+	int x, y;
+	struct moonfish_move moves[32];
+	struct moonfish_move *other_move;
+	char file_ambiguity, rank_ambiguity, ambiguity;
+	int to_x, to_y;
+	int from_x, from_y;
+	
+	from_x = move->from % 10 - 1;
+	from_y = move->from / 10 - 2;
+	
+	to_x = move->to % 10 - 1;
+	to_y = move->to / 10 - 2;
+	
+	if (move->piece % 16 == moonfish_pawn)
+	{
+		if (from_x != to_x)
+		{
+			*name++ = from_x + 'a';
+			*name++ = 'x';
+		}
+		
+		*name++ = to_x + 'a';
+		*name++ = to_y + '1';
+		
+		if (move->promotion % 16 != moonfish_pawn)
+		{
+			*name++ = '=';
+			*name++ = names[move->promotion % 16 - 2];
+		}
+		
+		*name = 0;
+		
+		return;
+	}
+	
+	file_ambiguity = 0;
+	rank_ambiguity = 0;
+	ambiguity = 0;
+	
+	for (y = 0 ; y < 8 ; y++)
+	for (x = 0 ; x < 8 ; x++)
+	{
+		if ((x + 1) + (y + 2) * 10 == move->from) continue;
+		moonfish_moves(chess, moves, (x + 1) + (y + 2) * 10);
+		
+		for (other_move = moves ; other_move->piece != moonfish_outside ; other_move++)
+		{
+			if (other_move->to != move->to) continue;
+			if (other_move->piece != move->piece) continue;
+			
+			ambiguity = 1;
+			if (other_move->from % 10 - 1 == from_x) file_ambiguity = 1;
+			if (other_move->from / 10 - 2 == from_y) rank_ambiguity = 1;
+		}
+	}
+	
+	*name++ = names[(move->piece & 0xF) - 2];
+	
+	if (ambiguity)
+	{
+		if (file_ambiguity)
+		{
+			if (rank_ambiguity)
+				*name++ = from_x + 'a';
+			*name++ = from_y + '1';
+		}
+		else
+		{
+			*name++ = from_x + 'a';
+		}
+	}
+	
+	if (move->captured != moonfish_empty)
+		*name++ = 'x';
+		
+	*name++ = to_x + 'a';
+	*name++ = to_y + '1';
+	
+	*name = 0;
 }
 
 #endif
--- a/main.c
+++ b/main.c
@@ -23,6 +23,7 @@
 	char *end;
 #ifndef moonfish_mini
 	long int long_depth;
+	long int time;
 #endif
 	
 	if (argc > 1)
@@ -56,6 +57,9 @@
 			wtime = -1;
 			btime = -1;
 			depth = -1;
+#ifndef moonfish_mini
+			time = -1;
+#endif
 			
 			for (;;)
 			{
@@ -113,6 +117,29 @@
 					
 					depth = long_depth;
 				}
+				else if (!strcmp(arg, "movetime"))
+				{
+					arg = strtok(NULL, "\r\n\t ");
+					
+					if (arg == NULL)
+					{
+						fprintf(stderr, "%s: malformed 'go movetime' command\n", argv[0]);
+						return 1;
+					}
+					
+					errno = 0;
+					time = strtol(arg, &end, 10);
+					if (errno != 0)
+					{
+						perror(argv[0]);
+						return 1;
+					}
+					if (*end != 0 || time < 0)
+					{
+						fprintf(stderr, "%s: malformed move time in 'go' command\n", argv[0]);
+						return 1;
+					}
+				}
 #endif
 			}
 			
@@ -119,16 +146,17 @@
 			if (wtime < 0) wtime = 0;
 			if (btime < 0) btime = 0;
 			
+#ifndef moonfish_mini
 			if (depth >= 0)
-#ifdef moonfish_mini
-				return 1;
-#else
 				score = moonfish_best_move_depth(analysis, &move, depth);
+			else if (time >= 0)
+				score = moonfish_best_move_time(analysis, &move, &depth, time);
+			else
 #endif
-			else if (chess.white)
-				score = moonfish_best_move_time(analysis, &move, &depth, wtime, btime);
+			if (chess.white)
+				score = moonfish_best_move_clock(analysis, &move, &depth, wtime, btime);
 			else
-				score = moonfish_best_move_time(analysis, &move, &depth, btime, wtime);
+				score = moonfish_best_move_clock(analysis, &move, &depth, btime, wtime);
 			
 			printf("info depth %d ", depth);
 			if (score >= moonfish_omega || score <= -moonfish_omega)
--- a/makefile
+++ b/makefile
@@ -15,7 +15,7 @@
 
 .PHONY: all clean install uninstall
 
-all: moonfish play lichess analyse battle ribbon uci-ugi ugi-uci
+all: moonfish play lichess analyse battle ribbon chat uci-ugi ugi-uci
 
 moonfish moonfish.exe moonfish.wasm: moonfish.h chess.c search.c main.c
 	$(moonfish_cc) -o $@ $(filter %.c,$^)
@@ -26,6 +26,9 @@
 lichess: $(tools_src) tools/lichess.c tools/https.c
 	$(tools_cc) -o $@ $(filter %.c,$^) -ltls -lssl -lcrypto -lcjson
 
+chat: $(tools_src) tools/chat.c tools/https.c
+	$(tools_cc) -o $@ $(filter %.c,$^) -ltls -lssl -lcrypto
+
 ugi-uci: $(ugi_src)
 	$(tools_cc) -o $@ $(filter %.c,$^)
 
@@ -42,6 +45,7 @@
 	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 chat $(BINDIR)/moonfish-chat
 	install -D -m 755 ugi-uci $(BINDIR)/ugi-uci
 	install -D -m 755 uci-ugi $(BINDIR)/uci-ugi
 
--- a/moonfish.h
+++ b/moonfish.h
@@ -69,7 +69,8 @@
 void moonfish_play(struct moonfish_chess *chess, struct moonfish_move *move);
 void moonfish_unplay(struct moonfish_chess *chess, struct moonfish_move *move);
 
-int moonfish_best_move_time(struct moonfish_analysis *analysis, struct moonfish_move *move, int *depth, long int our_time, long int their_time);
+int moonfish_best_move_time(struct moonfish_analysis *analysis, struct moonfish_move *move, int *depth, long int time);
+int moonfish_best_move_clock(struct moonfish_analysis *analysis, struct moonfish_move *move, int *depth, long int our_time, long int their_time);
 int moonfish_countdown(int score);
 
 struct moonfish_analysis *moonfish_analysis(char *argv0);
@@ -87,6 +88,9 @@
 
 int moonfish_fen(struct moonfish_chess *chess, char *fen);
 int moonfish_best_move_depth(struct moonfish_analysis *analysis, struct moonfish_move *move, int depth);
+int moonfish_from_san(struct moonfish_chess *chess, struct moonfish_move *move, char *name);
+void moonfish_to_san(struct moonfish_chess *chess, char *name, struct moonfish_move *move);
+void moonfish_to_fen(struct moonfish_chess *chess, char *fen);
 
 int moonfish_finished(struct moonfish_chess *chess);
 int moonfish_checkmate(struct moonfish_chess *chess);
--- a/search.c
+++ b/search.c
@@ -347,18 +347,14 @@
 
 #endif
 
-int moonfish_best_move_time(struct moonfish_analysis *analysis, struct moonfish_move *best_move, int *depth, long int our_time, long int their_time)
+int moonfish_best_move_time(struct moonfish_analysis *analysis, struct moonfish_move *best_move, int *depth, long int time)
 {
-	long int d, t, t0, t1;
+	long int t, t0, t1;
 	int r;
 	
 	r = 24 * 2048;
 	t = -1;
 	
-	d = our_time - their_time;
-	if (d < 0) d = 0;
-	d += our_time / 8;
-	
 	for (;;)
 	{
 		t0 = moonfish_clock(analysis);
@@ -368,8 +364,8 @@
 		if (t >= 0) r = (t1 - t0) * 2048 / (t + 1);
 		t = t1 - t0;
 		
-		d -= t;
-		if (t * r > d * 2048) break;
+		time -= t;
+		if (t * r > time * 2048) break;
 		
 		analysis->depth++;
 	}
@@ -376,4 +372,13 @@
 	
 	*depth = analysis->depth;
 	return analysis->score;
+}
+
+int moonfish_best_move_clock(struct moonfish_analysis *analysis, struct moonfish_move *best_move, int *depth, long int our_time, long int their_time)
+{
+	long int time;
+	time = our_time - their_time;
+	if (time < 0) time = 0;
+	time += our_time / 8;
+	return moonfish_best_move_time(analysis, best_move, depth, time);
 }
--- a/tools/analyse.c
+++ b/tools/analyse.c
@@ -289,91 +289,6 @@
 	fflush(stdout);
 }
 
-static void moonfish_to_san(struct moonfish_chess *chess, char *name, struct moonfish_move *move)
-{
-	static char names[] = "NBRQK";
-	
-	int x, y;
-	struct moonfish_move moves[32];
-	struct moonfish_move *other_move;
-	char file_ambiguity, rank_ambiguity, ambiguity;
-	int to_x, to_y;
-	int from_x, from_y;
-	
-	from_x = move->from % 10 - 1;
-	from_y = move->from / 10 - 2;
-	
-	to_x = move->to % 10 - 1;
-	to_y = move->to / 10 - 2;
-	
-	if (move->piece % 16 == moonfish_pawn)
-	{
-		if (from_x != to_x)
-		{
-			*name++ = from_x + 'a';
-			*name++ = 'x';
-		}
-		
-		*name++ = to_x + 'a';
-		*name++ = to_y + '1';
-		
-		if (move->promotion % 16 != moonfish_pawn)
-		{
-			*name++ = '=';
-			*name++ = names[move->promotion % 16 - 2];
-		}
-		
-		*name = 0;
-		
-		return;
-	}
-	
-	file_ambiguity = 0;
-	rank_ambiguity = 0;
-	ambiguity = 0;
-	
-	for (y = 0 ; y < 8 ; y++)
-	for (x = 0 ; x < 8 ; x++)
-	{
-		if ((x + 1) + (y + 2) * 10 == move->from) continue;
-		moonfish_moves(chess, moves, (x + 1) + (y + 2) * 10);
-		
-		for (other_move = moves ; other_move->piece != moonfish_outside ; other_move++)
-		{
-			if (other_move->to != move->to) continue;
-			if (other_move->piece != move->piece) continue;
-			
-			ambiguity = 1;
-			if (other_move->from % 10 - 1 == from_x) file_ambiguity = 1;
-			if (other_move->from / 10 - 2 == from_y) rank_ambiguity = 1;
-		}
-	}
-	
-	*name++ = names[(move->piece & 0xF) - 2];
-	
-	if (ambiguity)
-	{
-		if (file_ambiguity)
-		{
-			if (rank_ambiguity)
-				*name++ = from_x + 'a';
-			*name++ = from_y + '1';
-		}
-		else
-		{
-			*name++ = from_x + 'a';
-		}
-	}
-	
-	if (move->captured != moonfish_empty)
-		*name++ = 'x';
-		
-	*name++ = to_x + 'a';
-	*name++ = to_y + '1';
-	
-	*name = 0;
-}
-
 static void *moonfish_start(void *data)
 {
 	static char line[2048];
--- /dev/null
+++ b/tools/chat.c
@@ -1,0 +1,307 @@
+/* moonfish is licensed under the AGPL (v3 or later) */
+/* copyright 2024 zamfofex */
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "../moonfish.h"
+#include "tools.h"
+#include "https.h"
+
+static void moonfish_parse_chat(char *argv0, char *line, char ***command, int *count)
+{
+	int length;
+	
+	*command = NULL;
+	*count = 0;
+	
+	while (*line == ' ') line++;
+	if (*line == 0) return;
+	if (*line == '@')
+	{
+		line++;
+		while (*line != ' ' && *line != 0) line++;
+		while (*line == ' ') line++;
+	}
+	if (*line == ':')
+	{
+		line++;
+		while (*line != ' ' && *line != 0) line++;
+		while (*line == ' ') line++;
+	}
+	
+	while (*line != 0)
+	{
+		*command = realloc(*command, sizeof **command * (*count + 1));
+		if (*line == ':')
+		{
+			(*command)[*count] = strdup(line + 1);
+			if ((*command)[*count] == NULL)
+			{
+				perror(argv0);
+				exit(1);
+			}
+			(*count)++;
+			break;
+		}
+		
+		length = 0;
+		while (line[length] != ' ' && line[length] != 0) length++;
+		(*command)[*count] = strndup(line, length);
+		line += length;
+		if ((*command)[*count] == NULL)
+		{
+			perror(argv0);
+			exit(1);
+		}
+		
+		while (*line == ' ') line++;
+		(*count)++;
+	}
+}
+
+static void moonfish_free_chat(char **command, int count)
+{
+	int i;
+	if (count == 0) return;
+	for (i = 0 ; i < count ; i++) free(command[i]);
+	free(command);
+}
+
+static void moonfish_read_chat(char *argv0, struct tls *tls, char *username, char **channel, char **message)
+{
+	static int under_count = 0;
+	
+	char *line;
+	char **command;
+	int count;
+	int i;
+	
+	for (;;)
+	{
+		line = moonfish_read_line(argv0, tls);
+		if (line == NULL)
+		{
+			fprintf(stderr, "%s: IRC connection closed unexpectedly\n", argv0);
+			exit(1);
+		}
+		
+		moonfish_parse_chat(argv0, line, &command, &count);
+		free(line);
+		
+		if (count == 0) continue;
+		
+		if (!strcmp(command[0], "433"))
+		{
+			moonfish_free_chat(command, count);
+			
+			under_count++;
+			
+			if (under_count > 16)
+			{
+				fprintf(stderr, "%s: Tried too many nicknames\n", argv0);
+				exit(1);
+			}
+			
+			moonfish_write_text(argv0, tls, "NICK ");
+			moonfish_write_text(argv0, tls, username);
+			for (i = 0 ; i < under_count ; i++) moonfish_write_text(argv0, tls, "_");
+			moonfish_write_text(argv0, tls, "\r\n");
+			
+			continue;
+		}
+		
+		if (!strcmp(command[0], "PING"))
+		{
+			if (count < 2)
+			{
+				moonfish_free_chat(command, count);
+				moonfish_write_text(argv0, tls, "PONG\r\n");
+				continue;
+			}
+			
+			moonfish_write_text(argv0, tls, "PONG ");
+			moonfish_write_text(argv0, tls, command[1]);
+			moonfish_write_text(argv0, tls, "\r\n");
+			moonfish_free_chat(command, count);
+			continue;
+		}
+		
+		if (!strcmp(command[0], "PRIVMSG"))
+		{
+			if (count < 3)
+			{
+				fprintf(stderr, "%s: Invalid IRC message\n", argv0);
+				exit(1);
+			}
+			
+			*channel = strdup(command[1]);
+			if (*channel == NULL)
+			{
+				perror(argv0);
+				exit(1);
+			}
+			
+			*message = strdup(command[2]);
+			if (*message == NULL)
+			{
+				perror(argv0);
+				exit(1);
+			}
+			
+			moonfish_free_chat(command, count);
+			break;
+		}
+		
+		moonfish_free_chat(command, count);
+	}
+}
+
+static void moonfish_chat(char *argv0, char **command, char **options, char *host, char *port, char *username, char *channels)
+{
+	struct tls *tls;
+	char *channel;
+	char *message;
+	struct moonfish_chess chess;
+	struct moonfish_move move;
+	char name[12];
+	FILE *in, *out;
+	char *value;
+	char *names, *name0;
+	char fen[128];
+	
+	moonfish_chess(&chess);
+	
+	moonfish_spawn(argv0, command, &in, &out);
+	fprintf(in, "uci\n");
+	moonfish_wait(out, "uciok");
+	
+	for (;;)
+	{
+		value = strchr(*options, '=');
+		if (value == NULL) break;
+		fprintf(in, "setoption name %.*s value %s\n", (int) (value - *options), *options, value + 1);
+		options++;
+	}
+	
+	fprintf(in, "isready\n");
+	moonfish_wait(out, "readyok");
+	
+	fprintf(in, "ucinewgame\n");
+	
+	tls = moonfish_connect(argv0, host, port);
+	
+	moonfish_write_text(argv0, tls, "USER ");
+	moonfish_write_text(argv0, tls, username);
+	moonfish_write_text(argv0, tls, " 0 * ");
+	moonfish_write_text(argv0, tls, username);
+	moonfish_write_text(argv0, tls, "\r\nNICK ");
+	moonfish_write_text(argv0, tls, username);
+	moonfish_write_text(argv0, tls, "\r\n");
+	
+	moonfish_write_text(argv0, tls, "JOIN ");
+	moonfish_write_text(argv0, tls, channels);
+	moonfish_write_text(argv0, tls, "\r\n");
+	
+	names = strdup("");
+	if (names == NULL)
+	{
+		perror(argv0);
+		exit(1);
+	}
+	
+	for (;;)
+	{
+		moonfish_read_chat(argv0, tls, username, &channel, &message);
+		if (moonfish_from_san(&chess, &move, message))
+		{
+			free(channel);
+			free(message);
+			continue;
+		}
+		
+		moonfish_to_uci(name, &move);
+		
+		names = realloc(names, strlen(names) + strlen(name) + 2);
+		if (names == NULL)
+		{
+			perror(argv0);
+			exit(1);
+		}
+		
+		strcat(names, " ");
+		strcat(names, name);
+		
+		fprintf(in, "isready\n");
+		moonfish_wait(out, "readyok");
+		fprintf(in, "position startpos moves");
+		fprintf(in, names);
+		fprintf(in, "\n");
+		
+		fprintf(in, "isready\n");
+		moonfish_wait(out, "readyok");
+		fprintf(in, "go movetime 5000\n");
+		
+		name0 = moonfish_wait(out, "bestmove");
+		names = realloc(names, strlen(names) + strlen(name0) + 2);
+		if (names == NULL)
+		{
+			perror(argv0);
+			exit(1);
+		}
+		
+		strcat(names, " ");
+		strcat(names, name0);
+		
+		moonfish_play(&chess, &move);
+		moonfish_from_uci(&chess, &move, name0);
+		moonfish_to_san(&chess, name, &move);
+		moonfish_play(&chess, &move);
+		moonfish_to_fen(&chess, fen);
+		
+		moonfish_write_text(argv0, tls, "PRIVMSG ");
+		moonfish_write_text(argv0, tls, channel);
+		moonfish_write_text(argv0, tls, " :");
+		moonfish_write_text(argv0, tls, name);
+		moonfish_write_text(argv0, tls, "\r\n");
+		
+		moonfish_write_text(argv0, tls, "PRIVMSG ");
+		moonfish_write_text(argv0, tls, channel);
+		moonfish_write_text(argv0, tls, " :https://lichess.org/export/fen.gif?fen=");
+		moonfish_write_text(argv0, tls, fen);
+		moonfish_write_text(argv0, tls, "\r\n");
+		
+		free(channel);
+		free(message);
+	}
+}
+
+int main(int argc, char **argv)
+{
+	static char *format = "<UCI-options> <cmd> <args>...";
+	static struct moonfish_arg args[] =
+	{
+		{"N", "host", "<name>", "irc.libera.chat", "network host name (default: 'irc.libera.chat')"},
+		{"P", "port", "<port>", "6697", "network port (default: '6697')"},
+		{"M", "nick", "<nickname>", "moonfish", "the bot's nickname (default: 'moonfish')"},
+		{"C", "channel", "<channels>", "#moonfish", "channels to join (default: '#moonfish')"},
+		{NULL, NULL, NULL, NULL, NULL},
+	};
+	
+	char **options, **command;
+	
+	/* todo: validate nickname & channels*/
+	options = moonfish_args(args, format, argc, argv);
+	
+	command = options;
+	for (;;)
+	{
+		if (*command == NULL) moonfish_usage(args, format, argv[0]);
+		if (strchr(*command, '=') == NULL) break;
+		command++;
+	}
+	
+	moonfish_chat(argv[0], command, options, args[0].value, args[1].value, args[2].value, args[3].value);
+	fprintf(stderr, "%s: Unreachable\n", argv[0]);
+	return 1;
+}
--