Update copyright year.
[cboard.git] / src / engine.c
blob6df2d2a16c1dae7cb8a6eff9419f7a4d440ee9ac
1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
2 /*
3 Copyright (C) 2002-2024 Ben Kibbey <bjk@luxsci.net>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 #ifdef HAVE_CONFIG_H
20 #include <config.h>
21 #endif
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <unistd.h>
26 #include <limits.h>
27 #include <string.h>
28 #include <sys/types.h>
29 #include <signal.h>
30 #include <sys/stat.h>
31 #include <sys/time.h>
32 #include <fcntl.h>
33 #include <ctype.h>
34 #include <stdarg.h>
35 #include <errno.h>
36 #include <err.h>
38 #ifdef HAVE_PATHS_H
39 #include <paths.h>
40 #endif
42 #ifdef HAVE_STDARG_H
43 #include <stdarg.h>
44 #endif
46 #ifdef HAVE_SYS_WAIT_H
47 #include <sys/wait.h>
48 #endif
50 #include "common.h"
51 #include "conf.h"
52 #include "misc.h"
53 #include "strings.h"
54 #include "window.h"
55 #include "message.h"
56 #include "engine.h"
58 int
59 send_signal_to_engine (pid_t pid, int sig)
61 if (kill (pid, sig) == -1)
62 return 1;
64 return 0;
67 int
68 send_to_engine (GAME g, int status, const char *format, ...)
70 va_list ap;
71 int len;
72 char *line;
73 struct userdata_s *d = g->data;
75 if (!d->engine || d->engine->status == ENGINE_OFFLINE ||
76 TEST_FLAG (d->flags, CF_HUMAN))
77 return 1;
79 va_start (ap, format);
80 #ifdef HAVE_VASPRINTF
81 len = vasprintf (&line, format, ap);
82 #else
83 line = Malloc (LINE_MAX);
84 len = vsnprintf (line, LINE_MAX, format, ap);
85 #endif
86 va_end (ap);
88 while (1)
90 int n;
91 fd_set fds;
92 struct timeval tv;
94 FD_ZERO (&fds);
95 FD_SET (d->engine->fd[ENGINE_OUT_FD], &fds);
97 tv.tv_sec = 0;
98 tv.tv_usec = 0;
100 if ((n = select (d->engine->fd[ENGINE_OUT_FD] + 1, NULL, &fds, NULL,
101 &tv)) > 0)
103 if (FD_ISSET (d->engine->fd[ENGINE_OUT_FD], &fds))
105 n = write (d->engine->fd[ENGINE_OUT_FD], line, len);
107 if (n == -1)
109 if (errno == EAGAIN)
111 usleep (50000);
112 continue;
115 if (kill (d->engine->pid, 0) == -1)
117 message (ERROR_STR, ANY_KEY_STR,
118 _("Could not write to engine. "
119 "Process no longer exists."));
120 d->engine->status = ENGINE_OFFLINE;
121 break;
124 message (ERROR_STR, ANY_KEY_STR,
125 "write() error to engine: %s", strerror (errno));
126 d->engine->status = ENGINE_OFFLINE;
127 break;
130 if (len != n)
132 message (NULL, ANY_KEY_STR,
134 ("short write() count to engine. Expected %i, got %i."),
135 len, n);
136 d->engine->status = ENGINE_OFFLINE;
137 break;
140 break;
143 else
145 /* timeout */
149 if (d->engine->status == ENGINE_OFFLINE)
150 stop_engine (g);
151 else
152 d->engine->status = status;
154 free (line);
155 return d->engine->status == ENGINE_OFFLINE ? 1 : 0;
158 #ifndef UNIX98
159 /* From the Xboard and screen packages. */
160 static int
161 get_pty (char *pty_name)
163 int i;
164 int fd;
166 for (i = 0; PTY_MAJOR[i]; i++)
168 int n;
170 for (n = 0; PTY_MINOR[n]; n++)
172 sprintf (pty_name, "%spty%c%c", _PATH_DEV, PTY_MAJOR[i],
173 PTY_MINOR[n]);
175 if ((fd = open (pty_name, O_RDWR | O_NOCTTY)) == -1)
176 continue;
178 sprintf (pty_name, "%stty%c%c", _PATH_DEV, PTY_MAJOR[i],
179 PTY_MINOR[n]);
181 if (access (pty_name, R_OK | W_OK) == -1)
183 close (fd);
184 continue;
187 return fd;
191 return -1;
193 #endif
195 static char **
196 parseargs (char *str)
198 char **pptr, *s;
199 char arg[255];
200 int n = 0;
201 int quote = 0;
202 int lastchar = 0;
203 int i;
205 if (!str)
206 return NULL;
208 if (!(pptr = Malloc (sizeof (char *))))
209 return NULL;
211 for (i = 0, s = str; *s; lastchar = *s++)
213 if ((*s == '\"' || *s == '\'') && lastchar != '\\')
215 quote = (quote) ? 0 : 1;
216 continue;
219 if (*s == ' ' && !quote)
221 arg[i] = 0;
222 pptr = Realloc (pptr, (n + 2) * sizeof (char *));
223 pptr[n++] = strdup (arg);
224 arg[0] = i = 0;
225 continue;
228 if ((i + 1) == sizeof (arg))
229 continue;
231 arg[i++] = *s;
234 arg[i] = 0;
236 if (arg[0])
238 pptr = Realloc (pptr, (n + 2) * sizeof (char *));
239 pptr[n++] = strdup (arg);
242 pptr[n] = NULL;
243 return pptr;
247 init_chess_engine (GAME g)
249 struct userdata_s *d = g->data;
250 char *tmp;
252 if (start_chess_engine (g) > 0)
254 d->sp.icon = 0;
255 return 1;
258 tmp = pgn_game_to_fen (g, d->b);
259 add_engine_command (g, ENGINE_READY, "setboard %s\n", tmp);
260 free (tmp);
262 if (config.fmpolyglot && !TEST_FLAG (d->flags, CF_HUMAN) )
264 add_engine_command (g, ENGINE_READY, "%s\n",
265 g->side == WHITE ? "white" : "black");
266 if (g->turn == WHITE && g->side == BLACK)
267 add_engine_command (g, ENGINE_READY, "%s\n", "go");
268 else if (g->turn == BLACK && g->side == WHITE)
269 add_engine_command (g, ENGINE_READY, "%s\n", "go");
272 return 0;
275 /* Is this dangerous if pty permissions are wrong? */
276 static pid_t
277 exec_chess_engine (GAME g, char **args)
279 pid_t pid;
280 int from[2], to[2];
281 struct userdata_s *d = g->data;
282 int tries = config.engine_timeout;
283 #ifndef UNIX98
284 char pty[FILENAME_MAX];
286 if ((to[1] = get_pty (pty)) == -1)
288 errno = 0;
289 return -1;
291 #else
292 if ((to[1] = open ("/dev/ptmx", O_RDWR | O_NOCTTY)) == -1)
294 return -1;
297 if (grantpt (to[1]) == -1)
299 return -1;
302 if (unlockpt (to[1]) == -1)
304 return -1;
306 #endif
308 from[0] = to[1];
309 errno = 0;
311 #ifndef UNIX98
312 if ((to[0] = open (pty, O_RDWR, 0)) == -1)
313 #else
314 if ((to[0] = open (ptsname (to[1]), O_RDWR | O_NOCTTY, 0)) == -1)
315 #endif
316 return -1;
318 from[1] = to[0];
319 errno = 0;
321 switch ((pid = fork ()))
323 case -1:
324 return -2;
325 case 0:
326 dup2 (to[0], STDIN_FILENO);
327 dup2 (from[1], STDOUT_FILENO);
328 close (to[0]);
329 close (to[1]);
330 close (from[0]);
331 close (from[1]);
332 dup2 (STDOUT_FILENO, STDERR_FILENO);
333 execvp (args[0], args);
334 _exit (EXIT_FAILURE);
335 default:
336 break;
339 while (tries--)
341 if (send_signal_to_engine (pid, 0) && !tries)
342 return -2;
343 else
344 break;
347 close (to[0]);
348 close (from[1]);
350 d->engine->fd[ENGINE_IN_FD] = from[0];
351 d->engine->fd[ENGINE_OUT_FD] = to[1];
353 if (fcntl (d->engine->fd[ENGINE_IN_FD], F_SETFL, O_NONBLOCK) == -1)
354 return -2;
356 if (fcntl (d->engine->fd[ENGINE_OUT_FD], F_SETFL, O_NONBLOCK) == -1)
357 return -2;
359 d->engine->pid = pid;
360 return 0;
363 void
364 stop_engine (GAME g)
366 struct userdata_s *d = g->data;
367 int s;
369 if (!d->engine || d->engine->pid == -1
370 || d->engine->status == ENGINE_OFFLINE)
371 return;
373 send_to_engine (g, ENGINE_READY, "quit\n");
375 if (!send_signal_to_engine (d->engine->pid, 0))
377 if (!send_signal_to_engine (d->engine->pid, SIGTERM))
378 send_signal_to_engine (d->engine->pid, SIGKILL);
381 waitpid (d->engine->pid, &s, WNOHANG);
382 d->engine->pid = -1;
383 d->engine->status = ENGINE_OFFLINE;
386 void
387 set_engine_defaults (GAME g, wchar_t ** init)
389 int i;
391 if (!init)
392 return;
394 for (i = 0; init[i]; i++)
395 add_engine_command (g, ENGINE_READY, "%ls\n", init[i]);
399 start_chess_engine (GAME g)
401 char **args;
402 int i;
403 int ret = 1;
404 struct userdata_s *d = g->data;
406 if (d->engine && d->engine->status != ENGINE_OFFLINE)
407 return -1;
409 args = parseargs (config.engine_cmd);
411 if (!d->engine)
412 d->engine = Calloc (1, sizeof (struct engine_s));
414 d->engine->status = ENGINE_INITIALIZING;
415 update_status_window (g);
416 update_panels ();
417 doupdate ();
419 switch (exec_chess_engine (g, args))
421 case -1:
422 /* Pty allocation. */
423 message (ERROR_STR, ANY_KEY_STR, "Could not allocate PTY");
424 d->engine->status = ENGINE_OFFLINE;
425 break;
426 case -2:
427 /* Could not execute engine. */
428 message (ERROR_STR, ANY_KEY_STR, "%s: %s", args[0], strerror (errno));
429 d->engine->status = ENGINE_OFFLINE;
430 break;
431 default:
432 ret = 0;
433 d->engine->status = ENGINE_READY;
434 set_engine_defaults (g, config.einit);
435 break;
438 for (i = 0; args[i]; i++)
439 free (args[i]);
441 free (args);
442 return ret;
445 static void
446 parse_xboard_line (GAME g, char *str)
448 char m[MAX_SAN_MOVE_LEN + 1] = { 0 }, *p = m;
449 struct userdata_s *d = g->data;
450 int n;
451 char *frfr;
452 int count;
454 p = str;
456 // 1. a2a4 (gnuchess)
457 while (isdigit (*p))
458 p++;
460 // gnuchess echos our input move so we don't need the duplicate (above).
461 if (*p == '.' && *(p + 1) == ' ' && *(p + 2) != '.')
462 return;
464 // 1. ... a2a4 (gnuchess/engine move)
465 if (*p == '.' && *(p + 1) == ' ' && *(p + 2) == '.')
467 while (*p == ' ' || *p == '.')
468 p++;
471 if (strncmp (str, "move ", 5) == 0)
472 p = str + 5;
474 if (strlen (p) > MAX_SAN_MOVE_LEN)
475 return;
477 // We should now have the real move which may be in SAN or frfr format.
478 if (sscanf (p, "%[0-9a-hprnqkxPRNBQKO+=#-]%n", m, &count) == 1)
480 char *buf = NULL;
483 * For engine commands (the '|' key). Don't try and validate them.
485 if (count != strlen (p))
486 return;
488 if (TEST_FLAG (g->flags, GF_GAMEOVER))
490 gameover (g);
491 return;
494 if (strlen (m) < 2)
495 RETURN (d);
497 p = m + strlen (m) - 1;
499 // Test SAN or a2a4 format.
500 if (!isdigit (*p) && *p != 'O' && *p != '+' && *p != '#'
501 && ((*(p - 1) != '=' || !isdigit (*(p - 1)))
502 && pgn_piece_to_int (*p) == -1))
503 return;
505 buf = strdup (m);
506 if ((n = pgn_parse_move (g, d->b, &buf, &frfr)) != E_PGN_OK)
508 invalid_move (d->n + 1, n, m);
509 RETURN (d);
512 strcpy (d->pm_frfr, frfr);
514 free (frfr);
515 pgn_history_add (g, d->b, buf);
516 SET_FLAG (d->flags, CF_MODIFIED);
517 pgn_switch_turn (g);
519 if (TEST_FLAG (d->flags, CF_ENGINE_LOOP))
521 free (buf);
522 update_cursor (g, g->hindex);
524 if (TEST_FLAG (g->flags, GF_GAMEOVER))
526 gameover (g);
527 return;
530 add_engine_command (g, ENGINE_THINKING, "go\n");
531 return;
534 if (TEST_FLAG (gp->flags, GF_GAMEOVER))
536 free (buf);
537 gameover (g);
538 return;
541 if (g->side == g->turn)
543 if (config.turn_cmd)
545 char *cmd = string_replace (config.turn_cmd, "%m", buf);
547 switch (fork ())
549 case 0:
550 system (cmd);
551 _exit (0);
552 case -1:
553 break;
554 default:
555 break;
558 free (cmd);
561 free (buf);
562 RETURN (d);
565 free (buf);
566 d->engine->status = ENGINE_THINKING;
569 return;
572 void
573 parse_engine_output (GAME g, char *str)
575 char buf[255], *p = buf;
577 while (*str)
579 if (*str == '\n' || *str == '\r')
581 *p = '\0';
583 if (*buf)
585 append_enginebuf (g, buf);
586 parse_xboard_line (g, buf);
589 str++;
591 if (*str == '\n')
592 str++;
594 p = buf;
595 continue;
598 *p++ = *str++;
601 update_all (gp);
604 void
605 send_engine_command (GAME g)
607 struct userdata_s *d = g->data;
608 struct queue_s **q;
609 int i;
611 if (!d->engine || !d->engine->queue)
612 return;
614 q = d->engine->queue;
616 if (send_to_engine (g, q[0]->status, "%s", q[0]->line))
617 return;
619 free (q[0]->line);
620 free (q[0]);
622 for (i = 0; q[i]; i++)
624 if (q[i + 1])
625 q[i] = q[i + 1];
626 else
628 q[i] = NULL;
629 break;
633 if (!q[0])
635 free (d->engine->queue);
636 d->engine->queue = NULL;
640 void
641 add_engine_command (GAME g, int s, const char *fmt, ...)
643 va_list ap;
644 int i = 0;
645 struct userdata_s *d = g->data;
646 struct queue_s **q = NULL;
647 char *line;
649 if (!d->engine || d->engine->status == ENGINE_OFFLINE)
651 if (init_chess_engine (g))
652 return;
655 if (!d->engine)
656 return;
658 q = d->engine->queue;
660 if (q)
661 for (i = 0; q[i]; i++);
663 q = Realloc (q, (i + 2) * sizeof (struct queue_s *));
664 va_start (ap, fmt);
665 #ifdef HAVE_VASPRINTF
666 vasprintf (&line, fmt, ap);
667 #else
668 line = Malloc (LINE_MAX + 1);
669 vsnprintf (line, LINE_MAX, fmt, ap);
670 #endif
671 va_end (ap);
672 q[i] = Malloc (sizeof (struct queue_s));
673 q[i]->line = line;
674 q[i++]->status = (s == -1) ? d->engine->status : s;
675 q[i] = NULL;
676 d->engine->queue = q;