shithub: moonfish

Download patch

ref: f770cee72ae565567d0a201cc211cf9329c6c771
parent: e4ef04c293715e65cb66a5922596240ecb99a24b
author: zamfofex <zamfofex@twdb.moe>
date: Wed May 28 17:45:32 EDT 2025

make miscellaneous improvements

--- a/README.md
+++ b/README.md
@@ -47,7 +47,6 @@
 These are things that might be resolved eventually.
 
 - the TUI does not let you underpromote
-- no support for `go depth`, `go infinite`, `go mate`
 - no support for some seldom used UCI features
 
 download
@@ -120,7 +119,7 @@
 
 After compiling and running it, you may use the mouse to click and move pieces around. (So, they require mouse support from your terminal.)
 
-To analyse a game with a UCI bot, use `./analyse` followed optionally by the UCI options you want to specify, and then the command of whichever bot you want to use for analysis. (Though note that moonfish has limited analysis capabilities.)
+To analyse a game with a UCI bot, use `./analyse` followed optionally by the UCI options you want to specify, and then the command of whichever bot you want to use for analysis.
 
 ~~~
 # (analyse a game using Stockfish)
--- a/main.c
+++ b/main.c
@@ -6,6 +6,7 @@
 #include <string.h>
 #include <stdio.h>
 #include <ctype.h>
+#include <math.h>
 
 #include "moonfish.h"
 #include "threads.h"
@@ -38,8 +39,13 @@
 	return -1;
 }
 
-static moonfish_result_t moonfish_go0(void *data)
+static void moonfish_log_result(struct moonfish_result *result)
 {
+	printf("info depth %.0f nodes %ld time %ld", log(result->node_count) / log(16), result->node_count, result->time);
+}
+
+static void moonfish_log(struct moonfish_result *result0, void *data)
+{
 	static struct moonfish_move pv[256];
 	static struct moonfish_result result;
 	static struct moonfish_chess chess;
@@ -49,15 +55,23 @@
 	char name[6];
 	
 	info = data;
+	count0 = moonfish_getoption(info->options, "MultiPV");
 	
-	moonfish_best_move(info->root, &info->result, &info->search_options);
+	if (count0 == 0) {
+		moonfish_log_result(result0);
+		printf(" score cp %d\n", result0->score);
+		fflush(stdout);
+		return;
+	}
 	
-	count0 = moonfish_getoption(info->options, "MultiPV");
 	for (i = 0 ; i < count0 ; i++) {
 		count = sizeof pv / sizeof *pv;
 		moonfish_pv(info->root, pv, &result, i, &count);
 		if (count == 0) continue;
-		printf("info depth 1 score cp %d nodes %ld multipv %d pv", result.score, result.node_count, i + 1);
+		moonfish_log_result(result0);
+		if (count0 > 1) printf(" multipv %d", i + 1);
+		printf(" score cp %d", result.score);
+		if (count > 0) printf(" pv");
 		moonfish_root(info->root, &chess);
 		for (j = 0 ; j < count ; j++) {
 			moonfish_to_uci(&chess, pv + j, name);
@@ -65,15 +79,34 @@
 			printf(" %s", name);
 		}
 		printf("\n");
+		fflush(stdout);
 	}
+}
+
+static moonfish_result_t moonfish_go0(void *data)
+{
+	static struct moonfish_chess chess;
 	
-	printf("info depth 1 score cp %d nodes %ld\n", info->result.score, info->result.node_count);
+	struct moonfish_info *info;
+	char name[6];
 	
+	info = data;
 	moonfish_root(info->root, &chess);
+	if (moonfish_finished(&chess)) {
+		printf("bestmove 0000\n");
+		fflush(stdout);
+		info->searching = 0;
+		return moonfish_value;
+	}
+	
+	moonfish_best_move(info->root, &info->result, &info->search_options);
 	moonfish_to_uci(&chess, &info->result.move, name);
 	
+	moonfish_log_result(&info->result);
+	printf(" score cp %d\n", info->result.score);
 	printf("bestmove %s\n", name);
 	fflush(stdout);
+	info->searching = 0;
 	return moonfish_value;
 }
 
