Merge branch 'master' into utf8
[cboard.git] / src / engine.c
blobff41cf5d787422da3c9ddc8db9a34385d00dce94
1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
2 /*
3 Copyright (C) 2002-2013 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_NCURSES_H
47 #include <ncurses.h>
48 #elif defined(HAVE_CURSES_H)
49 #include <curses.h>
50 #endif
52 #ifdef HAVE_PANEL_H
53 #include <panel.h>
54 #endif
56 #ifdef HAVE_SYS_WAIT_H
57 #include <sys/wait.h>
58 #endif
60 #include "chess.h"
61 #include "conf.h"
62 #include "misc.h"
63 #include "strings.h"
64 #include "window.h"
65 #include "message.h"
66 #include "common.h"
67 #include "engine.h"
69 #ifdef WITH_DMALLOC
70 #include <dmalloc.h>
71 #endif
73 int send_signal_to_engine(pid_t pid, int sig)
75 if (kill(pid, sig) == -1)
76 return 1;
78 return 0;
81 int send_to_engine(GAME g, int status, const char *format, ...)
83 va_list ap;
84 int len;
85 char *line;
86 int try = 0;
87 struct userdata_s *d = g->data;
89 if (!d->engine || d->engine->status == ENGINE_OFFLINE ||
90 TEST_FLAG(d->flags, CF_HUMAN))
91 return 1;
93 va_start(ap, format);
94 #ifdef HAVE_VASPRINTF
95 len = vasprintf(&line, format, ap);
96 #else
97 line = Malloc(LINE_MAX);
98 len = vsnprintf(line, LINE_MAX, format, ap);
99 #endif
100 va_end(ap);
102 while (1) {
103 int n;
104 fd_set fds;
105 struct timeval tv;
107 FD_ZERO(&fds);
108 FD_SET(d->engine->fd[ENGINE_OUT_FD], &fds);
110 tv.tv_sec = 0;
111 tv.tv_usec = 0;
113 if ((n = select(d->engine->fd[ENGINE_OUT_FD] + 1, NULL, &fds, NULL,
114 &tv)) > 0) {
115 if (FD_ISSET(d->engine->fd[ENGINE_OUT_FD], &fds)) {
116 n = write(d->engine->fd[ENGINE_OUT_FD], line, len);
118 if (n == -1) {
119 if (errno == EAGAIN)
120 continue;
122 if (kill(d->engine->pid, 0) == -1) {
123 message(_("[ ERROR ]"), _("[ press any key to continue ]"), "Could not write to engine. "
124 "Process no longer exists.");
125 d->engine->status = ENGINE_OFFLINE;
126 return 1;
129 message(_("[ ERROR ]"), _("[ press any key to continue ]"), "Attempt #%i. write(): %s", ++try,
130 strerror(errno));
131 continue;
134 if (len != n) {
135 message(NULL, _("[ press any key to continue ]"), "try #%i: write() error to engine. "
136 "Expected %i, got %i.", ++try, len, n);
137 continue;
140 break;
143 else {
144 /* timeout */
148 d->engine->status = status;
149 free(line);
150 return 0;
153 #ifndef UNIX98
154 /* From the Xboard and screen packages. */
155 static int get_pty(char *pty_name)
157 int i;
158 int fd;
160 for (i = 0; PTY_MAJOR[i]; i++) {
161 int n;
163 for (n = 0; PTY_MINOR[n]; n++) {
164 sprintf(pty_name, "%spty%c%c", _PATH_DEV, PTY_MAJOR[i],
165 PTY_MINOR[n]);
167 if ((fd = open(pty_name, O_RDWR | O_NOCTTY)) == -1)
168 continue;
170 sprintf(pty_name, "%stty%c%c", _PATH_DEV, PTY_MAJOR[i],
171 PTY_MINOR[n]);
173 if (access(pty_name, R_OK | W_OK) == -1) {
174 close(fd);
175 continue;
178 return fd;
182 return -1;
184 #endif
186 static char **parseargs(char *str)
188 char **pptr, *s;
189 char arg[255];
190 int n = 0;
191 int quote = 0;
192 int lastchar = 0;
193 int i;
195 if (!str)
196 return NULL;
198 if (!(pptr = malloc(sizeof(char *))))
199 return NULL;
201 for (i = 0, s = str; *s; lastchar = *s++) {
202 if ((*s == '\"' || *s == '\'') && lastchar != '\\') {
203 quote = (quote) ? 0 : 1;
204 continue;
207 if (*s == ' ' && !quote) {
208 arg[i] = 0;
209 pptr = realloc(pptr, (n + 2) * sizeof(char *));
210 pptr[n++] = strdup(arg);
211 arg[0] = i = 0;
212 continue;
215 if ((i + 1) == sizeof(arg))
216 continue;
218 arg[i++] = *s;
221 arg[i] = 0;
223 if (arg[0]) {
224 pptr = realloc(pptr, (n + 2) * sizeof(char *));
225 pptr[n++] = strdup(arg);
228 pptr[n] = NULL;
229 return pptr;
232 int init_chess_engine(GAME g)
234 struct userdata_s *d = g->data;
236 if (start_chess_engine(g) > 0) {
237 d->sp.icon = 0;
238 return 1;
241 add_engine_command(g, ENGINE_READY, "setboard %s\n",
242 pgn_game_to_fen(g, d->b));
243 return 0;
246 /* Is this dangerous if pty permissions are wrong? */
247 static pid_t exec_chess_engine(GAME g, char **args)
249 pid_t pid;
250 int from[2], to[2];
251 struct userdata_s *d = g->data;
252 #ifndef UNIX98
253 char pty[FILENAME_MAX];
255 if ((to[1] = get_pty(pty)) == -1) {
256 errno = 0;
257 return -1;
259 #else
260 if ((to[1] = open("/dev/ptmx", O_RDWR | O_NOCTTY)) == -1) {
261 return -1;
264 if (grantpt(to[1]) == -1) {
265 return -1;
268 if (unlockpt(to[1]) == -1) {
269 return -1;
271 #endif
273 from[0] = to[1];
274 errno = 0;
276 #ifndef UNIX98
277 if ((to[0] = open(pty, O_RDWR, 0)) == -1)
278 #else
279 if ((to[0] = open(ptsname(to[1]), O_RDWR, 0)) == -1)
280 #endif
281 return -1;
283 from[1] = to[0];
284 errno = 0;
286 switch ((pid = fork())) {
287 case -1:
288 return -2;
289 case 0:
290 dup2(to[0], STDIN_FILENO);
291 dup2(from[1], STDOUT_FILENO);
292 close(to[0]);
293 close(to[1]);
294 close(from[0]);
295 close(from[1]);
296 dup2(STDOUT_FILENO, STDERR_FILENO);
297 execvp(args[0], args);
298 _exit(EXIT_FAILURE);
299 default:
300 break;
304 * FIXME shouldn't block here. When theres a high load average, the engine
305 * process will fail to start. Better to handle this in game_loop() and
306 * give up after a timer expires.
308 sleep(1);
310 if (send_signal_to_engine(pid, 0))
311 return -2;
313 close(to[0]);
314 close(from[1]);
316 d->engine->fd[ENGINE_IN_FD] = from[0];
317 d->engine->fd[ENGINE_OUT_FD] = to[1];
318 fcntl(d->engine->fd[ENGINE_IN_FD], F_SETFL, O_NONBLOCK);
319 fcntl(d->engine->fd[ENGINE_OUT_FD], F_SETFL, O_NONBLOCK);
320 d->engine->pid = pid;
321 return 0;
324 void stop_engine(GAME g)
326 struct userdata_s *d = g->data;
327 int s;
329 if (!d->engine || d->engine->pid == -1 || d->engine->status == ENGINE_OFFLINE)
330 return;
332 send_to_engine(g, ENGINE_READY, "quit\n");
334 if (!send_signal_to_engine(d->engine->pid, 0)) {
335 if (!send_signal_to_engine(d->engine->pid, SIGTERM))
336 send_signal_to_engine(d->engine->pid, SIGKILL);
339 waitpid(d->engine->pid, &s, 0);
340 d->engine->pid = -1;
341 d->engine->status = ENGINE_OFFLINE;
344 void set_engine_defaults(GAME g, char **init)
346 int i;
348 if (!init)
349 return;
351 for (i = 0; init[i]; i++)
352 add_engine_command(g, ENGINE_READY, "%s\n", init[i]);
355 int start_chess_engine(GAME g)
357 char **args;
358 int i;
359 int ret = 1;
360 struct userdata_s *d = g->data;
362 if (d->engine && d->engine->status != ENGINE_OFFLINE)
363 return -1;
365 args = parseargs(config.engine_cmd);
367 if (!d->engine)
368 d->engine = Calloc(1, sizeof(struct engine_s));
370 d->engine->status = ENGINE_INITIALIZING;
371 update_status_window(g);
372 update_panels();
373 doupdate();
375 switch (exec_chess_engine(g, args)) {
376 case -1:
377 /* Pty allocation. */
378 message(_("[ ERROR ]"), _("[ press any key to continue ]"), "Could not allocate PTY");
379 d->engine->status = ENGINE_OFFLINE;
380 break;
381 case -2:
382 /* Could not execute engine. */
383 message(_("[ ERROR ]"), _("[ press any key to continue ]"), "%s: %s", args[0], strerror(errno));
384 d->engine->status = ENGINE_OFFLINE;
385 break;
386 default:
387 ret = 0;
388 d->engine->status = ENGINE_READY;
389 set_engine_defaults(g, config.einit);
390 break;
393 for (i = 0; args[i]; i++)
394 free(args[i]);
396 free(args);
397 return ret;
400 static void parse_xboard_line(GAME g, char *str)
402 char m[MAX_SAN_MOVE_LEN + 1] = {0}, *p = m;
403 struct userdata_s *d = g->data;
404 int n;
405 char *frfr;
406 int count;
408 p = str;
410 // 1. a2a4 (gnuchess)
411 while (isdigit(*p))
412 p++;
414 // gnuchess echos our input move so we don't need the duplicate (above).
415 if (*p == '.' && *(p + 1) == ' ' && *(p + 2) != '.')
416 return;
418 // 1. ... a2a4 (gnuchess/engine move)
419 if (*p == '.' && *(p + 1) == ' ' && *(p + 2) == '.') {
420 while (*p == ' ' || *p == '.')
421 p++;
424 if (strncmp(str, "move ", 5) == 0)
425 p = str + 5;
427 if (strlen(p) > MAX_SAN_MOVE_LEN)
428 return;
430 // We should now have the real move which may be in SAN or frfr format.
431 if (sscanf(p, "%[0-9a-hprnqkxPRNBQKO+=#-]%n", m, &count) == 1) {
433 * For engine commands (the '|' key). Don't try and validate them.
435 if (count != strlen(p))
436 return;
438 if (TEST_FLAG(g->flags, GF_GAMEOVER)) {
439 gameover(g);
440 return;
443 if (strlen(m) < 2)
444 RETURN(d);
446 p = m + strlen(m) - 1;
448 // Test SAN or a2a4 format.
449 if (!isdigit(*p) && *p != 'O' && *p != '+' && *p != '#' &&
450 ((*(p - 1) != '=' || !isdigit(*(p - 1))) &&
451 pgn_piece_to_int(*p) == -1))
452 return;
454 p = m;
456 if ((n = pgn_parse_move(g, d->b, &p, &frfr)) != E_PGN_OK) {
457 invalid_move(d->n + 1, n, m);
458 RETURN(d);
461 free (frfr);
462 pgn_history_add(g, d->b, p);
463 SET_FLAG(d->flags, CF_MODIFIED);
464 pgn_switch_turn(g);
466 if (TEST_FLAG(d->flags, CF_ENGINE_LOOP)) {
467 update_cursor(g, g->hindex);
469 if (TEST_FLAG(g->flags, GF_GAMEOVER)) {
470 gameover(g);
471 return;
474 add_engine_command(g, ENGINE_THINKING, "go\n");
475 return;
478 if (g->side == g->turn)
479 RETURN(d);
481 d->engine->status = ENGINE_THINKING;
484 return;
487 void parse_engine_output(GAME g, char *str)
489 char buf[255], *p = buf;
491 while (*str) {
492 if (*str == '\n' || *str == '\r') {
493 *p = '\0';
495 if (*buf) {
496 append_enginebuf(g, buf);
497 parse_xboard_line(g, buf);
500 str++;
502 if (*str == '\n')
503 str++;
505 p = buf;
506 continue;
509 *p++ = *str++;
512 update_all(gp);
515 void send_engine_command(GAME g)
517 struct userdata_s *d = g->data;
518 struct queue_s **q = d->engine->queue;
519 int i;
521 if (!d->engine || !d->engine->queue)
522 return;
524 if (!q || !q[0])
525 return;
527 if (send_to_engine(g, q[0]->status, "%s", q[0]->line))
528 return;
530 free(q[0]->line);
531 free(q[0]);
533 for (i = 0; q[i]; i++) {
534 if (q[i+1])
535 q[i] = q[i+1];
536 else {
537 q[i] = NULL;
538 break;
543 void add_engine_command(GAME g, int s, char *fmt, ...)
545 va_list ap;
546 int i = 0;
547 struct userdata_s *d = g->data;
548 struct queue_s **q;
549 char *line;
551 if (!d->engine || d->engine->status == ENGINE_OFFLINE) {
552 if (init_chess_engine(g))
553 return;
556 q = d->engine->queue;
558 if (q)
559 for (i = 0; q[i]; i++);
561 q = Realloc(q, (i + 2) * sizeof(struct queue_s *));
562 va_start(ap, fmt);
563 #ifdef HAVE_VASPRINTF
564 vasprintf(&line, fmt, ap);
565 #else
566 line = Malloc(LINE_MAX + 1);
567 vsnprintf(line, LINE_MAX, fmt, ap);
568 #endif
569 va_end(ap);
570 q[i] = Malloc(sizeof(struct queue_s));
571 q[i]->line = line;
572 q[i++]->status = (s == -1) ? d->engine->status : s;
573 q[i] = NULL;
574 d->engine->queue = q;