parse_xboard_line() complains about the 'remove' command. Fixed.
[cboard.git] / src / engine.c
blob366385d27baa33f408897dcc66f72fbc64c11859
1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
2 /*
3 Copyright (C) 2002-2006 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 #include <stdio.h>
20 #include <stdlib.h>
21 #include <unistd.h>
22 #include <err.h>
23 #include <limits.h>
24 #include <string.h>
25 #include <sys/types.h>
26 #include <signal.h>
27 #include <sys/stat.h>
28 #include <sys/time.h>
29 #include <fcntl.h>
30 #include <ctype.h>
31 #include <stdarg.h>
32 #include <errno.h>
33 #include <paths.h>
35 #ifdef HAVE_CONFIG_H
36 #include <config.h>
37 #endif
39 #ifdef HAVE_SYS_WAIT_H
40 #include <sys/wait.h>
41 #endif
43 #include "chess.h"
44 #include "conf.h"
45 #include "misc.h"
46 #include "strings.h"
47 #include "window.h"
48 #include "common.h"
49 #include "engine.h"
51 #ifdef WITH_DMALLOC
52 #include <dmalloc.h>
53 #endif
55 int send_signal_to_engine(pid_t pid, int sig)
57 if (kill(pid, sig) == -1)
58 return 1;
60 return 0;
63 int send_to_engine(GAME *g, int status, const char *format, ...)
65 va_list ap;
66 int len;
67 char *line;
68 int try = 0;
69 struct userdata_s *d = g->data;
71 if (!d->engine || d->engine->status == ENGINE_OFFLINE ||
72 TEST_FLAG(d->flags, CF_HUMAN))
73 return 1;
75 if (send_signal_to_engine(d->engine->pid, SIGINT))
76 return 1;
78 va_start(ap, format);
79 #ifdef HAVE_VASPRINTF
80 len = vasprintf(&line, format, ap);
81 #else
82 line = Malloc(LINE_MAX);
83 len = vsnprintf(line, LINE_MAX, format, ap);
84 #endif
85 va_end(ap);
87 while (1) {
88 int n;
89 fd_set fds;
90 struct timeval tv;
92 FD_ZERO(&fds);
93 FD_SET(d->engine->fd[ENGINE_OUT_FD], &fds);
95 tv.tv_sec = 0;
96 tv.tv_usec = 0;
98 if ((n = select(d->engine->fd[ENGINE_OUT_FD] + 1, NULL, &fds, NULL,
99 &tv)) > 0) {
100 if (FD_ISSET(d->engine->fd[ENGINE_OUT_FD], &fds)) {
101 n = write(d->engine->fd[ENGINE_OUT_FD], line, len);
103 if (n == -1) {
104 if (errno == EAGAIN)
105 continue;
107 if (kill(d->engine->pid, 0) == -1) {
108 message(ERROR, ANYKEY, "Could not write to engine. "
109 "Process no longer exists.");
110 d->engine->status = ENGINE_OFFLINE;
111 return 1;
114 message(ERROR, ANYKEY, "Attempt #%i. write(): %s", ++try,
115 strerror(errno));
116 continue;
119 if (len != n) {
120 message(NULL, ANYKEY, "try #%i: write() error to engine. "
121 "Expected %i, got %i.", ++try, len, n);
122 continue;
125 break;
128 else {
129 /* timeout */
133 d->engine->status = status;
134 free(line);
135 return 0;
138 #ifndef UNIX98
139 /* From the Xboard and screen packages. */
140 static int get_pty(char *pty_name)
142 int i;
143 int fd;
145 for (i = 0; PTY_MAJOR[i]; i++) {
146 int n;
148 for (n = 0; PTY_MINOR[n]; n++) {
149 sprintf(pty_name, "%spty%c%c", _PATH_DEV, PTY_MAJOR[i],
150 PTY_MINOR[n]);
152 if ((fd = open(pty_name, O_RDWR | O_NOCTTY)) == -1)
153 continue;
155 sprintf(pty_name, "%stty%c%c", _PATH_DEV, PTY_MAJOR[i],
156 PTY_MINOR[n]);
158 if (access(pty_name, R_OK | W_OK) == -1) {
159 close(fd);
160 continue;
163 return fd;
167 return -1;
169 #endif
171 static char **parseargs(char *str)
173 char **pptr, *s;
174 char arg[255];
175 int n = 0;
176 int quote = 0;
177 int lastchar = 0;
178 int i;
180 if (!str)
181 return NULL;
183 if (!(pptr = malloc(sizeof(char *))))
184 return NULL;
186 for (i = 0, s = str; *s; lastchar = *s++) {
187 if ((*s == '\"' || *s == '\'') && lastchar != '\\') {
188 quote = (quote) ? 0 : 1;
189 continue;
192 if (*s == ' ' && !quote) {
193 arg[i] = 0;
194 pptr = realloc(pptr, (n + 2) * sizeof(char *));
195 pptr[n++] = strdup(arg);
196 arg[0] = i = 0;
197 continue;
200 if ((i + 1) == sizeof(arg))
201 continue;
203 arg[i++] = *s;
206 arg[i] = 0;
208 if (arg[0]) {
209 pptr = realloc(pptr, (n + 2) * sizeof(char *));
210 pptr[n++] = strdup(arg);
213 pptr[n] = NULL;
214 return pptr;
217 /* Is this dangerous if pty permissions are wrong? */
218 static pid_t init_chess_engine(GAME *g, char **args)
220 pid_t pid;
221 int from[2], to[2];
222 struct userdata_s *d = g->data;
223 #ifndef UNIX98
224 char pty[FILENAME_MAX];
226 if ((to[1] = get_pty(pty)) == -1) {
227 errno = 0;
228 return -1;
230 #else
231 if ((to[1] = open("/dev/ptmx", O_RDWR | O_NOCTTY)) == -1) {
232 return -1;
235 if (grantpt(to[1]) == -1) {
236 return -1;
239 if (unlockpt(to[1]) == -1) {
240 return -1;
242 #endif
244 from[0] = to[1];
245 errno = 0;
247 #ifndef UNIX98
248 if ((to[0] = open(pty, O_RDWR, 0)) == -1)
249 #else
250 if ((to[0] = open(ptsname(to[1]), O_RDWR, 0)) == -1)
251 #endif
252 return -1;
254 from[1] = to[0];
255 errno = 0;
257 switch ((pid = fork())) {
258 case -1:
259 return -2;
260 case 0:
261 dup2(to[0], STDIN_FILENO);
262 dup2(from[1], STDOUT_FILENO);
263 close(to[0]);
264 close(to[1]);
265 close(from[0]);
266 close(from[1]);
267 dup2(STDOUT_FILENO, STDERR_FILENO);
268 execvp(args[0], args);
269 _exit(EXIT_FAILURE);
270 default:
271 break;
274 sleep(1);
276 if (kill(pid, 0) == -1)
277 return -2;
279 close(to[0]);
280 close(from[1]);
282 d->engine->fd[ENGINE_IN_FD] = from[0];
283 d->engine->fd[ENGINE_OUT_FD] = to[1];
284 fcntl(d->engine->fd[ENGINE_IN_FD], F_SETFL, O_NONBLOCK | O_DIRECT);
285 fcntl(d->engine->fd[ENGINE_OUT_FD], F_SETFL, O_NONBLOCK | O_DIRECT);
286 d->engine->pid = pid;
287 return 0;
290 void stop_engine(GAME *g)
292 struct userdata_s *d = g->data;
293 int s;
295 if (!d->engine || d->engine->status == ENGINE_OFFLINE)
296 return;
298 send_to_engine(g, ENGINE_READY, "quit\n");
300 if (!send_signal_to_engine(d->engine->pid, 0)) {
301 if (!send_signal_to_engine(d->engine->pid, SIGTERM))
302 send_signal_to_engine(d->engine->pid, SIGKILL);
305 waitpid(d->engine->pid, &s, 0);
308 void set_engine_defaults(GAME *g, char **init)
310 int i;
312 if (!init)
313 return;
315 for (i = 0; init[i]; i++)
316 add_engine_command(g, ENGINE_READY, "%s\n", init[i]);
319 int start_chess_engine(GAME *g)
321 char **args;
322 int i;
323 int ret = 1;
324 struct userdata_s *d = g->data;
326 if (d->engine)
327 return -1;
329 args = parseargs(config.engine_cmd);
331 d->engine = Calloc(1, sizeof(struct engine_s));
332 d->engine->status = ENGINE_INITIALIZING;
333 update_status_window(*g);
334 refresh_all();
336 switch (init_chess_engine(g, args)) {
337 case -1:
338 /* Pty allocation. */
339 message(ERROR, ANYKEY, "Could not allocate PTY");
340 d->engine->status = ENGINE_OFFLINE;
341 break;
342 case -2:
343 /* Could not execute engine. */
344 message(ERROR, ANYKEY, "%s: %s", args[0], strerror(errno));
345 d->engine->status = ENGINE_OFFLINE;
346 break;
347 default:
348 ret = 0;
349 set_engine_defaults(g, config.einit);
350 d->engine->status = ENGINE_READY;
351 break;
354 for (i = 0; args[i]; i++)
355 free(args[i]);
357 free(args);
358 update_status_window(*g);
359 return ret;
362 static void parse_xboard_line(GAME *g, char *str)
364 char m[MAX_SAN_MOVE_LEN + 1] = {0}, *p = m;
365 struct userdata_s *d = g->data;
367 p = str;
369 // 1. a2a4 (gnuchess)
370 while (isdigit(*p))
371 p++;
373 // gnuchess echos our input move so we don't need the duplicate (above).
374 if (*p == '.' && *(p + 1) == ' ' && *(p + 2) != '.')
375 return;
377 // 1. ... a2a4 (gnuchess/engine move)
378 if (*p == '.' && *(p + 1) == ' ' && *(p + 2) == '.') {
379 while (*p == ' ' || *p == '.')
380 p++;
383 if (strncmp(str, "move ", 5) == 0)
384 p = str + 5;
386 if (strlen(p) > MAX_SAN_MOVE_LEN)
387 return;
389 // We should now have the real move which may be in SAN or frfr format.
390 if (sscanf(p, "%[0-9a-hprnqkxPRNBQKO+=#-]", m) == 1) {
391 if (TEST_FLAG(g->flags, GF_GAMEOVER))
392 RETURN(d);
394 if (strlen(m) < 2)
395 RETURN(d);
397 p = m + strlen(m) - 1;
399 // Test SAN or a2a4 format.
400 if (!isdigit(*p) && *p != 'O' && *p != '+' && *p != '#' &&
401 ((*(p - 1) != '=' || !isdigit(*(p - 1))) &&
402 pgn_piece_to_int(*p) == -1))
403 return;
405 p = m;
407 if (pgn_parse_move(g, d->b, &p) != E_PGN_OK) {
408 invalid_move(0, m);
409 RETURN(d);
412 pgn_history_add(g, p);
413 SET_FLAG(g->flags, GF_MODIFIED);
414 pgn_switch_turn(g);
416 if (TEST_FLAG(d->flags, CF_ENGINE_LOOP)) {
417 if (d->n == gindex)
418 update_cursor(*g, g->hindex);
420 if (TEST_FLAG(g->flags, GF_GAMEOVER))
421 RETURN(d);
423 add_engine_command(g, ENGINE_THINKING, "go\n");
426 if (g->side == g->turn)
427 RETURN(d);
430 return;
433 void parse_engine_output(GAME *g, char *str)
435 char buf[255], *p = buf;
437 while (*str) {
438 if (*str == '\n' || *str == '\r') {
439 *p = '\0';
441 if (*buf) {
442 append_enginebuf(buf);
443 parse_xboard_line(g, buf);
446 str++;
448 if (*str == '\n')
449 str++;
451 p = buf;
452 continue;
455 *p++ = *str++;
459 void send_engine_command(GAME *g)
461 struct userdata_s *d = g->data;
462 struct queue_s **q = d->engine->queue;
463 int i;
465 if (!d->engine || !d->engine->queue)
466 return;
468 if (!q || !q[0])
469 return;
471 if (send_to_engine(g, q[0]->status, "%s", q[0]->line) == 0) {
472 if (d->n == gindex)
473 update_status_window(*g);
476 free(q[0]->line);
477 free(q[0]);
479 for (i = 0; q[i]; i++) {
480 if (q[i+1])
481 q[i] = q[i+1];
482 else {
483 q[i] = NULL;
484 break;
489 void add_engine_command(GAME *g, int s, char *fmt, ...)
491 va_list ap;
492 int i = 0;
493 struct userdata_s *d = g->data;
494 struct queue_s **q;
495 char *line;
497 if (!d->engine)
498 return;
500 q = d->engine->queue;
502 if (q)
503 for (i = 0; q[i]; i++);
505 q = Realloc(q, (i + 2) * sizeof(struct queue_s *));
506 va_start(ap, fmt);
507 #ifdef HAVE_VASPRINTF
508 vasprintf(&line, fmt, ap);
509 #else
510 line = Malloc(LINE_MAX + 1);
511 vsnprintf(line, LINE_MAX, fmt, ap);
512 #endif
513 va_end(ap);
514 q[i] = Malloc(sizeof(struct queue_s));
515 q[i]->line = line;
516 q[i++]->status = (s == -1) ? d->engine->status : s;
517 q[i] = NULL;
518 d->engine->queue = q;