@@ -84,6 +117,7 @@
 	long int our_time, their_time, *xtime, time;
 	char *arg, *end;
 	long int node_count;
+	long int depth;
 	
 	info->searching = 1;
 	
@@ -91,6 +125,7 @@
 	their_time = -1;
 	time = -1;
 	node_count = -1;
+	depth = -1;
 	
 	moonfish_root(info->root, &chess);
 	
@@ -99,6 +134,8 @@
 		arg = strtok(NULL, "\r\n\t ");
 		if (arg == NULL) break;
 		
+		if (!strcmp(arg, "infinite")) continue;
+		
 		if (!strcmp(arg, "wtime") || !strcmp(arg, "btime")) {
 			
 			if (chess.white) {
@@ -161,6 +198,24 @@
 			
 			continue;
 		}
+		
+		if (!strcmp(arg, "depth")) {
+			
+			arg = strtok(NULL, "\r\n\t ");
+			if (arg == NULL) {
+				fprintf(stderr, "malformed 'go depth' command\n");
+				exit(1);
+			}
+			
+			errno = 0;
+			depth = strtol(arg, &end, 10);
+			if (errno || *end != 0 || depth < 0) {
+				fprintf(stderr, "malformed 'depth' in 'go' command\n");
+				exit(1);
+			}
+			
+			continue;
+		}
 	}
 	
 	info->search_options.max_time = time;
@@ -168,6 +223,13 @@
 	info->search_options.thread_count = moonfish_getoption(info->options, "Threads");
 	info->search_options.node_count = node_count;
 	
+	if (depth >= 0 && depth < 6) {
+		node_count = pow(16, depth);
+		if (node_count < info->search_options.node_count || info->search_options.node_count < 0) {
+			info->search_options.node_count = node_count;
+		}
+	}
+	
 #ifdef moonfish_no_threads
 	moonfish_go0(info);
 #else
@@ -184,8 +246,6 @@
 		exit(1);
 	}
 #endif
-	
-	info->searching = 0;
 }
 
 static void moonfish_position(struct moonfish_root *root)
@@ -325,8 +385,8 @@
 #ifndef moonfish_no_threads
 		{"Threads", "spin", 1, 1, 0xFFFF},
 #endif
-		{"MultiPV", "spin", 0, 0, 256},
-		{NULL},
+		{"MultiPV", "spin", 1, 0, 256},
+		{NULL, NULL, 0, 0, 0},
 	};
 	
 	char *arg;
@@ -345,6 +405,8 @@
 	info.has_thread = 0;
 #endif
 	
