1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
3 Copyright (C) 2002-2015 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
28 #include <sys/types.h>
46 #ifdef HAVE_SYS_WAIT_H
58 int send_signal_to_engine(pid_t pid
, int sig
)
60 if (kill(pid
, sig
) == -1)
66 int send_to_engine(GAME g
, int status
, const char *format
, ...)
71 struct userdata_s
*d
= g
->data
;
73 if (!d
->engine
|| d
->engine
->status
== ENGINE_OFFLINE
||
74 TEST_FLAG(d
->flags
, CF_HUMAN
))
79 len
= vasprintf(&line
, format
, ap
);
81 line
= Malloc(LINE_MAX
);
82 len
= vsnprintf(line
, LINE_MAX
, format
, ap
);
92 FD_SET(d
->engine
->fd
[ENGINE_OUT_FD
], &fds
);
97 if ((n
= select(d
->engine
->fd
[ENGINE_OUT_FD
] + 1, NULL
, &fds
, NULL
,
99 if (FD_ISSET(d
->engine
->fd
[ENGINE_OUT_FD
], &fds
)) {
100 n
= write(d
->engine
->fd
[ENGINE_OUT_FD
], line
, len
);
103 if (errno
== EAGAIN
) {
108 if (kill(d
->engine
->pid
, 0) == -1) {
109 message(ERROR_STR
, ANY_KEY_STR
,
110 _("Could not write to engine. "
111 "Process no longer exists."));
112 d
->engine
->status
= ENGINE_OFFLINE
;
116 message(ERROR_STR
, ANY_KEY_STR
,
117 "write() error to engine: %s", strerror(errno
));
118 d
->engine
->status
= ENGINE_OFFLINE
;
123 message(NULL
, ANY_KEY_STR
,
124 _("short write() count to engine. Expected %i, got %i."),
126 d
->engine
->status
= ENGINE_OFFLINE
;
138 if (d
->engine
->status
== ENGINE_OFFLINE
)
141 d
->engine
->status
= status
;
144 return d
->engine
->status
== ENGINE_OFFLINE
? 1 : 0;
148 /* From the Xboard and screen packages. */
149 static int get_pty(char *pty_name
)
154 for (i
= 0; PTY_MAJOR
[i
]; i
++) {
157 for (n
= 0; PTY_MINOR
[n
]; n
++) {
158 sprintf(pty_name
, "%spty%c%c", _PATH_DEV
, PTY_MAJOR
[i
],
161 if ((fd
= open(pty_name
, O_RDWR
| O_NOCTTY
)) == -1)
164 sprintf(pty_name
, "%stty%c%c", _PATH_DEV
, PTY_MAJOR
[i
],
167 if (access(pty_name
, R_OK
| W_OK
) == -1) {
180 static char **parseargs(char *str
)
192 if (!(pptr
= malloc(sizeof(char *))))
195 for (i
= 0, s
= str
; *s
; lastchar
= *s
++) {
196 if ((*s
== '\"' || *s
== '\'') && lastchar
!= '\\') {
197 quote
= (quote
) ? 0 : 1;
201 if (*s
== ' ' && !quote
) {
203 pptr
= realloc(pptr
, (n
+ 2) * sizeof(char *));
204 pptr
[n
++] = strdup(arg
);
209 if ((i
+ 1) == sizeof(arg
))
218 pptr
= realloc(pptr
, (n
+ 2) * sizeof(char *));
219 pptr
[n
++] = strdup(arg
);
226 int init_chess_engine(GAME g
)
228 struct userdata_s
*d
= g
->data
;
231 if (start_chess_engine(g
) > 0) {
236 tmp
= pgn_game_to_fen(g
, d
->b
);
237 add_engine_command(g
, ENGINE_READY
, "setboard %s\n", tmp
);
242 /* Is this dangerous if pty permissions are wrong? */
243 static pid_t
exec_chess_engine(GAME g
, char **args
)
247 struct userdata_s
*d
= g
->data
;
249 char pty
[FILENAME_MAX
];
251 if ((to
[1] = get_pty(pty
)) == -1) {
256 if ((to
[1] = open("/dev/ptmx", O_RDWR
| O_NOCTTY
)) == -1) {
260 if (grantpt(to
[1]) == -1) {
264 if (unlockpt(to
[1]) == -1) {
273 if ((to
[0] = open(pty
, O_RDWR
, 0)) == -1)
275 if ((to
[0] = open(ptsname(to
[1]), O_RDWR
| O_NOCTTY
, 0)) == -1)
282 switch ((pid
= fork())) {
286 dup2(to
[0], STDIN_FILENO
);
287 dup2(from
[1], STDOUT_FILENO
);
292 dup2(STDOUT_FILENO
, STDERR_FILENO
);
293 execvp(args
[0], args
);
300 * FIXME shouldn't block here. When theres a high load average, the engine
301 * process will fail to start. Better to handle this in game_loop() and
302 * give up after a timer expires.
306 if (send_signal_to_engine(pid
, 0))
312 d
->engine
->fd
[ENGINE_IN_FD
] = from
[0];
313 d
->engine
->fd
[ENGINE_OUT_FD
] = to
[1];
315 if (fcntl(d
->engine
->fd
[ENGINE_IN_FD
], F_SETFL
, O_NONBLOCK
) == -1)
318 if (fcntl(d
->engine
->fd
[ENGINE_OUT_FD
], F_SETFL
, O_NONBLOCK
) == -1)
321 d
->engine
->pid
= pid
;
325 void stop_engine(GAME g
)
327 struct userdata_s
*d
= g
->data
;
330 if (!d
->engine
|| d
->engine
->pid
== -1 || d
->engine
->status
== ENGINE_OFFLINE
)
333 send_to_engine(g
, ENGINE_READY
, "quit\n");
335 if (!send_signal_to_engine(d
->engine
->pid
, 0)) {
336 if (!send_signal_to_engine(d
->engine
->pid
, SIGTERM
))
337 send_signal_to_engine(d
->engine
->pid
, SIGKILL
);
340 waitpid(d
->engine
->pid
, &s
, WNOHANG
);
342 d
->engine
->status
= ENGINE_OFFLINE
;
345 void set_engine_defaults(GAME g
, wchar_t **init
)
352 for (i
= 0; init
[i
]; i
++)
353 add_engine_command(g
, ENGINE_READY
, "%ls\n", init
[i
]);
356 int start_chess_engine(GAME g
)
361 struct userdata_s
*d
= g
->data
;
363 if (d
->engine
&& d
->engine
->status
!= ENGINE_OFFLINE
)
366 args
= parseargs(config
.engine_cmd
);
369 d
->engine
= Calloc(1, sizeof(struct engine_s
));
371 d
->engine
->status
= ENGINE_INITIALIZING
;
372 update_status_window(g
);
376 switch (exec_chess_engine(g
, args
)) {
378 /* Pty allocation. */
379 message(ERROR_STR
, ANY_KEY_STR
, "Could not allocate PTY");
380 d
->engine
->status
= ENGINE_OFFLINE
;
383 /* Could not execute engine. */
384 message(ERROR_STR
, ANY_KEY_STR
, "%s: %s", args
[0], strerror(errno
));
385 d
->engine
->status
= ENGINE_OFFLINE
;
389 d
->engine
->status
= ENGINE_READY
;
390 set_engine_defaults(g
, config
.einit
);
394 for (i
= 0; args
[i
]; i
++)
401 static void parse_xboard_line(GAME g
, char *str
)
403 char m
[MAX_SAN_MOVE_LEN
+ 1] = {0}, *p
= m
;
404 struct userdata_s
*d
= g
->data
;
411 // 1. a2a4 (gnuchess)
415 // gnuchess echos our input move so we don't need the duplicate (above).
416 if (*p
== '.' && *(p
+ 1) == ' ' && *(p
+ 2) != '.')
419 // 1. ... a2a4 (gnuchess/engine move)
420 if (*p
== '.' && *(p
+ 1) == ' ' && *(p
+ 2) == '.') {
421 while (*p
== ' ' || *p
== '.')
425 if (strncmp(str
, "move ", 5) == 0)
428 if (strlen(p
) > MAX_SAN_MOVE_LEN
)
431 // We should now have the real move which may be in SAN or frfr format.
432 if (sscanf(p
, "%[0-9a-hprnqkxPRNBQKO+=#-]%n", m
, &count
) == 1) {
436 * For engine commands (the '|' key). Don't try and validate them.
438 if (count
!= strlen(p
))
441 if (TEST_FLAG(g
->flags
, GF_GAMEOVER
)) {
449 p
= m
+ strlen(m
) - 1;
451 // Test SAN or a2a4 format.
452 if (!isdigit(*p
) && *p
!= 'O' && *p
!= '+' && *p
!= '#'
453 && ((*(p
- 1) != '=' || !isdigit(*(p
- 1)))
454 && pgn_piece_to_int(*p
) == -1))
458 if ((n
= pgn_parse_move(g
, d
->b
, &buf
, &frfr
)) != E_PGN_OK
) {
459 invalid_move(d
->n
+ 1, n
, m
);
463 strcpy(d
->pm_frfr
, frfr
);
466 pgn_history_add(g
, d
->b
, buf
);
468 SET_FLAG(d
->flags
, CF_MODIFIED
);
471 if (TEST_FLAG(d
->flags
, CF_ENGINE_LOOP
)) {
472 update_cursor(g
, g
->hindex
);
474 if (TEST_FLAG(g
->flags
, GF_GAMEOVER
)) {
479 add_engine_command(g
, ENGINE_THINKING
, "go\n");
483 if (TEST_FLAG(gp
->flags
, GF_GAMEOVER
)) {
488 if (g
->side
== g
->turn
)
491 d
->engine
->status
= ENGINE_THINKING
;
497 void parse_engine_output(GAME g
, char *str
)
499 char buf
[255], *p
= buf
;
502 if (*str
== '\n' || *str
== '\r') {
506 append_enginebuf(g
, buf
);
507 parse_xboard_line(g
, buf
);
525 void send_engine_command(GAME g
)
527 struct userdata_s
*d
= g
->data
;
531 if (!d
->engine
|| !d
->engine
->queue
)
534 q
= d
->engine
->queue
;
536 if (send_to_engine(g
, q
[0]->status
, "%s", q
[0]->line
))
542 for (i
= 0; q
[i
]; i
++) {
552 free (d
->engine
->queue
);
553 d
->engine
->queue
= NULL
;
557 void add_engine_command(GAME g
, int s
, char *fmt
, ...)
561 struct userdata_s
*d
= g
->data
;
562 struct queue_s
**q
= NULL
;
565 if (!d
->engine
|| d
->engine
->status
== ENGINE_OFFLINE
) {
566 if (init_chess_engine(g
))
573 q
= d
->engine
->queue
;
576 for (i
= 0; q
[i
]; i
++);
578 q
= Realloc(q
, (i
+ 2) * sizeof(struct queue_s
*));
580 #ifdef HAVE_VASPRINTF
581 vasprintf(&line
, fmt
, ap
);
583 line
= Malloc(LINE_MAX
+ 1);
584 vsnprintf(line
, LINE_MAX
, fmt
, ap
);
587 q
[i
] = Malloc(sizeof(struct queue_s
));
589 q
[i
++]->status
= (s
== -1) ? d
->engine
->status
: s
;
591 d
->engine
->queue
= q
;