testsAndMisc-archive/C/opening_learner/engine.c

280 lines
7.5 KiB
C
Raw Permalink Normal View History

2025-09-08 16:58:17 +02:00
#include "engine.h"
2025-11-01 20:11:45 +01:00
#include <errno.h>
2025-09-08 16:58:17 +02:00
#include <fcntl.h>
2025-11-01 20:11:45 +01:00
#include <limits.h>
2025-09-08 16:58:17 +02:00
#include <stdio.h>
#include <stdlib.h>
2025-11-01 20:11:45 +01:00
#include <string.h>
2025-09-08 16:58:17 +02:00
#include <sys/types.h>
#include <sys/wait.h>
2025-11-01 20:11:45 +01:00
#include <time.h>
#include <unistd.h>
static void sleep_millis(unsigned int milliseconds)
{
struct timespec req = {
.tv_sec = milliseconds / 1000,
.tv_nsec = (long)(milliseconds % 1000) * 1000000L,
};
(void)nanosleep(&req, NULL);
}
2025-09-08 16:58:17 +02:00
2025-11-01 20:11:45 +01:00
static bool spawn_process(const char *path, Engine *e)
{
2025-09-08 16:58:17 +02:00
int inpipe[2], outpipe[2];
2025-11-01 20:11:45 +01:00
if (pipe(inpipe) < 0 || pipe(outpipe) < 0)
return false;
2025-09-08 16:58:17 +02:00
pid_t pid = fork();
2025-11-01 20:11:45 +01:00
if (pid < 0)
return false;
if (pid == 0)
{
dup2(inpipe[0], STDIN_FILENO); // child stdin from inpipe[0]
2025-09-08 16:58:17 +02:00
dup2(outpipe[1], STDOUT_FILENO); // child stdout to outpipe[1]
dup2(outpipe[1], STDERR_FILENO);
2025-11-01 20:11:45 +01:00
close(inpipe[0]);
close(inpipe[1]);
close(outpipe[0]);
close(outpipe[1]);
execlp(path, path, (char *)NULL);
2025-09-08 16:58:17 +02:00
_exit(127);
}
// parent
2025-11-01 20:11:45 +01:00
close(inpipe[0]);
close(outpipe[1]);
e->pid = pid;
e->in_fd = inpipe[1];
e->out_fd = outpipe[0];
e->ready = false;
2025-09-08 16:58:17 +02:00
// make out_fd non-blocking for reads with polling
2025-11-01 20:11:45 +01:00
int flags = fcntl(e->out_fd, F_GETFL, 0);
fcntl(e->out_fd, F_SETFL, flags | O_NONBLOCK);
2025-09-08 16:58:17 +02:00
return true;
}
2025-11-01 20:11:45 +01:00
static bool try_start(Engine *e, const char *name)
{
if (!spawn_process(name, e))
return false;
2025-09-08 16:58:17 +02:00
// send UCI init
engine_cmd(e, "uci\n");
2025-11-01 20:11:45 +01:00
char buf[4096];
int attempts = 50; // ~5s total
while (attempts--)
{
sleep_millis(100);
ssize_t n = read(e->out_fd, buf, sizeof(buf) - 1);
if (n > 0)
{
buf[n] = '\0';
if (strstr(buf, "uciok"))
{
e->ready = true;
break;
}
2025-09-08 16:58:17 +02:00
}
}
2025-11-01 20:11:45 +01:00
if (!e->ready)
{
engine_stop(e);
return false;
}
2025-09-08 16:58:17 +02:00
engine_cmd(e, "isready\n");
2025-11-01 20:11:45 +01:00
attempts = 50;
while (attempts--)
{
sleep_millis(100);
ssize_t n = read(e->out_fd, buf, sizeof(buf) - 1);
if (n > 0)
{
buf[n] = '\0';
if (strstr(buf, "readyok"))
break;
}
2025-09-08 16:58:17 +02:00
}
return e->ready;
}
2025-11-01 20:11:45 +01:00
bool engine_start(Engine *e)
{
memset(e, 0, sizeof(*e));
e->pid = -1;
e->in_fd = -1;
e->out_fd = -1;
e->ready = false;
if (try_start(e, "stockfish"))
return true;
if (try_start(e, "asmfish"))
return true;
2025-09-08 16:58:17 +02:00
return false;
}
2025-11-01 20:11:45 +01:00
void engine_stop(Engine *e)
{
if (e->in_fd != -1)
{
write(e->in_fd, "quit\n", 5);
close(e->in_fd);
}
if (e->out_fd != -1)
close(e->out_fd);
if (e->pid > 0)
{
int status;
waitpid(e->pid, &status, 0);
}
e->pid = -1;
e->in_fd = e->out_fd = -1;
e->ready = false;
2025-09-08 16:58:17 +02:00
}
2025-11-01 20:11:45 +01:00
bool engine_cmd(Engine *e, const char *cmd)
{
if (e->in_fd == -1)
return false;
size_t len = strlen(cmd);
ssize_t n = write(e->in_fd, cmd, len);
if (n < 0)
{
2025-09-08 16:58:17 +02:00
return false;
}
return n == (ssize_t)len;
}
2025-11-01 20:11:45 +01:00
static void position_to_uci(const Position *pos, char *out, size_t outsz)
{
// Send starting position and moves list by comparing with startpos; for simplicity, use FEN
// always.
char fen[256];
chess_to_fen(pos, fen, sizeof(fen));
(void)snprintf(out, outsz, "position fen %s\n", fen);
2025-09-08 16:58:17 +02:00
}
2025-11-01 20:11:45 +01:00
size_t engine_get_top_moves(Engine *e, const Position *pos, EngineMove *out, size_t max)
{
if (!e->ready)
return 0;
char cmd[512];
position_to_uci(pos, cmd, sizeof(cmd));
engine_cmd(e, cmd);
2025-09-08 16:58:17 +02:00
// ask multiPV up to max (cap at 5 as requested)
2025-11-01 20:11:45 +01:00
size_t req = max;
if (req > 5)
req = 5;
char go[128];
(void)snprintf(go, sizeof(go), "setoption name MultiPV value %zu\n", req);
engine_cmd(e, go);
2025-09-08 16:58:17 +02:00
engine_cmd(e, "go movetime 400\n");
2025-11-01 20:11:45 +01:00
char buf[8192];
size_t count = 0;
int attempts = 50;
while (attempts--)
{
sleep_millis(100);
ssize_t n = read(e->out_fd, buf, sizeof(buf) - 1);
if (n <= 0)
continue;
buf[n] = '\0';
2025-09-08 16:58:17 +02:00
char *line = strtok(buf, "\n");
2025-11-01 20:11:45 +01:00
while (line)
{
if (strncmp(line, "info ", 5) == 0)
{
2025-09-08 16:58:17 +02:00
// parse "info ... multipv X score cp Y ... pv <uci>"
2025-11-01 20:11:45 +01:00
char *mpv = strstr(line, " multipv ");
char *score = strstr(line, " score ");
char *pv = strstr(line, " pv ");
if (mpv && score && pv)
{
char *idx_end = NULL;
long idx_long = strtol(mpv + 9, &idx_end, 10);
if (idx_end == mpv + 9 || idx_long < 1 || (size_t)idx_long > req)
{
line = strtok(NULL, "\n");
continue;
}
size_t idx = (size_t)idx_long;
if (idx >= 1 && idx <= req)
{
int cp = 0;
char *cp_loc = strstr(score, "cp ");
if (cp_loc)
{
char *cp_end = NULL;
long cp_long = strtol(cp_loc + 3, &cp_end, 10);
if (cp_end != cp_loc + 3 && cp_long >= INT_MIN && cp_long <= INT_MAX)
{
cp = (int)cp_long;
}
}
char mv[8] = {0};
if (sscanf(pv + 4, "%7s", mv) != 1)
{
line = strtok(NULL, "\n");
continue;
}
size_t i = idx - 1;
if (i < req)
{
out[i].score_cp = cp;
(void)snprintf(out[i].uci, sizeof(out[i].uci), "%s", mv);
if (i + 1 > count)
count = i + 1;
}
2025-09-08 16:58:17 +02:00
}
}
2025-11-01 20:11:45 +01:00
}
else if (strncmp(line, "bestmove ", 9) == 0)
{
attempts = 0;
break;
}
2025-09-08 16:58:17 +02:00
line = strtok(NULL, "\n");
}
}
// simple sort by score descending (best to worst), keeping empties at end
2025-11-01 20:11:45 +01:00
for (size_t i = 0; i < count; i++)
{
for (size_t j = i + 1; j < count; j++)
{
if (out[j].score_cp > out[i].score_cp)
{
EngineMove tmp = out[i];
out[i] = out[j];
out[j] = tmp;
}
2025-09-08 16:58:17 +02:00
}
}
return count;
}
2025-11-01 20:11:45 +01:00
bool engine_get_best_move(Engine *e, const Position *pos, char out_uci[8])
{
if (!e->ready)
return false;
char cmd[512];
position_to_uci(pos, cmd, sizeof(cmd));
engine_cmd(e, cmd);
2025-09-08 16:58:17 +02:00
engine_cmd(e, "go movetime 300\n");
2025-11-01 20:11:45 +01:00
char buf[4096];
int attempts = 50;
while (attempts--)
{
sleep_millis(100);
ssize_t n = read(e->out_fd, buf, sizeof(buf) - 1);
if (n <= 0)
continue;
buf[n] = '\0';
2025-09-08 16:58:17 +02:00
char *line = strtok(buf, "\n");
2025-11-01 20:11:45 +01:00
while (line)
{
if (strncmp(line, "bestmove ", 9) == 0)
{
return sscanf(line + 9, "%7s", out_uci) == 1;
}
2025-09-08 16:58:17 +02:00
line = strtok(NULL, "\n");
}
}
return false;
}