ref: a03b6067b6a3064f49b7f99b60bec83129fb4884
dir: /tools/analyse.c/
/* moonfish is licensed under the AGPL (v3 or later) */
/* copyright 2023, 2024 zamfofex */
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <termios.h>
#include <signal.h>
#include <poll.h>
#include "../moonfish.h"
#include "tools.h"
struct moonfish_ply
{
struct moonfish_chess chess;
char name[6];
char best[6];
char san[16];
int white, black, draw;
int score;
int checkmate;
int depth;
};
struct moonfish_fancy
{
struct moonfish_ply plies[256];
int i, count;
pthread_mutex_t *mutex;
char *argv0;
int x, y;
FILE *in, *out;
char *fen;
int ox, oy;
int offset;
char pv[32];
int time, taken;
int idle;
int stop;
};
static void moonfish_fancy_square(struct moonfish_fancy *fancy, int x, int y)
{
unsigned char piece;
if (x + 1 == fancy->x && y + 1 == fancy->y)
printf("\x1B[48;5;219m");
else if (x % 2 == y % 2)
printf("\x1B[48;5;111m");
else
printf("\x1B[48;5;69m");
y = 7 - y;
piece = fancy->plies[fancy->i].chess.board[(x + 1) + (y + 2) * 10];
if (piece == moonfish_empty)
{
printf(" ");
return;
}
if (piece / 16 == 1)
printf("\x1B[38;5;253m");
else
printf("\x1B[38;5;240m");
switch (piece % 16)
{
case 1:
printf("\xE2\x99\x9F ");
return;
case 2:
printf("\xE2\x99\x9E ");
return;
case 3:
printf("\xE2\x99\x9D ");
return;
case 4:
printf("\xE2\x99\x9C ");
return;
case 5:
printf("\xE2\x99\x9B ");
return;
case 6:
printf("\xE2\x99\x9A ");
return;
}
}
static void moonfish_evaluation(struct moonfish_fancy *fancy)
{
int score;
int white, black, draw;
int white_mod, black_mod;
struct moonfish_ply *ply;
ply = fancy->plies + fancy->i;
if (ply->checkmate != 0)
{
score = 1;
white = 0;
black = 0;
if (ply->checkmate > 0) white = 1;
else black = 1;
}
else
{
white = ply->white;
black = ply->black;
score = white + black + ply->draw;
if (score == 0)
{
white = 1;
black = 1;
score = 2;
}
}
white *= 16;
black *= 16;
white_mod = white % score;
black_mod = black % score;
white /= score;
black /= score;
if (ply->draw == 0)
{
while (16 - white - black > 0)
{
if (white_mod > black_mod) white++;
else black++;
}
}
draw = 16 - white - black;
while (black > 1)
{
printf("\x1B[48;5;240m \x1B[0m\x1B[B\x08");
black -= 2;
}
if (black)
{
if (draw)
{
printf("\x1B[38;5;67m\x1B[48;5;240m\xE2\x96\x84\x1B[0m\x1B[B\x08");
draw--;
}
else
{
printf("\x1B[38;5;253m\x1B[48;5;240m\xE2\x96\x84\x1B[0m\x1B[B\x08");
white--;
}
}
while (draw > 1)
{
printf("\x1B[48;5;67m \x1B[0m\x1B[B\x08");
draw -= 2;
}
if (draw)
{
printf("\x1B[38;5;253m\x1B[48;5;67m\xE2\x96\x84\x1B[0m\x1B[B\x08");
black--;
}
while (white > 1)
{
printf("\x1B[48;5;253m \x1B[0m\x1B[B\x08");
white -= 2;
}
}
static void moonfish_scoresheet_move(struct moonfish_fancy *fancy, int i)
{
struct moonfish_ply *ply;
int checkmate;
int score;
ply = fancy->plies + i;
if (i >= fancy->count)
{
printf("%10s", "");
return;
}
if (i == fancy->i) printf("\x1B[48;5;248m\x1B[38;5;235m");
printf(" %s", ply->san);
if (moonfish_finished(&ply->chess))
{
if (moonfish_checkmate(&ply->chess)) printf("\x1B[38;5;160m# ");
else printf("\x1B[38;5;111m(0)");
}
else if (ply->checkmate)
{
checkmate = ply->checkmate;
if (checkmate < 0) checkmate *= -1;
printf("\x1B[38;5;162m#");
if (ply->checkmate > 0) printf("+");
else printf("-");
if (checkmate < 10)
printf("%d", checkmate);
else
printf("X");
}
else if (i > 0)
{
score = ply->score + fancy->plies[i - 1].score;
if (fancy->plies[i - 1].checkmate != 0 || score > 200) printf("\x1B[38;5;124m?? ");
else if (score > 100) printf("\x1B[38;5;173m? ");
else printf(" ");
}
else
{
printf(" ");
}
printf("%*s\x1B[0m", 6 - (int) strlen(ply->san), "");
}
static void moonfish_scoresheet(struct moonfish_fancy *fancy)
{
int i, j;
i = fancy->offset * 2;
if (fancy->plies[0].chess.white != 0) i++;
for (j = 0 ; j < 6 ; j++)
{
printf("\x1B[23G");
moonfish_scoresheet_move(fancy, i);
moonfish_scoresheet_move(fancy, i + 1);
printf("\x1B[B");
i += 2;
}
}
static void moonfish_fancy_score(struct moonfish_fancy *fancy)
{
struct moonfish_ply *ply;
int score;
if (fancy->idle == 0) printf("\x1B[38;5;103m");
else printf("\x1B[38;5;111m");
printf("(+)\x1B[0m ");
ply = fancy->plies + fancy->i;
score = ply->score;
if (!ply->chess.white) score *= -1;
if (moonfish_checkmate(&ply->chess))
printf("\x1B[38;5;160m#\x1B[0m");
else if (moonfish_finished(&ply->chess))
printf("0");
else if (ply->checkmate != 0)
printf("#%d", ply->checkmate);
else if (score < 0)
printf("-%d.%02d", -score / 100, -score % 100);
else
printf("%d.%02d", score / 100, score % 100);
printf(" (depth %d)", ply->depth);
if (fancy->idle == 0) printf(" in %d.%ds of %ds", fancy->taken / 1000, fancy->taken % 1000 / 100, fancy->time / 1000);
else printf(" in %ds", fancy->time / 1000);
printf("\x1B[0K");
}
static void moonfish_fancy(struct moonfish_fancy *fancy)
{
unsigned char x, y;
printf("\x1B[%dH", fancy->oy);
for (y = 0 ; y < 8 ; y++)
{
printf(" ");
for (x = 0 ; x < 8 ; x++) moonfish_fancy_square(fancy, x, y);
printf("\x1B[0m\n");
}
printf("\x1B[%d;23H", fancy->oy);
moonfish_fancy_score(fancy);
printf("\x1B[%dH", fancy->oy + 1);
moonfish_scoresheet(fancy);
printf("\x1B[%d;21H", fancy->oy);
moonfish_evaluation(fancy);
printf("\x1B[%d;23H", fancy->oy + 7);
printf("best:%s\x1B[0K\n", fancy->pv);
fflush(stdout);
}
static void *moonfish_start(void *data)
{
static char line[2048];
static struct moonfish_ply ply;
static char san[16];
static struct moonfish_move move;
struct moonfish_fancy *fancy;
char *arg;
int score;
char *buffer;
unsigned int i, length;
int changed;
fancy = data;
pthread_mutex_lock(fancy->mutex);
changed = 0;
for (;;)
{
if (changed != 0) moonfish_fancy(fancy);
changed = 0;
pthread_mutex_unlock(fancy->mutex);
if (fgets(line, sizeof line, fancy->out) == NULL) exit(1);
pthread_mutex_lock(fancy->mutex);
arg = strtok_r(line, "\r\n\t ", &buffer);
if (arg == NULL) continue;
if (!strcmp(arg, "bestmove"))
{
fancy->idle = 1;
fancy->taken = fancy->time;
fancy->stop = 0;
changed = 1;
continue;
}
if (fancy->stop != 0) continue;
if (strcmp(arg, "info")) continue;
ply = fancy->plies[fancy->i];
ply.depth = 0;
for (;;)
{
arg = strtok_r(NULL, "\r\n\t ", &buffer);
if (arg == NULL) break;
if (!strcmp(arg, "lowerbound") || !strcmp(arg, "upperbound"))
{
changed = 0;
break;
}
if (!strcmp(arg, "depth"))
{
arg = strtok_r(NULL, "\r\n\t ", &buffer);
if (arg == NULL || moonfish_int(arg, &ply.depth) != 0 || ply.depth < 0)
{
fprintf(stderr, "%s: malformed 'depth' in 'info' command\n", fancy->argv0);
exit(1);
}
continue;
}
if (!strcmp(arg, "pv"))
{
changed = 1;
fancy->idle = 0;
i = 0;
while (i < sizeof fancy->pv - 1)
{
arg = strtok_r(NULL, "\r\n\t ", &buffer);
if (arg == NULL) break;
if (moonfish_from_uci(&ply.chess, &move, arg) != 0)
{
fprintf(stderr, "%s: invalid move: %s\n", fancy->argv0, arg);
exit(1);
}
if (i == 0) strcpy(ply.best, arg);
moonfish_to_san(&ply.chess, &move, san);
length = strlen(san);
if (i + length > sizeof fancy->pv - 2) break;
ply.chess = move.chess;
fancy->pv[i++] = ' ';
strcpy(fancy->pv + i, san);
i += length;
}
ply.chess = fancy->plies[fancy->i].chess;
continue;
}
if (!strcmp(arg, "wdl"))
{
changed = 1;
fancy->idle = 0;
arg = strtok_r(NULL, "\r\n\t ", &buffer);
if (arg == NULL || moonfish_int(arg, &ply.white) != 0 || ply.white < 0)
{
fprintf(stderr, "%s: malformed 'wdl' win in 'info' command\n", fancy->argv0);
exit(1);
}
arg = strtok_r(NULL, "\r\n\t ", &buffer);
if (arg == NULL || moonfish_int(arg, &ply.draw) != 0 || ply.draw < 0)
{
fprintf(stderr, "%s: malformed 'wdl' draw in 'info' command\n", fancy->argv0);
exit(1);
}
arg = strtok_r(NULL, "\r\n\t ", &buffer);
if (arg == NULL || moonfish_int(arg, &ply.black) != 0 || ply.black < 0)
{
fprintf(stderr, "%s: malformed 'wdl' loss in 'info' command\n", fancy->argv0);
exit(1);
}
ply.checkmate = 0;
if (!ply.chess.white)
{
score = ply.white;
ply.white = ply.black;
ply.black = score;
}
continue;
}
if (!strcmp(arg, "time"))
{
changed = 1;
fancy->idle = 0;
arg = strtok_r(NULL, "\r\n\t ", &buffer);
if (arg == NULL || moonfish_int(arg, &fancy->taken) != 0 || fancy->taken < 0)
{
fprintf(stderr, "%s: malformed 'time' in 'info' command\n", fancy->argv0);
exit(1);
}
continue;
}
if (strcmp(arg, "score")) continue;
changed = 1;
fancy->idle = 0;
arg = strtok_r(NULL, "\r\n\t ", &buffer);
if (arg == NULL) break;
if (!strcmp(arg, "cp"))
{
arg = strtok_r(NULL, "\r\n\t ", &buffer);
if (arg == NULL || moonfish_int(arg, &score) != 0)
{
fprintf(stderr, "%s: malformed 'cp' in 'info score' command\n", fancy->argv0);
exit(1);
}
ply.score = score;
ply.white = 100;
ply.black = 100;
ply.draw = 0;
ply.checkmate = 0;
if (score > 0) ply.white += score;
else ply.black -= score;
if (!ply.chess.white)
{
score = ply.white;
ply.white = ply.black;
ply.black = score;
}
continue;
}
if (!strcmp(arg, "mate"))
{
arg = strtok_r(NULL, "\r\n\t ", &buffer);
if (arg == NULL || moonfish_int(arg, &score) != 0)
{
fprintf(stderr, "%s: malformed 'mate' in 'info score' command\n", fancy->argv0);
exit(1);
}
if (!ply.chess.white) score *= -1;
ply.white = 0;
ply.black = 0;
ply.draw = 0;
ply.checkmate = score;
ply.score = 0;
continue;
}
}
if (changed != 0) fancy->plies[fancy->i] = ply;
}
}
static struct termios moonfish_termios;
static FILE *moonfish_engine = NULL;
static void moonfish_exit(void)
{
if (moonfish_engine != NULL) fprintf(moonfish_engine, "quit\n");
tcsetattr(0, TCSANOW, &moonfish_termios);
printf("\n\x1B[?1000l");
fflush(stdout);
}
static void moonfish_signal(int signal)
{
(void) signal;
exit(1);
}
static int moonfish_move_from(struct moonfish_chess *chess, struct moonfish_move *found, int x0, int y0, int x1, int y1)
{
y0 = 10 - y0;
y1 = 10 - y1;
return moonfish_move(chess, found, x0 + y0 * 10, x1 + y1 * 10);
}
static void moonfish_analyse(struct moonfish_fancy *fancy)
{
int i;
fancy->taken = 0;
if (fancy->idle == 0) fancy->stop = 1;
fprintf(fancy->in, "stop\n");
fprintf(fancy->in, "position ");
if (fancy->fen == NULL) fprintf(fancy->in, "startpos");
else fprintf(fancy->in, "fen %s", fancy->fen);
if (fancy->i > 0)
{
fprintf(fancy->in, " moves");
for (i = 1 ; i <= fancy->i ; i++)
fprintf(fancy->in, " %s", fancy->plies[i].name);
}
fprintf(fancy->in, "\n");
fprintf(fancy->in, "go movetime %d\n", fancy->time);
fancy->pv[0] = 0;
moonfish_fancy(fancy);
}
static void moonfish_go(struct moonfish_fancy *fancy)
{
fancy->time = 4000;
moonfish_analyse(fancy);
}
static void moonfish_bump(struct moonfish_fancy *fancy)
{
fancy->time += 2000;
moonfish_analyse(fancy);
}
static void moonfish_scroll(struct moonfish_fancy *fancy)
{
int i;
i = fancy->i + 2;
if (fancy->plies[0].chess.white != 0) i--;
if (i < fancy->offset * 2 + 2) fancy->offset = i / 2 - 1;
if (i > fancy->offset * 2 + 12) fancy->offset = i / 2 - 6;
if (fancy->offset < 0) fancy->offset = 0;
}
static void moonfish_play(struct moonfish_fancy *fancy, struct moonfish_move *move)
{
if (fancy->i + 1 >= (int) (sizeof fancy->plies / sizeof *fancy->plies)) return;
fancy->i++;
fancy->count = fancy->i + 1;
fancy->plies[fancy->i] = fancy->plies[fancy->i - 1];
fancy->plies[fancy->i].depth = 0;
fancy->plies[fancy->i].score *= -1;
fancy->plies[fancy->i].best[0] = 0;
moonfish_to_uci(&fancy->plies[fancy->i].chess, move, fancy->plies[fancy->i].name);
moonfish_to_san(&fancy->plies[fancy->i].chess, move, fancy->plies[fancy->i].san);
fancy->plies[fancy->i].chess = move->chess;
fancy->x = 0;
moonfish_scroll(fancy);
moonfish_go(fancy);
}
int main(int argc, char **argv)
{
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static char *format = "<UCI-options>... [--] <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;
struct termios termios;
struct sigaction action;
int ch, ch0;
int x1, y1;
struct moonfish_move move;
int error;
char **command;
int command_count;
char *value;
char **options;
int i;
moonfish_spawner(argv[0]);
/* handle command line arguments */
command = moonfish_args(args, format, argc, argv);
command_count = argc - (command - argv);
if (command_count < 1) moonfish_usage(args, format, argv[0]);
options = command;
for (;;)
{
value = strchr(*command, '=');
if (value == NULL)
{
if (!strcmp(*command, "--"))
{
command_count--;
command++;
if (command_count <= 0) moonfish_usage(args, format, argv[0]);
}
break;
}
if (strchr(*command, '\n') != NULL || strchr(*command, '\r') != NULL) moonfish_usage(args, format, argv[0]);
command_count--;
command++;
if (command_count <= 0) moonfish_usage(args, format, argv[0]);
}
/* initialise data structures */
fancy = malloc(sizeof *fancy);
if (fancy == NULL)
{
perror(argv[0]);
return 1;
}
fancy->argv0 = argv[0];
fancy->mutex = &mutex;
fancy->offset = 0;
fancy->pv[0] = 0;
fancy->idle = 1;
fancy->stop = 0;
fancy->x = 0;
fancy->y = 0;
fancy->i = 0;
fancy->count = 1;
strcpy(fancy->plies[0].san, "...");
fancy->plies[0].white = 0;
fancy->plies[0].black = 0;
fancy->plies[0].draw = 0;
fancy->plies[0].checkmate = 0;
fancy->plies[0].depth = 0;
fancy->plies[0].score = 0;
moonfish_chess(&fancy->plies[0].chess);
if (args[0].value == NULL)
{
fancy->fen = NULL;
}
else
{
fancy->fen = args[0].value;
moonfish_from_fen(&fancy->plies[0].chess, fancy->fen);
}
/* configure the terminal for displaying the user interface */
if (tcgetattr(0, &moonfish_termios))
{
perror(argv[0]);
return 1;
}
if (atexit(&moonfish_exit))
{
moonfish_exit();
return 1;
}
action.sa_handler = &moonfish_signal;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
if (sigaction(SIGTERM, &action, NULL) || sigaction(SIGINT, &action, NULL) || sigaction(SIGQUIT, &action, NULL))
{
perror(argv[0]);
return 1;
}
termios = moonfish_termios;
termios.c_lflag &= ~(ECHO | ICANON);
if (tcsetattr(0, TCSANOW, &termios))
{
perror(argv[0]);
return 1;
}
printf("\n\n\n\n\n\n\n\n\n");
printf("\x1B[8A");
printf("\x1B[6n");
fflush(stdout);
for (;;)
{
ch = getchar();
if (ch == EOF) return 1;
if (ch == 0x1B) break;
}
if (scanf("[%d;%dR", &fancy->oy, &fancy->ox) != 2) return 1;
printf("\x1B[?1000h");
fflush(stdout);
/* begin setting up UCI bot */
moonfish_spawn(command, &fancy->in, &fancy->out, NULL);
fprintf(fancy->in, "uci\n");
moonfish_wait(fancy->out, "uciok");
for (;;)
{
value = strchr(*options, '=');
if (value == NULL) break;
fprintf(fancy->in, "setoption name %.*s value %s\n", (int) (value - *options), *options, value + 1);
options++;
}
fprintf(fancy->in, "ucinewgame\n");
moonfish_go(fancy);
/* start thread to communicate with the bot */
error = pthread_create(&thread, NULL, &moonfish_start, fancy);
if (error != 0)
{
fprintf(stderr, "%s: %s\n", fancy->argv0, strerror(error));
return 1;
}
/* main UI loop */
for (ch0 = 0 ; ch0 != EOF ; ch0 = getchar())
{
if (ch0 != 0x1B) continue;
ch0 = getchar();
if (ch0 == EOF) break;
if (ch0 != 0x5B) continue;
ch0 = getchar();
if (ch0 == EOF) break;
/* handle up arrow */
if (ch0 == 'A')
{
if (fancy->i == 0) continue;
pthread_mutex_lock(fancy->mutex);
fancy->i = 0;
moonfish_scroll(fancy);
moonfish_go(fancy);
pthread_mutex_unlock(fancy->mutex);
continue;
}
/* handle down arrow */
if (ch0 == 'B')
{
if (fancy->i == fancy->count - 1) continue;
pthread_mutex_lock(fancy->mutex);
fancy->i = fancy->count - 1;
moonfish_scroll(fancy);
moonfish_go(fancy);
pthread_mutex_unlock(fancy->mutex);
continue;
}
/* handle right arrow */
if (ch0 == 'C')
{
if (fancy->i == fancy->count - 1) continue;
pthread_mutex_lock(fancy->mutex);
fancy->i++;
moonfish_scroll(fancy);
moonfish_go(fancy);
pthread_mutex_unlock(fancy->mutex);
continue;
}
/* handle left arrow */
if (ch0 == 'D')
{
if (fancy->i == 0) continue;
pthread_mutex_lock(fancy->mutex);
fancy->i--;
moonfish_scroll(fancy);
moonfish_go(fancy);
pthread_mutex_unlock(fancy->mutex);
continue;
}
/* 'M' means "mouse button" */
/* (only handle them henceforth) */
if (ch0 != 'M') continue;
/* which mouse button? */
ch0 = getchar();
if (ch0 == EOF) break;
/* mouse 'x' coordinate */
ch = getchar();
if (ch == EOF) break;
x1 = ch - 0x21 - fancy->ox;
/* mouse 'y' coordinate */
ch = getchar();
if (ch == EOF) break;
y1 = ch - 0x21 - fancy->oy + 2;
/* handle scroll up */
if (ch0 == 0x60)
{
pthread_mutex_lock(fancy->mutex);
if (fancy->offset > 0)
{
fancy->offset--;
moonfish_fancy(fancy);
}
pthread_mutex_unlock(fancy->mutex);
continue;
}
/* handle scroll down */
if (ch0 == 0x61)
{
pthread_mutex_lock(fancy->mutex);
if (fancy->offset < fancy->count / 2 - 6)
{
fancy->offset++;
moonfish_fancy(fancy);
}
pthread_mutex_unlock(fancy->mutex);
continue;
}
/* "(+)" button clicked */
if (ch0 == 0x20 && y1 == 1 && x1 >= 21 && x1 <= 23)
{
pthread_mutex_lock(fancy->mutex);
moonfish_bump(fancy);
pthread_mutex_unlock(fancy->mutex);
continue;
}
/* move name clicked (on scoresheet) */
if (ch0 == 0x20 && y1 >= 2 && y1 <= 7 && x1 >= 21 && x1 <= 40)
{
pthread_mutex_lock(fancy->mutex);
i = (fancy->offset + y1) * 2 - 4;
if (fancy->plies[0].chess.white) i++;
if (x1 > 30) i++;
if (i < fancy->count)
{
fancy->i = i;
moonfish_go(fancy);
}
pthread_mutex_unlock(fancy->mutex);
continue;
}
/* "best move" button clicked */
if (ch0 == 0x20 && y1 == 8 && x1 >= 21 && x1 <= 40)
{
pthread_mutex_lock(fancy->mutex);
if (fancy->plies[fancy->i].best[0] != 0)
{
if (moonfish_from_uci(&fancy->plies[fancy->i].chess, &move, fancy->plies[fancy->i].best))
{
fprintf(stderr, "%s: invalid best move: %s\n", fancy->argv0, fancy->plies[fancy->i].best);
return 1;
}
moonfish_play(fancy, &move);
}
pthread_mutex_unlock(fancy->mutex);
continue;
}
/* only handle clicks on the board henceforth */
if (x1 < 2 || x1 > 17 || y1 < 1 || y1 > 8) continue;
x1 /= 2;
pthread_mutex_lock(fancy->mutex);
/* mouse down with no square selected: select the square under the mouse */
if (ch0 == 0x20 && fancy->x == 0)
{
fancy->x = x1;
fancy->y = y1;
moonfish_fancy(fancy);
pthread_mutex_unlock(fancy->mutex);
continue;
}
/* only handle cases where a square is selected henceforth */
if (fancy->x == 0)
{
pthread_mutex_unlock(fancy->mutex);
continue;
}
/* handle mouse down: if the clicked square is the selected square, deselect it */
if (ch0 == 0x20 && x1 == fancy->x && y1 == fancy->y)
{
fancy->x = 0;
moonfish_fancy(fancy);
pthread_mutex_unlock(fancy->mutex);
continue;
}
/* handle mouse up or mouse down: if it forms a valid move, play it on the board */
if (ch0 == 0x20 || ch0 == 0x23)
if (moonfish_move_from(&fancy->plies[fancy->i].chess, &move, fancy->x, fancy->y, x1, y1) == 0)
{
moonfish_play(fancy, &move);
pthread_mutex_unlock(fancy->mutex);
continue;
}
/* handle mouse down: when there isn't a valid move under the mouse, just select the new square */
if (ch0 == 0x20)
{
fancy->x = x1;
fancy->y = y1;
moonfish_fancy(fancy);
pthread_mutex_unlock(fancy->mutex);
continue;
}
pthread_mutex_unlock(fancy->mutex);
}
return 0;
}