1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
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
25 #include <sys/types.h>
51 void send_to_engine(GAME
*g
, const char *format
, ...)
57 struct user_data_s
*d
= g
->data
;
59 if (!g
->data
|| d
->status
== ENGINE_OFFLINE
)
62 d
->status
= ENGINE_THINKING
;
63 update_status_window(*g
);
69 len
= vasprintf(&line
, format
, ap
);
71 line
= Malloc(LINE_MAX
);
72 len
= vsnprintf(line
, LINE_MAX
, format
, ap
);
82 FD_SET(d
->fd
[ENGINE_OUT_FD
], &fds
);
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
);
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);
103 message(ERROR
, ANYKEY
, "Attempt #%i. write(): %s", ++try,
109 message(NULL
, ANYKEY
, "try #%i: write() error to engine. "
110 "Expected %i, got %i.", ++try, len
, n
);
126 /* From the Xboard and screen packages. */
127 static int get_pty(char *pty_name
)
132 for (i
= 0; PTY_MAJOR
[i
]; i
++) {
135 for (n
= 0; PTY_MINOR
[n
]; n
++) {
136 sprintf(pty_name
, "%spty%c%c", _PATH_DEV
, PTY_MAJOR
[i
],
139 if ((fd
= open(pty_name
, O_RDWR
| O_NOCTTY
)) == -1)
142 sprintf(pty_name
, "%stty%c%c", _PATH_DEV
, PTY_MAJOR
[i
],
145 if (access(pty_name
, R_OK
| W_OK
) == -1) {
158 static char **parseargs(char *str
)
170 if (!(pptr
= malloc(sizeof(char *))))
173 for (i
= 0, s
= str
; *s
; lastchar
= *s
++) {
174 if ((*s
== '\"' || *s
== '\'') && lastchar
!= '\\') {
175 quote
= (quote
) ? 0 : 1;
179 if (*s
== ' ' && !quote
) {
181 pptr
= realloc(pptr
, (n
+ 2) * sizeof(char *));
182 pptr
[n
++] = strdup(arg
);
187 if ((i
+ 1) == sizeof(arg
))
196 pptr
= realloc(pptr
, (n
+ 2) * sizeof(char *));
197 pptr
[n
++] = strdup(arg
);
204 /* Is this dangerous if pty permissions are wrong? */
205 static pid_t
init_chess_engine(GAME
*g
, char **args
)
209 struct user_data_s
*d
= g
->data
;
211 char pty
[FILENAME_MAX
];
213 if ((to
[1] = get_pty(pty
)) == -1) {
218 if ((to
[1] = open("/dev/ptmx", O_RDWR
| O_NOCTTY
)) == -1) {
222 if (grantpt(to
[1]) == -1) {
226 if (unlockpt(to
[1]) == -1) {
235 if ((to
[0] = open(pty
, O_RDWR
, 0)) == -1)
237 if ((to
[0] = open(ptsname(to
[1]), O_RDWR
, 0)) == -1)
244 switch ((pid
= fork())) {
248 dup2(to
[0], STDIN_FILENO
);
249 dup2(from
[1], STDOUT_FILENO
);
254 dup2(STDOUT_FILENO
, STDERR_FILENO
);
255 execvp(args
[0], args
);
263 if (kill(pid
, 0) == -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
);
277 void stop_engine(GAME
*g
)
279 struct user_data_s
*d
= g
->data
;
281 if (!d
|| d
->status
== ENGINE_OFFLINE
)
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
)
298 struct user_data_s
*d
= NULL
;
300 args
= parseargs(config
.engine_cmd
);
303 d
= Calloc(1, sizeof(struct user_data_s
));
307 d
->status
= ENGINE_INITIALIZING
;
308 update_status_window(*g
);
312 switch (init_chess_engine(g
, args
)) {
314 /* Pty allocation. */
315 message(ERROR
, ANYKEY
, "Could not allocate PTY");
316 d
->status
= ENGINE_OFFLINE
;
319 /* Could not execute engine. */
320 message(ERROR
, ANYKEY
, "%s: %s", args
[0], strerror(errno
));
321 d
->status
= ENGINE_OFFLINE
;
325 d
->status
= ENGINE_READY
;
329 for (i
= 0; args
[i
]; i
++)
333 update_status_window(*g
);
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
;
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)) {
355 if (history_total(g
->hp
) == 0 && g
->side
== BLACK
)
356 SET_FLAG(g
->flags
, GF_BLACK_OPENING
);
359 SET_FLAG(g
->flags
, GF_MODIFIED
);
366 if (sscanf(str
, "%*d%*1[.]%*1[ ]%*3[.]%*1[ ]%[a-zA-Z0-9+=#-]%n", m
,
368 /* Moves from the engine are in a2a4 format (Xboard protocol) so we
369 * need to convert them.
372 if ((p
= pgn_a2a4tosan(g
, g
->b
, m
)) == NULL
)
375 if (pgn_validate_move(g
, g
->b
, p
)) {
380 if (history_total(g
->hp
) == 0 && g
->side
== BLACK
)
381 SET_FLAG(g
->flags
, GF_BLACK_OPENING
);
384 SET_FLAG(g
->flags
, GF_MODIFIED
);
388 if (TEST_FLAG(g
->flags
, GF_GAMEOVER
)) {
389 //history_update_board(g, g.htotal); FIXME
393 if (d
&& TEST_FLAG(d
->flags
, CF_ENGINE_LOOP
)) {
394 send_to_engine(g
, "go\n");
395 update_cursor(*g
, g
->hindex
);
402 if (TEST_FLAG(g
->flags
, GF_GAMEOVER
)) {
403 //history_update_board(g); FIXME
407 /* Miscellaneous one-liners. */
409 /* The engine is now reading a FIFO. Dump what we need to it. */
411 if (strncmp(str
, "pgnload ", 8) == 0) {
412 if (save_pgn(config
.fifo
, 1, gindex
)) {
417 history_free(g
->hp
, g
->hindex
+ 1);
418 CLEAR_FLAG(g
->flags
, GF_GAMEOVER
);
419 SET_FLAG(g
->flags
, GF_MODIFIED
);
425 /* 'switch' command. */
426 if (strncmp(str
, "White to move", 13) == 0) {
427 g
->side
= g
->turn
= WHITE
;
430 else if (strncmp(str
, "Black to move", 13) == 0) {
431 g
->side
= g
->turn
= BLACK
;
435 /* Bad engine command or move. */
436 if (strncmp(str
, "Illegal move: ", 14) == 0) {
441 static void parse_engine_line(GAME
*g
, char *line
)
448 parse_gnuchess_line(g
, line
);
451 void parse_engine_output(GAME
*g
, char *str
)
453 char buf
[LINE_MAX
], *p
= buf
;
458 /* FIXME test this ("White ... : ", "Black ... : "). Needed for the
468 parse_engine_line(g
, buf
);