Fix engine status.
[cboard.git] / src / engine.c
blobc299f973cdc01bbbbde352af863cb9f2366b6757
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 #include "chess.h"
40 #include "conf.h"
41 #include "misc.h"
42 #include "strings.h"
43 #include "window.h"
44 #include "common.h"
45 #include "engine.h"
47 #ifdef WITH_DMALLOC
48 #include <dmalloc.h>
49 #endif
51 void send_to_engine(GAME *g, const char *format, ...)
53 va_list ap;
54 int len;
55 char *line;
56 int try = 0;
57 struct user_data_s *d = g->data;
59 if (!g->data || d->status == ENGINE_OFFLINE)
60 return;
62 d->status = ENGINE_THINKING;
63 update_status_window(*g);
64 update_panels();
65 doupdate();
67 va_start(ap, format);
68 #ifdef HAVE_VASPRINTF
69 len = vasprintf(&line, format, ap);
70 #else
71 line = Malloc(LINE_MAX);
72 len = vsnprintf(line, LINE_MAX, format, ap);
73 #endif
74 va_end(ap);
76 while (1) {
77 int n;
78 fd_set fds;
79 struct timeval tv;
81 FD_ZERO(&fds);
82 FD_SET(d->fd[ENGINE_OUT_FD], &fds);
84 tv.tv_sec = 0;
85 tv.tv_usec = 0;
87 if ((n = select(d->fd[ENGINE_OUT_FD] + 1, NULL, &fds, NULL, &tv)) > 0) {
88 if (FD_ISSET(d->fd[ENGINE_OUT_FD], &fds)) {
89 n = write(d->fd[ENGINE_OUT_FD], line, len);
91 if (n == -1) {
92 if (errno == EAGAIN)
93 continue;
95 if (kill(d->pid, 0) == -1) {
96 message(ERROR, ANYKEY, "Could not write to engine. "
97 "Process no longer exists.");
98 d->status = ENGINE_OFFLINE;
99 //update_status_window(NULL);
100 return;
103 message(ERROR, ANYKEY, "Attempt #%i. write(): %s", ++try,
104 strerror(errno));
105 continue;
108 if (len != n) {
109 message(NULL, ANYKEY, "try #%i: write() error to engine. "
110 "Expected %i, got %i.", ++try, len, n);
111 continue;
114 break;
117 else {
118 /* timeout */
122 free(line);
125 #ifndef UNIX98
126 /* From the Xboard and screen packages. */
127 static int get_pty(char *pty_name)
129 int i;
130 int fd;
132 for (i = 0; PTY_MAJOR[i]; i++) {
133 int n;
135 for (n = 0; PTY_MINOR[n]; n++) {
136 sprintf(pty_name, "%spty%c%c", _PATH_DEV, PTY_MAJOR[i],
137 PTY_MINOR[n]);
139 if ((fd = open(pty_name, O_RDWR | O_NOCTTY)) == -1)
140 continue;
142 sprintf(pty_name, "%stty%c%c", _PATH_DEV, PTY_MAJOR[i],
143 PTY_MINOR[n]);
145 if (access(pty_name, R_OK | W_OK) == -1) {
146 close(fd);
147 continue;
150 return fd;
154 return -1;
156 #endif
158 static char **parseargs(char *str)
160 char **pptr, *s;
161 char arg[255];
162 int n = 0;
163 int quote = 0;
164 int lastchar = 0;
165 int i;
167 if (!str)
168 return NULL;
170 if (!(pptr = malloc(sizeof(char *))))
171 return NULL;
173 for (i = 0, s = str; *s; lastchar = *s++) {
174 if ((*s == '\"' || *s == '\'') && lastchar != '\\') {
175 quote = (quote) ? 0 : 1;
176 continue;
179 if (*s == ' ' && !quote) {
180 arg[i] = 0;
181 pptr = realloc(pptr, (n + 2) * sizeof(char *));
182 pptr[n++] = strdup(arg);
183 arg[0] = i = 0;
184 continue;
187 if ((i + 1) == sizeof(arg))
188 continue;
190 arg[i++] = *s;
193 arg[i] = 0;
195 if (arg[0]) {
196 pptr = realloc(pptr, (n + 2) * sizeof(char *));
197 pptr[n++] = strdup(arg);
200 pptr[n] = NULL;
201 return pptr;
204 /* Is this dangerous if pty permissions are wrong? */
205 static pid_t init_chess_engine(GAME *g, char **args)
207 pid_t pid;
208 int from[2], to[2];
209 struct user_data_s *d = g->data;
210 #ifndef UNIX98
211 char pty[FILENAME_MAX];
213 if ((to[1] = get_pty(pty)) == -1) {
214 errno = 0;
215 return -1;
217 #else
218 if ((to[1] = open("/dev/ptmx", O_RDWR | O_NOCTTY)) == -1) {
219 return -1;
222 if (grantpt(to[1]) == -1) {
223 return -1;
226 if (unlockpt(to[1]) == -1) {
227 return -1;
229 #endif
231 from[0] = to[1];
232 errno = 0;
234 #ifndef UNIX98
235 if ((to[0] = open(pty, O_RDWR, 0)) == -1)
236 #else
237 if ((to[0] = open(ptsname(to[1]), O_RDWR, 0)) == -1)
238 #endif
239 return -1;
241 from[1] = to[0];
242 errno = 0;
244 switch ((pid = fork())) {
245 case -1:
246 return -2;
247 case 0:
248 dup2(to[0], STDIN_FILENO);
249 dup2(from[1], STDOUT_FILENO);
250 close(to[0]);
251 close(to[1]);
252 close(from[0]);
253 close(from[1]);
254 dup2(STDOUT_FILENO, STDERR_FILENO);
255 execvp(args[0], args);
256 _exit(EXIT_FAILURE);
257 default:
258 break;
261 sleep(1);
263 if (kill(pid, 0) == -1)
264 return -2;
266 close(to[0]);
267 close(from[1]);
269 d->fd[ENGINE_IN_FD] = from[0];
270 d->fd[ENGINE_OUT_FD] = to[1];
271 fcntl(d->fd[ENGINE_IN_FD], F_SETFL, O_NONBLOCK | O_DIRECT);
272 fcntl(d->fd[ENGINE_OUT_FD], F_SETFL, O_NONBLOCK | O_DIRECT);
273 d->pid = pid;
274 return 0;
277 void stop_engine(GAME *g)
279 struct user_data_s *d = g->data;
281 if (!d || d->status == ENGINE_OFFLINE)
282 return;
284 send_to_engine(g, "quit\n");
286 if (kill(d->pid, 0) != -1)
287 kill(d->pid, SIGTERM);
289 if (kill(d->pid, 0) != -1)
290 kill(d->pid, SIGKILL);
293 int start_chess_engine(GAME *g)
295 char **args;
296 int i;
297 int ret = 1;
298 struct user_data_s *d = NULL;
300 args = parseargs(config.engine_cmd);
302 if (!g->data) {
303 d = Calloc(1, sizeof(struct user_data_s));
304 g->data = d;
307 d->status = ENGINE_INITIALIZING;
308 update_status_window(*g);
309 update_panels();
310 doupdate();
312 switch (init_chess_engine(g, args)) {
313 case -1:
314 /* Pty allocation. */
315 message(ERROR, ANYKEY, "Could not allocate PTY");
316 d->status = ENGINE_OFFLINE;
317 break;
318 case -2:
319 /* Could not execute engine. */
320 message(ERROR, ANYKEY, "%s: %s", args[0], strerror(errno));
321 d->status = ENGINE_OFFLINE;
322 break;
323 default:
324 ret = 0;
325 d->status = ENGINE_READY;
326 break;
329 for (i = 0; args[i]; i++)
330 free(args[i]);
332 free(args);
333 update_status_window(*g);
334 return ret;
337 /* Once the PGN parser has been well tested, validate_move() from the human
338 * move can disappear.
340 void parse_gnuchess_line(GAME *g, char *str)
342 char m[MAX_SAN_MOVE_LEN + 1] = {0}, *p = m;
343 int count;
344 struct user_data_s *d = g->data;
346 /* Human move. Add it to the move history. */
347 if (sscanf(str, "%*d%*1[.]%*1[ ]%[a-zA-Z0-9+=#-]%n", m, &count) == 1) {
349 if (validate_move(g, m)) {
350 invalid_move(0, m);
351 return;
355 if (history_total(g->hp) == 0 && g->side == BLACK)
356 SET_FLAG(g->flags, GF_BLACK_OPENING);
358 history_add(g, p);
359 SET_FLAG(g->flags, GF_MODIFIED);
360 pgn_switch_turn(g);
361 str += count;
362 return;
365 /* Engine move. */
366 if (sscanf(str, "%*d%*1[.]%*1[ ]%*3[.]%*1[ ]%[a-zA-Z0-9+=#-]%n", m,
367 &count) == 1) {
368 /* Moves from the engine are in a2a4 format (Xboard protocol) so we
369 * need to convert them.
371 //FIXME
372 if ((p = pgn_a2a4tosan(g, g->b, m)) == NULL)
373 return;
375 if (pgn_validate_move(g, g->b, p)) {
376 invalid_move(0, p);
377 RETURN(d);
380 if (history_total(g->hp) == 0 && g->side == BLACK)
381 SET_FLAG(g->flags, GF_BLACK_OPENING);
383 history_add(g, p);
384 SET_FLAG(g->flags, GF_MODIFIED);
385 pgn_switch_turn(g);
386 str += count;
388 if (TEST_FLAG(g->flags, GF_GAMEOVER)) {
389 //history_update_board(g, g.htotal); FIXME
390 RETURN(d);
393 if (d && TEST_FLAG(d->flags, CF_ENGINE_LOOP)) {
394 send_to_engine(g, "go\n");
395 update_cursor(*g, g->hindex);
396 return;
399 RETURN(d);
402 if (TEST_FLAG(g->flags, GF_GAMEOVER)) {
403 //history_update_board(g); FIXME
404 RETURN(d);
407 /* Miscellaneous one-liners. */
409 /* The engine is now reading a FIFO. Dump what we need to it. */
410 // FIXME FEN
411 if (strncmp(str, "pgnload ", 8) == 0) {
412 if (save_pgn(config.fifo, 1, gindex)) {
413 oldhistorytotal = 0;
414 return;
416 else {
417 history_free(g->hp, g->hindex + 1);
418 CLEAR_FLAG(g->flags, GF_GAMEOVER);
419 SET_FLAG(g->flags, GF_MODIFIED);
422 return;
425 /* 'switch' command. */
426 if (strncmp(str, "White to move", 13) == 0) {
427 g->side = g->turn = WHITE;
428 RETURN(d);
430 else if (strncmp(str, "Black to move", 13) == 0) {
431 g->side = g->turn = BLACK;
432 RETURN(d);
435 /* Bad engine command or move. */
436 if (strncmp(str, "Illegal move: ", 14) == 0) {
437 RETURN(d);
441 static void parse_engine_line(GAME *g, char *line)
443 line = trim(line);
445 if (!*line)
446 return;
448 parse_gnuchess_line(g, line);
451 void parse_engine_output(GAME *g, char *str)
453 char buf[LINE_MAX], *p = buf;
455 while (*str) {
456 *p = '\0';
458 /* FIXME test this ("White ... : ", "Black ... : "). Needed for the
459 * 'g'o command. */
460 if (*str == ':') {
461 p = buf;
462 str++;
463 continue;
466 if (*str == '\n') {
467 *p = '\0';
468 parse_engine_line(g, buf);
469 str++;
470 p = buf;
471 continue;
474 *p++ = *str++;