+	moonfish_idle(info.root, &moonfish_log, &info);
+	
 	for (;;) {
 		
 		fflush(stdout);
@@ -395,7 +457,7 @@
 		
 		if (!strcmp(arg, "setoption")) {
 			moonfish_setoption(&info);
-			if (info.searching) printf("info string warning: option may only take effect next search request\n");
+			if (info.searching) printf("info string warning: option might only take effect next search request\n");
 			continue;
 		}
 		
--- a/makefile
+++ b/makefile
@@ -30,6 +30,7 @@
 lichess: tools/lichess.o tools/https.o
 analyse: tools/analyse.o tools/pgn.o
 chat: tools/chat.o tools/https.o
+perft: tools/perft.o
 
 $(obj): moonfish.h
 $(tool_obj): moonfish.h tools/tools.h
--- a/moonfish.h
+++ b/moonfish.h
@@ -108,6 +108,7 @@
 struct moonfish_result {
 	struct moonfish_move move;
 	long int node_count;
+	long int time;
 	int score;
 };
 
@@ -210,6 +211,9 @@
 
 /* requests the PV with the given index, with at most 'count' moves */
 void moonfish_pv(struct moonfish_root *root, struct moonfish_move *moves, struct moonfish_result *result, int index, int *count);
+
+/* adds an "idle/log" handler, which will be called every once in a while during search */
+void moonfish_idle(struct moonfish_root *root, void (*log)(struct moonfish_result *result, void *data), void *data);
 
 #endif
 
--- a/scripts/check.sh
+++ b/scripts/check.sh
@@ -7,9 +7,8 @@
 
 go()
 {
-	echo 'setoption name Threads value 1'
 	echo "position fen $1"
-	echo "go nodes $2"
+	echo "go depth $2"
 	while read -r line
 	do
 		case "$line" in "bestmove "*)
@@ -23,8 +22,8 @@
 {
 	echo "- - - POSITION $2 - - -" >&2
 	./perft -F "$3" "$1"
-	coproc go "$3" `expr "$1" '*' 4096 + 2048`
-	{ ./moonfish | tee /dev/fd/3 3> /dev/fd/3 ; } <&"${COPROC[0]}" 3>&"${COPROC[1]}"
+	coproc go "$3" "$1"
+	{ ./moonfish | tee /dev/fd/3 3> /dev/fd/3 | tail -2 | sed -E 's/ time [^ ]+//g' ; } <&"${COPROC[0]}" 3>&"${COPROC[1]}"
 	echo >&2
 }
 
--- a/scripts/check.txt
+++ b/scripts/check.txt
@@ -1,105 +1,105 @@
 perft 0: 1
-info depth 1 score cp 23 nodes 2048
-bestmove e2e4
+info depth 2 nodes 1024 score cp 30
+bestmove b1c3
 perft 0: 1
-info depth 1 score cp 213 nodes 2048
+info depth 2 nodes 1024 score cp 223
 bestmove e2a6
 perft 0: 1
-info depth 1 score cp 136 nodes 2048
+info depth 2 nodes 1024 score cp 123
 bestmove b4f4
 perft 0: 1
-info depth 1 score cp -481 nodes 2048
-bestmove d2d4
+info depth 2 nodes 1024 score cp -491
+bestmove c4c5
 perft 0: 1
-info depth 1 score cp -481 nodes 2048
-bestmove d7d5
+info depth 2 nodes 1024 score cp -491
+bestmove c5c4
 perft 0: 1
-info depth 1 score cp 365 nodes 2048
+info depth 2 nodes 1024 score cp 577
 bestmove d7c8q
 perft 0: 1
-info depth 1 score cp 179 nodes 2048
-bestmove c3d5
+info depth 2 nodes 1024 score cp 96
+bestmove g5f6
 perft 1: 20
-info depth 1 score cp -3 nodes 6144
+info depth 2 nodes 1024 score cp 30
 bestmove b1c3
 perft 1: 48
-info depth 1 score cp 47 nodes 6144
+info depth 2 nodes 1024 score cp 223
 bestmove e2a6
 perft 1: 14
-info depth 1 score cp 64 nodes 6144
-bestmove b4c4
+info depth 2 nodes 1024 score cp 123
+bestmove b4f4
 perft 1: 6
-info depth 1 score cp -442 nodes 6144
+info depth 2 nodes 1024 score cp -491
 bestmove c4c5
 perft 1: 6
-info depth 1 score cp -442 nodes 6144
+info depth 2 nodes 1024 score cp -491
 bestmove c5c4
 perft 1: 44
-info depth 1 score cp 600 nodes 6144
-bestmove d7c8r
+info depth 2 nodes 1024 score cp 577
+bestmove d7c8q
 perft 1: 46
-info depth 1 score cp -27 nodes 6144
-bestmove c3d5
+info depth 2 nodes 1024 score cp 96
+bestmove g5f6
 perft 2: 400
-info depth 1 score cp 0 nodes 10240
+info depth 2 nodes 1024 score cp 30
 bestmove b1c3
 perft 2: 2039
-info depth 1 score cp 93 nodes 10240
+info depth 2 nodes 1024 score cp 223
 bestmove e2a6
 perft 2: 191
-info depth 1 score cp 38 nodes 10240
-bestmove b4c4
+info depth 2 nodes 1024 score cp 123
+bestmove b4f4
 perft 2: 264
-info depth 1 score cp -173 nodes 10240
+info depth 2 nodes 1024 score cp -491
 bestmove c4c5
 perft 2: 264
-info depth 1 score cp -173 nodes 10240
+info depth 2 nodes 1024 score cp -491
 bestmove c5c4
 perft 2: 1486
-info depth 1 score cp 640 nodes 10240
-bestmove d7c8r
+info depth 2 nodes 1024 score cp 577
+bestmove d7c8q
 perft 2: 2079
-info depth 1 score cp 15 nodes 10240
-bestmove c3d5
+info depth 2 nodes 1024 score cp 96
+bestmove g5f6
 perft 3: 8902
-info depth 1 score cp 9 nodes 14336
+info depth 3 nodes 4096 score cp 2
 bestmove b1c3
 perft 3: 97862
-info depth 1 score cp 212 nodes 14336
+info depth 3 nodes 4096 score cp 116
 bestmove e2a6
 perft 3: 2812
-info depth 1 score cp 26 nodes 14336
+info depth 3 nodes 4096 score cp 64
 bestmove b4c4
 perft 3: 9467
-info depth 1 score cp -343 nodes 14336
-bestmove g1h1
+info depth 3 nodes 4096 score cp -498
+bestmove b4c5
 perft 3: 9467
-info depth 1 score cp -343 nodes 14336
-bestmove g8h8
+info depth 3 nodes 4096 score cp -498
+bestmove b5c4
 perft 3: 62379
-info depth 1 score cp 640 nodes 14336
-bestmove d7c8r
+info depth 3 nodes 4096 score cp 304
+bestmove d7c8q
 perft 3: 89890
-info depth 1 score cp 161 nodes 14336
-bestmove c3d5
+info depth 3 nodes 4096 score cp 35
+bestmove c4d5
 perft 4: 197281
-info depth 1 score cp 16 nodes 18432
-bestmove g1f3
+info depth 4 nodes 65536 score cp 37
+bestmove d2d4
 perft 4: 4085603
-info depth 1 score cp 217 nodes 18432
+info depth 4 nodes 65536 score cp -78
 bestmove e2a6
 perft 4: 43238
-info depth 1 score cp 123 nodes 18432
-bestmove b4f4
+info depth 4 nodes 65536 score cp 109
+bestmove b4c4
 perft 4: 422333
-info depth 1 score cp -251 nodes 18432
-bestmove f3d4
+info depth 4 nodes 65536 score cp -473
+bestmove c4c5
 perft 4: 422333
-info depth 1 score cp -251 nodes 18432
-bestmove f6d5
+info depth 4 nodes 65536 score cp -473
+bestmove c5c4
 perft 4: 2103487
-info depth 1 score cp 641 nodes 18432
-bestmove d7c8r
+info depth 4 nodes 65536 score cp 585
+bestmove d7c8q
 perft 4: 3894594
-info depth 1 score cp 164 nodes 18432
+info depth 4 nodes 65536 score cp -43
 bestmove c3d5
--- a/scripts/compare.sh
+++ b/scripts/compare.sh
@@ -11,9 +11,6 @@
 
 # note: use this script only from the root/project directory
 
-make=gmake
-which "$make" &> /dev/null || make=make
-
 rm -f moonfish
 mkdir -p compare
 [[ -f compare/openings.fen ]] || wget -O- https://moonfish.cc/pohl.fen.xz | xz -d > compare/openings.fen
@@ -22,7 +19,7 @@
 [[ "x$(git status --porcelain)" = x ]] || dirty=-dirty
 
 rev1="$(git rev-parse --short HEAD)"
-"$make" moonfish
+make moonfish
 mv -f moonfish compare/moonfish-"$rev1$dirty"
 
 git stash
@@ -29,7 +26,7 @@
 git reset --hard "${1:-main}"
 
 rev2="$(git rev-parse --short HEAD)"
-"$make" moonfish
+make moonfish
 mv -f moonfish compare/moonfish-"$rev2"
 
 [[ "$rev1" = "$rev2" ]] || git reset --hard "$rev1"
@@ -58,7 +55,7 @@
 "$cli" \
 	-engine {name=,cmd=./}moonfish-"$rev1$dirty" \
 	-engine {name=,cmd=./}moonfish-"$rev2" \
-	-each $protocol tc=inf/10+0.1 option.Threads=1 \
+	-each $protocol tc=inf/10+0.1 option.MultiPV=0 \
 	-openings $format file=openings.fen order=random \
 	-games 2 -rounds 1024 \
 	-sprt elo0=0 elo1=12 alpha=0.05 beta=0.05 \
--- a/search.c
+++ b/search.c
@@ -18,7 +18,7 @@
 
 #ifdef _WIN32
 
-static unsigned long int moonfish_clock(void)
+static long int moonfish_clock(void)
 {
 	return GetTickCount();
 }
@@ -27,7 +27,7 @@
 
 #ifdef moonfish_no_clock
 
-static unsigned long int moonfish_clock(void)
+static long int moonfish_clock(void)
 {
 	time_t t;
 	if (time(&t) < 0) {
@@ -39,7 +39,7 @@
 
 #else
 
-static unsigned long int moonfish_clock(void)
+static long int moonfish_clock(void)
 {
 	struct timespec ts;
 	if (clock_gettime(CLOCK_MONOTONIC, &ts)) {
@@ -56,7 +56,7 @@
 struct moonfish_node {
 	struct moonfish_node *parent;
 	struct moonfish_node *children;
-	_Atomic int visits, count, uses;
+	_Atomic int visits, count;
 	_Atomic short int score;
 	_Atomic unsigned char bounds[2];
 	_Atomic unsigned char ignored;
@@ -66,17 +66,11 @@
 struct moonfish_root {
 	struct moonfish_node node;
 	struct moonfish_chess chess;
+#ifndef moonfish_mini
 	_Atomic int stop;
-};
-
-struct moonfish_thread {
-#ifndef moonfish_no_threads
-	thrd_t thread;
+	void (*log)(struct moonfish_result *result, void *data);
+	void *data;
 #endif
-	struct moonfish_root *root;
-	unsigned long int time, time0;
-	long int node_count;
-	unsigned char clean;
 };
 
 static short int moonfish_score(struct moonfish_chess *chess)
@@ -125,23 +119,9 @@
 
 static void moonfish_discard(struct moonfish_node *node)
 {
-	int i, count;
-	
-#ifdef moonfish_no_threads
-	count = node->count;
-#else
-	for (;;) {
-		count = node->count;
-		if (count < 0) continue;
-		if (atomic_compare_exchange_strong(&node->count, &count, -2)) break;
-	}
-	for (;;) {
-		if (node->uses == 0) break;
-	}
-#endif
-	
-	for (i = 0 ; i < count ; i++) moonfish_discard(node->children + i);
-	if (count > 0) free(node->children);
+	int i;
+	for (i = 0 ; i < node->count ; i++) moonfish_discard(node->children + i);
+	if (node->count > 0) free(node->children);
 	node->count = 0;
 }
 
@@ -153,7 +133,6 @@
 	node->ignored = 0;
 	node->bounds[0] = 0;
 	node->bounds[1] = 1;
-	node->uses = 0;
 }
 
 static int moonfish_compare(const void *ax, const void *bx)
@@ -239,17 +218,6 @@
 	moonfish_play(chess, &move);
 }
 
-#ifndef moonfish_no_threads
-
-static void moonfish_clear(struct moonfish_node *node)
-{
-	if (node == NULL) return;
-	moonfish_clear(node->parent);
-	atomic_fetch_add(&node->uses, -1);
-}
-
-#endif
-
 static struct moonfish_node *moonfish_select(struct moonfish_node *node, struct moonfish_chess *chess)
 {
 	struct moonfish_node *next;
@@ -262,23 +230,16 @@
 		count = node->count;
 		if (count == 0) return node;
 #else
-		atomic_fetch_add(&node->uses, 1);
 		for (;;) {
 			count = 0;
 			if (atomic_compare_exchange_strong(&node->count, &count, -1)) return node;
-			if (count == -2) {
-				moonfish_clear(node);
-				return NULL;
-			}
 			if (count > 0) break;
 		}
 #endif
 		
-		for (;;) {
-			
-			next = NULL;
-			max_confidence = -1;
-			
+		max_confidence = -1;
+		
+		while (max_confidence < 0) {
 			for (i = 0 ; i < count ; i++) {
 				if (node->children[i].ignored) continue;
 				if (node->children[i].count == -1) continue;
@@ -288,8 +249,6 @@
 					max_confidence = confidence;
 				}
 			}
-			
-			if (next != NULL) break;
 		}
 		
 		node = next;
@@ -315,7 +274,6 @@
 		node->visits++;
 #else
 		atomic_fetch_add(&node->visits, 1);
-		atomic_fetch_add(&node->uses, -1);
 #endif
 		node = next;
 	}
@@ -327,23 +285,18 @@
 	int bound;
 	
 	i = 1;
-	
 	while (node != NULL) {
-		
 		bound = 0;
-		
 		for (j = 0 ; j < node->count ; j++) {
 			if (1 - node->children[j].bounds[1 - i] > bound) {
 				bound = 1 - node->children[j].bounds[1 - i];
 			}
 		}
-		
 		for (j = 0 ; j < node->count ; j++) {
 			if (1 - node->children[j].bounds[1 - i] < bound) {
 				node->children[j].ignored = 1;
 			}
 		}
-		
 		node->bounds[i] = bound;
 		node = node->parent;
 		i = 1 - i;
@@ -350,22 +303,58 @@
 	}
 }
 
-static void moonfish_search(struct moonfish_node *node, struct moonfish_chess *chess0)
+static moonfish_result_t moonfish_search(void *data)
 {
-	int i;
+	struct moonfish_root *root;
 	struct moonfish_node *leaf;
 	struct moonfish_chess chess;
+	int i;
 	
-	for (i = 0 ; i < 256 ; i++) {
-		chess = *chess0;
-		leaf = moonfish_select(node, &chess);
-		if (leaf == NULL) continue;
+	root = data;
+	
+	for (i = 0 ; i < 1024 ; i++) {
+		chess = root->chess;
+		leaf = moonfish_select(&root->node, &chess);
 		moonfish_expand(leaf, &chess);
 		if (leaf->count == 0 && moonfish_check(&chess)) moonfish_propagate_bounds(leaf);
 		moonfish_propagate(leaf);
 	}
+	
+	return moonfish_value;
 }
 
+#ifndef moonfish_no_threads
+
+static void moonfish_start(struct moonfish_root *root, int thread_count)
+{
+	thrd_t *threads;
+	int i;
+	
+	threads = malloc(thread_count * sizeof *threads);
+	if (threads == NULL) {
+		perror("malloc");
+		exit(1);
+	}
+	
+	for (i = 0 ; i < thread_count ; i++) {
+		if (thrd_create(&threads[i], &moonfish_search, root) != thrd_success) {
+			fprintf(stderr, "could not create thread\n");
+			exit(1);
+		}
+	}
+	
+	for (i = 0 ; i < thread_count ; i++) {
+		if (thrd_join(threads[i], NULL) != thrd_success) {
+			fprintf(stderr, "could not join thread\n");
+			exit(1);
+		}
+	}
+	
+	free(threads);
+}
+
+#endif
+
 static void moonfish_clean(struct moonfish_node *node)
 {
 	int i;
@@ -377,37 +366,13 @@
 	}
 }
 
-static moonfish_result_t moonfish_start(void *data)
-{
-	struct moonfish_thread *thread;
-	int i, count;
-	
-	thread = data;
-	
-	moonfish_search(&thread->root->node, &thread->root->chess);
-	while (moonfish_clock() - thread->time0 < thread->time) {
-#ifndef moonfish_mini
-		if (thread->root->stop) break;
-		if (thread->root->node.visits >= thread->node_count) break;
-#endif
-		count = thread->root->node.count;
-		for (i = 0 ; i < thread->root->node.count ; i++) {
-			if (thread->root->node.children[i].ignored) count--;
-		}
-		if (count <= 1) break;
-		moonfish_search(&thread->root->node, &thread->root->chess);
-		if (thread->clean) moonfish_clean(&thread->root->node);
-	}
-	
-	return moonfish_value;
-}
-
 void moonfish_best_move(struct moonfish_root *root, struct moonfish_result *result, struct moonfish_options *options)
 {
-	struct moonfish_thread thread, *threads;
 	struct moonfish_node *node;
-	long int time;
+	long int time, time0;
+	long int node_count;
 	int i, j;
+	int count;
 	
 	time = LONG_MAX;
 	if (options->our_time >= 0) time = options->our_time / 16;
@@ -415,55 +380,38 @@
 	time -= time / 32 + 125;
 	if (time < 0) time = 0;
 	
-	thread.clean = 1;
-	thread.root = root;
-	thread.time = time;
-	thread.time0 = moonfish_clock();
-	if (options->node_count < 0) thread.node_count = LONG_MAX;
-	else thread.node_count = root->node.visits + options->node_count;
+	time0 = moonfish_clock();
+	node_count = options->node_count;
+	if (node_count < 0) node_count = LONG_MAX;
 	
+	for (;;) {
 #ifdef moonfish_no_threads
-	
-	moonfish_start(&thread);
-	
+		moonfish_search(root);
 #else
-	
-	threads = malloc(options->thread_count * sizeof *threads);
-	if (threads == NULL) {
-		perror("malloc");
-		exit(1);
-	}
-	
-	for (i = 0 ; i < options->thread_count ; i++) {
-		threads[i] = thread;
-		thread.clean = 0;
-		if (thrd_create(&threads[i].thread, &moonfish_start, threads + i) != thrd_success) {
-			fprintf(stderr, "could not create thread\n");
-			exit(1);
+		moonfish_start(root, options->thread_count);
+#endif
+		moonfish_clean(&root->node);
+		if (root->node.count > 0) qsort(root->node.children, root->node.count, sizeof root->node, &moonfish_compare);
+		for (i = 0 ; i < root->node.count ; i++) {
+			node = root->node.children + i;
+			for (j = 0 ; j < node->count ; j++) node->children[j].parent = node;
 		}
-	}
-	
-	for (i = 0 ; i < options->thread_count ; i++) {
-		if (thrd_join(threads[i].thread, NULL) != thrd_success) {
-			fprintf(stderr, "could not join thread\n");
-			exit(1);
+		moonfish_node_move(root->node.children, &root->chess, &result->move);
+		result->score = root->node.score;
+		result->node_count = root->node.visits;
+		result->time = moonfish_clock() - time0;
+#ifndef moonfish_mini
+		if (root->log != NULL) (*root->log)(result, root->data);
+		if (root->stop) break;
+		if (root->node.visits >= node_count) break;
+#endif
+		if (result->time >= time) break;
+		count = root->node.count;
+		for (i = 0 ; i < root->node.count ; i++) {
+			if (root->node.children[i].ignored) count--;
 		}
+		if (count <= 1) break;
 	}
-	
-	free(threads);
-	
-#endif
-	
-	moonfish_clean(&root->node);
-	
-	if (root->node.count > 0) qsort(root->node.children, root->node.count, sizeof root->node, &moonfish_compare);
-	for (i = 0 ; i < root->node.count ; i++) {
-		node = root->node.children + i;
-		for (j = 0 ; j < node->count ; j++) node->children[j].parent = node;
-	}
-	moonfish_node_move(root->node.children, &root->chess, &result->move);
-	result->score = root->node.score;
-	result->node_count = root->node.visits;
 }
 
 void moonfish_reroot(struct moonfish_root *root, struct moonfish_chess *chess)
@@ -518,6 +466,9 @@
 		exit(1);
 	}
 	
+#ifndef moonfish_mini
+	root->log = NULL;
+#endif
 	root->stop = 0;
 	moonfish_node(&root->node);
 	moonfish_chess(&root->chess);
@@ -583,6 +534,12 @@
 		
 		node = best_node;
 	}
+}
+
+void moonfish_idle(struct moonfish_root *root, void (*log)(struct moonfish_result *result, void *data), void *data)
+{
+	root->log = log;
+	root->data = data;
 }
 
 #endif
--