1
/* This file is a part of gtkboard, a board games system.
2
Copyright (C) 2003, Arvind Narayanan <arvindn@users.sourceforge.net>
4
This program is free software; you can redistribute it and/or modify
5
it under the terms of the GNU General Public License as published by
6
the Free Software Foundation; either version 2 of the License, or
7
(at your option) any later version.
9
This program is distributed in the hope that it will be useful,
10
but WITHOUT ANY WARRANTY; without even the implied warranty of
11
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
GNU General Public License for more details.
14
You should have received a copy of the GNU General Public License
15
along with this program; if not, write to the Free Software
16
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA
20
\brief The engine forms the backend that does the number crunching
35
extern int board_wid, board_heit;
36
extern int opt_verbose;
37
//extern int state_player;
39
// FIXME: this is ugly. Code should be refactored.
40
extern gboolean engine_flag;
44
extern Game *opt_game, *games[];
46
byte * engine_search (Pos *);
47
static FILE *engine_fin, *engine_fout;
49
//! Eval fn for white (can be NULL, in which case game_eval will be used for both)
50
ResultType (*game_eval_white) (Pos *, Player, float *);
51
//! Eval fn for black (can be NULL, in which case game_eval will be used for both)
52
ResultType (*game_eval_black) (Pos *, Player, float *);
54
// FIXME: following 3 extern decls must be removed by refactoring (i.e, move all fns common to client and server to a new file)
55
extern void reset_game_params ();
56
extern void set_game_params ();
57
extern void game_set_init_pos_def (Pos *);
59
//! Alpha-beta search function (using depth first iterative deepening).
60
extern byte *ab_dfid (Pos *, int);
62
//! The input pipe is accessed through a GIOChannel so that we can register a callback for events
63
static GIOChannel *channel_in = NULL;
65
//! If an event occurs when we are thinking this will be set to TRUE so that we will know to stop thinking
66
gboolean engine_stop_search = FALSE;
68
//! Indicates whether we have to stop and return the move or stop and cancel the move
69
static gboolean cancel_move = FALSE;
71
//! Max time per move. alpha-beta will often return earlier than this.
72
int time_per_move = 5000;
74
gboolean engine_hup_cb ()
77
fprintf (stderr, "engine: Connection broken. Exiting.\n");
78
// FIXME: do we want to free anything here?
82
ResultType engine_eval (Pos *pos, /*Player player,*/ float *eval)
85
result = pos->player == WHITE ? game_eval_white(pos, pos->player, eval) :
86
game_eval_black (pos, pos->player, eval);
90
void engine_set_to_play (char *line)
92
if (!strncasecmp (line, "white", 5))
93
cur_pos.player = WHITE;
94
else if (!strncasecmp (line, "black", 5))
95
cur_pos.player = BLACK;
98
void engine_take_move (char *line)
100
byte *move = move_read (line);
102
movstack_push (cur_pos.board, move);
105
void *newstate = game_newstate (&cur_pos, move);
106
statestack_push (newstate);
107
cur_pos.state = statestack_peek ();
109
move_apply (cur_pos.board, move);
111
cur_pos.player = cur_pos.player == WHITE ? BLACK : WHITE;
114
void engine_make_move ()
119
move = engine_search (&cur_pos);
124
move_fwrite_nak ("Nice try", engine_fout);
127
movstack_push (cur_pos.board, move);
130
void *newstate = game_newstate (&cur_pos, move);
131
statestack_push (newstate);
132
cur_pos.state = statestack_peek ();
134
move_apply (cur_pos.board, move);
136
move_fwrite_ack (move, engine_fout);
137
cur_pos.player = cur_pos.player == WHITE ? BLACK : WHITE;
140
void engine_new_game (char *gamename)
143
Game *old_game = opt_game;
145
// strip trailing newline
146
if (gamename[len = strlen(gamename) - 1] == '\n')
148
for (i=0; i<num_games; i++)
149
if (!strcmp (games[i]->name, gamename))
156
// FIXME: isn't there a more elegant way to do this?
157
GameLevel *level = game_levels;
161
if (!strcmp (level->game->name, gamename))
163
opt_game = level->game;
171
fprintf (stderr, "engine: unknown game: %s\n", gamename);
174
reset_game_params ();
175
if (opt_game->game_init)
176
opt_game->game_init(opt_game);
178
if (game_set_init_pos != game_set_init_pos_def)
180
fwrite (cur_pos.board, board_wid * board_heit, 1, engine_fout);
181
fflush (engine_fout);
186
void engine_reset_game ()
189
// FIXME : there should be a separate reset_game_params() for engine
190
cur_pos.player = WHITE;
191
cur_pos.state = NULL;
192
cur_pos.num_moves = 0;
193
game_set_init_pos (&cur_pos);
196
void engine_back_move ()
198
byte *move = movstack_back ();
199
if (game_stateful) cur_pos.state = statestack_back ();
202
move_fwrite_nak (NULL, engine_fout);
205
move_apply (cur_pos.board, move);
207
cur_pos.player = cur_pos.player == WHITE ? BLACK : WHITE;
208
move_fwrite_ack (move, engine_fout);
211
void engine_forw_move ()
213
byte *move = movstack_forw ();
215
if (game_stateful && (state = statestack_forw ()))
216
cur_pos.state = state;
219
move_fwrite_nak (NULL, engine_fout);
222
move_apply (cur_pos.board, move);
224
cur_pos.player = cur_pos.player == WHITE ? BLACK : WHITE;
225
move_fwrite_ack (move, engine_fout);
228
void engine_msec_per_move (char *line)
231
time_per_move = atoi (line);
232
if (time_per_move < 0)
233
time_per_move = 3000;
236
void engine_who_won (char *line)
243
move_fwrite_nak (NULL, engine_fout);
246
who = game_who_won (&cur_pos, cur_pos.player, &msg);
249
case RESULT_WHITE: who_str = "WHITE"; break;
250
case RESULT_BLACK: who_str = "BLACK"; break;
251
case RESULT_TIE : who_str = "TIE" ; break;
252
case RESULT_WON : who_str = "WON" ; break;
253
case RESULT_LOST : who_str = "LOST" ; break;
254
case RESULT_MISC : who_str = "MISC" ; break;
257
who_str = "NYET"; break; // ;^)
261
fprintf (engine_fout, "ACK %s %s\n", who_str, msg);
263
fprintf (engine_fout, "ACK %s\n", who_str);
264
fflush (engine_fout);
268
void engine_move_now (char *line)
270
engine_stop_search = TRUE;
273
int engine_timeout_cb ()
275
engine_stop_search = TRUE;
279
void engine_cancel_move (char *line)
281
engine_stop_search = TRUE;
286
//! This structure defines the protocol
289
{ "MSEC_PER_MOVE" , 1 , engine_msec_per_move},
290
{ "SUGGEST_MOVE" , 0 , NULL},
291
{ "TAKE_MOVE" , 1 , engine_take_move},
292
{ "BACK_MOVE" , 1 , engine_back_move},
293
{ "FORW_MOVE" , 1 , engine_forw_move},
294
{ "MAKE_MOVE" , 1 , engine_make_move},
295
{ "MOVE_NOW" , 1 , engine_move_now },
296
{ "CANCEL_MOVE" , 1 , engine_cancel_move },
297
{ "END_GAME" , 0 , NULL},
298
{ "RESET_GAME" , 1 , engine_reset_game},
299
{ "TO_PLAY" , 1 , engine_set_to_play},
300
{ "SET_POSITION" , 0 , NULL},
301
{ "NEW_GAME" , 1 , engine_new_game},
302
{ "GET_EVAL" , 0 , NULL},
303
{ "SET_HEUR" , 0 , NULL},
304
{ "SET_STRATEGY" , 0 , NULL},
305
{ "WHO_WON" , 1 , engine_who_won},
308
#define NUM_COMMANDS (sizeof (commands) / sizeof (commands[0]))
310
//! Parse the command and pass control to the corresponding function pointer
311
static void execute_command (char *line)
315
tail = strpbrk (line, " \t");
318
for (i=0; i<NUM_COMMANDS; i++)
319
if (!strcmp (line, commands[i].proto_str))
321
if (!commands[i].isimpl)
323
fprintf (stderr, "warning: command %s not yet implemented\n",
324
commands[i].proto_str);
327
commands[i].impl_func (tail ? (tail+1) : 0);
330
fprintf (stderr, "warning: unknown command \"%s\" \n", line);
334
static GSList *command_list = NULL;
335
static gboolean process_line ()
338
line = (char *) g_slist_nth_data (command_list, 0);
339
if (!line) return FALSE;
340
command_list = g_slist_remove (command_list, line);
341
execute_command (line);
346
static gboolean channel_process_input ()
348
static char linebuf[4096];
349
char *linep = linebuf;
352
#if GLIB_MAJOR_VERSION > 1
353
// we need to call this again because we will get new events before returning
354
// from this function
355
// semantics of add_watch silently changing between glib versions!!!!
356
g_io_add_watch (channel_in, G_IO_IN, (GIOFunc) channel_process_input, NULL);
358
g_io_channel_read (channel_in, linebuf, 4096, &bytes_read);
359
linebuf[bytes_read] = '\0';
360
while (*linep != '\0')
363
while (*linep++ != '\n')
364
g_assert (linep[-1] != '\0');
366
if (opt_verbose) printf ("engine got command \"%s\"\n", line);
367
command_list = g_slist_append (command_list, g_strdup (line));
369
while (process_line ())
371
#if GLIB_MAJOR_VERSION == 1
380
static int poll_count = 0;
381
if (++poll_count == 100)
384
// listen for input in the pipe
385
while (g_main_iteration (FALSE))
387
// execute pending commands
388
// we execute only ONE command so that if there is a CANCEL_MOVE followed by a MAKE_MOVE we won't start on the new move before finishing this one
393
static void ignore () {}
395
//! This is the main function for the engine
396
void engine_main (int infd, int outfd)
401
signal (SIGHUP, ignore);
402
signal (SIGINT, ignore);
403
engine_fin = fdopen (infd, "r");
404
engine_fout = fdopen (outfd, "w");
406
assert (engine_fout);
407
channel_in = g_io_channel_unix_new (infd);
408
g_io_add_watch (channel_in, G_IO_IN, (GIOFunc) channel_process_input, NULL);
409
g_io_add_watch (channel_in, G_IO_HUP | G_IO_NVAL, (GIOFunc) engine_hup_cb, NULL);
410
#if GLIB_MAJOR_VERSION > 1
411
loop = g_main_loop_new (NULL, TRUE);
413
loop = g_main_new (TRUE);
418
byte * engine_search (Pos *pos/*, int player*/)
422
engine_stop_search = FALSE;
424
game_search (pos, &move);
425
else if (game_single_player)
429
tag = g_timeout_add (2 * time_per_move, engine_timeout_cb, NULL);
430
move = ab_dfid (pos, pos->player);
431
g_source_remove (tag);