changes from H.G. Muller; version 4.3.15
[xboard.git] / backend.c
1 /*\r
2  * backend.c -- Common back end for X and Windows NT versions of\r
3  * XBoard $Id: backend.c,v 2.6 2003/11/28 09:37:36 mann Exp $\r
4  *\r
5  * Copyright 1991 by Digital Equipment Corporation, Maynard, Massachusetts.\r
6  * Enhancements Copyright 1992-2001 Free Software Foundation, Inc.\r
7  *\r
8  * The following terms apply to Digital Equipment Corporation's copyright\r
9  * interest in XBoard:\r
10  * ------------------------------------------------------------------------\r
11  * All Rights Reserved\r
12  *\r
13  * Permission to use, copy, modify, and distribute this software and its\r
14  * documentation for any purpose and without fee is hereby granted,\r
15  * provided that the above copyright notice appear in all copies and that\r
16  * both that copyright notice and this permission notice appear in\r
17  * supporting documentation, and that the name of Digital not be\r
18  * used in advertising or publicity pertaining to distribution of the\r
19  * software without specific, written prior permission.\r
20  *\r
21  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING\r
22  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL\r
23  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR\r
24  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,\r
25  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,\r
26  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS\r
27  * SOFTWARE.\r
28  * ------------------------------------------------------------------------\r
29  *\r
30  * The following terms apply to the enhanced version of XBoard distributed\r
31  * by the Free Software Foundation:\r
32  * ------------------------------------------------------------------------\r
33  * This program is free software; you can redistribute it and/or modify\r
34  * it under the terms of the GNU General Public License as published by\r
35  * the Free Software Foundation; either version 2 of the License, or\r
36  * (at your option) any later version.\r
37  *\r
38  * This program is distributed in the hope that it will be useful,\r
39  * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
40  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
41  * GNU General Public License for more details.\r
42  *\r
43  * You should have received a copy of the GNU General Public License\r
44  * along with this program; if not, write to the Free Software\r
45  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.\r
46  * ------------------------------------------------------------------------\r
47  *\r
48  * See the file ChangeLog for a revision history.  */\r
49 \r
50 /* [AS] Also useful here for debugging */\r
51 #ifdef WIN32\r
52 #include <windows.h>\r
53 \r
54 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );\r
55 \r
56 #else\r
57 \r
58 #define DoSleep( n )\r
59 \r
60 #endif\r
61 \r
62 #include "config.h"\r
63 \r
64 #include <assert.h>\r
65 #include <stdio.h>\r
66 #include <ctype.h>\r
67 #include <errno.h>\r
68 #include <sys/types.h>\r
69 #include <sys/stat.h>\r
70 #include <math.h>\r
71 \r
72 #if STDC_HEADERS\r
73 # include <stdlib.h>\r
74 # include <string.h>\r
75 #else /* not STDC_HEADERS */\r
76 # if HAVE_STRING_H\r
77 #  include <string.h>\r
78 # else /* not HAVE_STRING_H */\r
79 #  include <strings.h>\r
80 # endif /* not HAVE_STRING_H */\r
81 #endif /* not STDC_HEADERS */\r
82 \r
83 #if HAVE_SYS_FCNTL_H\r
84 # include <sys/fcntl.h>\r
85 #else /* not HAVE_SYS_FCNTL_H */\r
86 # if HAVE_FCNTL_H\r
87 #  include <fcntl.h>\r
88 # endif /* HAVE_FCNTL_H */\r
89 #endif /* not HAVE_SYS_FCNTL_H */\r
90 \r
91 #if TIME_WITH_SYS_TIME\r
92 # include <sys/time.h>\r
93 # include <time.h>\r
94 #else\r
95 # if HAVE_SYS_TIME_H\r
96 #  include <sys/time.h>\r
97 # else\r
98 #  include <time.h>\r
99 # endif\r
100 #endif\r
101 \r
102 #if defined(_amigados) && !defined(__GNUC__)\r
103 struct timezone {\r
104     int tz_minuteswest;\r
105     int tz_dsttime;\r
106 };\r
107 extern int gettimeofday(struct timeval *, struct timezone *);\r
108 #endif\r
109 \r
110 #if HAVE_UNISTD_H\r
111 # include <unistd.h>\r
112 #endif\r
113 \r
114 #include "common.h"\r
115 #include "frontend.h"\r
116 #include "backend.h"\r
117 #include "parser.h"\r
118 #include "moves.h"\r
119 #if ZIPPY\r
120 # include "zippy.h"\r
121 #endif\r
122 #include "backendz.h"\r
123 \r
124 /* A point in time */\r
125 typedef struct {\r
126     long sec;  /* Assuming this is >= 32 bits */\r
127     int ms;    /* Assuming this is >= 16 bits */\r
128 } TimeMark;\r
129 \r
130 /* Search stats from chessprogram */\r
131 typedef struct {\r
132   char movelist[2*MSG_SIZ]; /* Last PV we were sent */\r
133   int depth;              /* Current search depth */\r
134   int nr_moves;           /* Total nr of root moves */\r
135   int moves_left;         /* Moves remaining to be searched */\r
136   char move_name[MOVE_LEN];  /* Current move being searched, if provided */\r
137   unsigned long nodes;    /* # of nodes searched */\r
138   int time;               /* Search time (centiseconds) */\r
139   int score;              /* Score (centipawns) */\r
140   int got_only_move;      /* If last msg was "(only move)" */\r
141   int got_fail;           /* 0 - nothing, 1 - got "--", 2 - got "++" */\r
142   int ok_to_send;         /* handshaking between send & recv */\r
143   int line_is_book;       /* 1 if movelist is book moves */\r
144   int seen_stat;          /* 1 if we've seen the stat01: line */\r
145 } ChessProgramStats;\r
146 \r
147 int establish P((void));\r
148 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,\r
149                          char *buf, int count, int error));\r
150 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,\r
151                       char *buf, int count, int error));\r
152 void SendToICS P((char *s));\r
153 void SendToICSDelayed P((char *s, long msdelay));\r
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,\r
155                       int toX, int toY));\r
156 void InitPosition P((int redraw));\r
157 void HandleMachineMove P((char *message, ChessProgramState *cps));\r
158 int AutoPlayOneMove P((void));\r
159 int LoadGameOneMove P((ChessMove readAhead));\r
160 int LoadGameFromFile P((char *filename, int n, char *title, int useList));\r
161 int LoadPositionFromFile P((char *filename, int n, char *title));\r
162 int SavePositionToFile P((char *filename));\r
163 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,\r
164                   Board board));\r
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));\r
166 void ShowMove P((int fromX, int fromY, int toX, int toY));\r
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,\r
168                    /*char*/int promoChar));\r
169 void BackwardInner P((int target));\r
170 void ForwardInner P((int target));\r
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));\r
172 void EditPositionDone P((void));\r
173 void PrintOpponents P((FILE *fp));\r
174 void PrintPosition P((FILE *fp, int move));\r
175 void StartChessProgram P((ChessProgramState *cps));\r
176 void SendToProgram P((char *message, ChessProgramState *cps));\r
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));\r
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,\r
179                            char *buf, int count, int error));\r
180 void SendTimeControl P((ChessProgramState *cps,\r
181                         int mps, long tc, int inc, int sd, int st));\r
182 char *TimeControlTagValue P((void));\r
183 void Attention P((ChessProgramState *cps));\r
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));\r
185 void ResurrectChessProgram P((void));\r
186 void DisplayComment P((int moveNumber, char *text));\r
187 void DisplayMove P((int moveNumber));\r
188 void DisplayAnalysis P((void));\r
189 \r
190 void ParseGameHistory P((char *game));\r
191 void ParseBoard12 P((char *string));\r
192 void StartClocks P((void));\r
193 void SwitchClocks P((void));\r
194 void StopClocks P((void));\r
195 void ResetClocks P((void));\r
196 char *PGNDate P((void));\r
197 void SetGameInfo P((void));\r
198 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));\r
199 int RegisterMove P((void));\r
200 void MakeRegisteredMove P((void));\r
201 void TruncateGame P((void));\r
202 int looking_at P((char *, int *, char *));\r
203 void CopyPlayerNameIntoFileName P((char **, char *));\r
204 char *SavePart P((char *));\r
205 int SaveGameOldStyle P((FILE *));\r
206 int SaveGamePGN P((FILE *));\r
207 void GetTimeMark P((TimeMark *));\r
208 long SubtractTimeMarks P((TimeMark *, TimeMark *));\r
209 int CheckFlags P((void));\r
210 long NextTickLength P((long));\r
211 void CheckTimeControl P((void));\r
212 void show_bytes P((FILE *, char *, int));\r
213 int string_to_rating P((char *str));\r
214 void ParseFeatures P((char* args, ChessProgramState *cps));\r
215 void InitBackEnd3 P((void));\r
216 void FeatureDone P((ChessProgramState* cps, int val));\r
217 void InitChessProgram P((ChessProgramState *cps, int setup));\r
218 ChessProgramState *WhitePlayer();\r
219 \r
220 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment\r
221 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c\r
222 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move\r
223 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book\r
224 extern char installDir[MSG_SIZ];\r
225 \r
226 extern int tinyLayout, smallLayout;\r
227 static ChessProgramStats programStats;\r
228 static int exiting = 0; /* [HGM] moved to top */\r
229 static int setboardSpoiledMachineBlack = 0, errorExitFlag = 0;\r
230 extern int startedFromPositionFile;\r
231 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */\r
232 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */\r
233 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */\r
234 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */\r
235 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */\r
236 \r
237 /* States for ics_getting_history */\r
238 #define H_FALSE 0\r
239 #define H_REQUESTED 1\r
240 #define H_GOT_REQ_HEADER 2\r
241 #define H_GOT_UNREQ_HEADER 3\r
242 #define H_GETTING_MOVES 4\r
243 #define H_GOT_UNWANTED_HEADER 5\r
244 \r
245 /* whosays values for GameEnds */\r
246 #define GE_ICS 0\r
247 #define GE_ENGINE 1\r
248 #define GE_PLAYER 2\r
249 #define GE_FILE 3\r
250 #define GE_XBOARD 4\r
251 #define GE_ENGINE1 5\r
252 #define GE_ENGINE2 6\r
253 \r
254 /* Maximum number of games in a cmail message */\r
255 #define CMAIL_MAX_GAMES 20\r
256 \r
257 /* Different types of move when calling RegisterMove */\r
258 #define CMAIL_MOVE   0\r
259 #define CMAIL_RESIGN 1\r
260 #define CMAIL_DRAW   2\r
261 #define CMAIL_ACCEPT 3\r
262 \r
263 /* Different types of result to remember for each game */\r
264 #define CMAIL_NOT_RESULT 0\r
265 #define CMAIL_OLD_RESULT 1\r
266 #define CMAIL_NEW_RESULT 2\r
267 \r
268 /* Telnet protocol constants */\r
269 #define TN_WILL 0373\r
270 #define TN_WONT 0374\r
271 #define TN_DO   0375\r
272 #define TN_DONT 0376\r
273 #define TN_IAC  0377\r
274 #define TN_ECHO 0001\r
275 #define TN_SGA  0003\r
276 #define TN_PORT 23\r
277 \r
278 /* [AS] */\r
279 static char * safeStrCpy( char * dst, const char * src, size_t count )\r
280 {\r
281     assert( dst != NULL );\r
282     assert( src != NULL );\r
283     assert( count > 0 );\r
284 \r
285     strncpy( dst, src, count );\r
286     dst[ count-1 ] = '\0';\r
287     return dst;\r
288 }\r
289 \r
290 static char * safeStrCat( char * dst, const char * src, size_t count )\r
291 {\r
292     size_t  dst_len;\r
293 \r
294     assert( dst != NULL );\r
295     assert( src != NULL );\r
296     assert( count > 0 );\r
297 \r
298     dst_len = strlen(dst);\r
299 \r
300     assert( count > dst_len ); /* Buffer size must be greater than current length */\r
301 \r
302     safeStrCpy( dst + dst_len, src, count - dst_len );\r
303 \r
304     return dst;\r
305 }\r
306 \r
307 /* Fake up flags for now, as we aren't keeping track of castling\r
308    availability yet. [HGM] Change of logic: the flag now only\r
309    indicates the type of castlings allowed by the rule of the game.\r
310    The actual rights themselves are maintained in the array\r
311    castlingRights, as part of the game history, and are not probed\r
312    by this function.\r
313  */\r
314 int\r
315 PosFlags(index)\r
316 {\r
317   int flags = F_ALL_CASTLE_OK;\r
318   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;\r
319   switch (gameInfo.variant) {\r
320   case VariantSuicide:\r
321   case VariantGiveaway:\r
322     flags |= F_IGNORE_CHECK;\r
323     flags &= ~F_ALL_CASTLE_OK;\r
324     break;\r
325   case VariantAtomic:\r
326     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;\r
327     break;\r
328   case VariantKriegspiel:\r
329     flags |= F_KRIEGSPIEL_CAPTURE;\r
330     break;\r
331   case VariantCapaRandom: \r
332   case VariantFischeRandom:\r
333     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */\r
334   case VariantNoCastle:\r
335   case VariantShatranj:\r
336   case VariantCourier:\r
337     flags &= ~F_ALL_CASTLE_OK;\r
338     break;\r
339   default:\r
340     break;\r
341   }\r
342   return flags;\r
343 }\r
344 \r
345 FILE *gameFileFP, *debugFP;\r
346 \r
347 /* \r
348     [AS] Note: sometimes, the sscanf() function is used to parse the input\r
349     into a fixed-size buffer. Because of this, we must be prepared to\r
350     receive strings as long as the size of the input buffer, which is currently\r
351     set to 4K for Windows and 8K for the rest.\r
352     So, we must either allocate sufficiently large buffers here, or\r
353     reduce the size of the input buffer in the input reading part.\r
354 */\r
355 \r
356 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];\r
357 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];\r
358 char thinkOutput1[MSG_SIZ*10];\r
359 \r
360 ChessProgramState first, second;\r
361 \r
362 /* premove variables */\r
363 int premoveToX = 0;\r
364 int premoveToY = 0;\r
365 int premoveFromX = 0;\r
366 int premoveFromY = 0;\r
367 int premovePromoChar = 0;\r
368 int gotPremove = 0;\r
369 Boolean alarmSounded;\r
370 /* end premove variables */\r
371 \r
372 #define ICS_GENERIC 0\r
373 #define ICS_ICC 1\r
374 #define ICS_FICS 2\r
375 #define ICS_CHESSNET 3 /* not really supported */\r
376 int ics_type = ICS_GENERIC;\r
377 char *ics_prefix = "$";\r
378 \r
379 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;\r
380 int pauseExamForwardMostMove = 0;\r
381 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;\r
382 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];\r
383 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;\r
384 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;\r
385 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;\r
386 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;\r
387 int whiteFlag = FALSE, blackFlag = FALSE;\r
388 int userOfferedDraw = FALSE;\r
389 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;\r
390 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;\r
391 int cmailMoveType[CMAIL_MAX_GAMES];\r
392 long ics_clock_paused = 0;\r
393 ProcRef icsPR = NoProc, cmailPR = NoProc;\r
394 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;\r
395 GameMode gameMode = BeginningOfGame;\r
396 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];\r
397 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];\r
398 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */\r
399 int hiddenThinkOutputState = 0; /* [AS] */\r
400 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */\r
401 int adjudicateLossPlies = 6;\r
402 char white_holding[64], black_holding[64];\r
403 TimeMark lastNodeCountTime;\r
404 long lastNodeCount=0;\r
405 int have_sent_ICS_logon = 0;\r
406 int movesPerSession;\r
407 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;\r
408 long timeControl_2; /* [AS] Allow separate time controls */\r
409 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */\r
410 long timeRemaining[2][MAX_MOVES];\r
411 int matchGame = 0;\r
412 TimeMark programStartTime;\r
413 char ics_handle[MSG_SIZ];\r
414 int have_set_title = 0;\r
415 \r
416 /* animateTraining preserves the state of appData.animate\r
417  * when Training mode is activated. This allows the\r
418  * response to be animated when appData.animate == TRUE and\r
419  * appData.animateDragging == TRUE.\r
420  */\r
421 Boolean animateTraining;\r
422 \r
423 GameInfo gameInfo;\r
424 \r
425 AppData appData;\r
426 \r
427 Board boards[MAX_MOVES];\r
428 /* [HGM] Following 7 needed for accurate legality tests: */\r
429 char  epStatus[MAX_MOVES];\r
430 char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1\r
431 char  castlingRank[BOARD_SIZE]; // and corresponding ranks\r
432 char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];\r
433 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status\r
434 int   initialRulePlies, FENrulePlies;\r
435 char  FENepStatus;\r
436 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)\r
437 int loadFlag = 0; \r
438 int shuffleOpenings;\r
439 \r
440 ChessSquare  FIDEArray[2][BOARD_SIZE] = {\r
441     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,\r
442         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },\r
443     { BlackRook, BlackKnight, BlackBishop, BlackQueen,\r
444         BlackKing, BlackBishop, BlackKnight, BlackRook }\r
445 };\r
446 \r
447 ChessSquare twoKingsArray[2][BOARD_SIZE] = {\r
448     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,\r
449         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },\r
450     { BlackRook, BlackKnight, BlackBishop, BlackQueen,\r
451         BlackKing, BlackKing, BlackKnight, BlackRook }\r
452 };\r
453 \r
454 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {\r
455     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,\r
456         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },\r
457     { BlackRook, BlackMan, BlackBishop, BlackQueen,\r
458         BlackUnicorn, BlackBishop, BlackMan, BlackRook }\r
459 };\r
460 \r
461 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */\r
462     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,\r
463         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },\r
464     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,\r
465         BlackKing, BlackBishop, BlackKnight, BlackRook }\r
466 };\r
467 \r
468 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */\r
469     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,\r
470         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },\r
471     { BlackRook, BlackKnight, BlackAlfil, BlackKing,\r
472         BlackFerz, BlackAlfil, BlackKnight, BlackRook }\r
473 };\r
474 \r
475 \r
476 #if (BOARD_SIZE>=10)\r
477 ChessSquare ShogiArray[2][BOARD_SIZE] = {\r
478     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,\r
479         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },\r
480     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,\r
481         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }\r
482 };\r
483 \r
484 ChessSquare XiangqiArray[2][BOARD_SIZE] = {\r
485     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,\r
486         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },\r
487     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,\r
488         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }\r
489 };\r
490 \r
491 ChessSquare CapablancaArray[2][BOARD_SIZE] = {\r
492     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, \r
493         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },\r
494     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, \r
495         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }\r
496 };\r
497 \r
498 ChessSquare JanusArray[2][BOARD_SIZE] = {\r
499     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, \r
500         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },\r
501     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, \r
502         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }\r
503 };\r
504 \r
505 #ifdef GOTHIC\r
506 ChessSquare GothicArray[2][BOARD_SIZE] = {\r
507     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, \r
508         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },\r
509     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, \r
510         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }\r
511 };\r
512 #else // !GOTHIC\r
513 #define GothicArray CapablancaArray\r
514 #endif // !GOTHIC\r
515 \r
516 #ifdef FALCON\r
517 ChessSquare FalconArray[2][BOARD_SIZE] = {\r
518     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, \r
519         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },\r
520     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, \r
521         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }\r
522 };\r
523 #else // !FALCON\r
524 #define FalconArray CapablancaArray\r
525 #endif // !FALCON\r
526 \r
527 #else // !(BOARD_SIZE>=10)\r
528 #define XiangqiPosition FIDEArray\r
529 #define CapablancaArray FIDEArray\r
530 #define GothicArray FIDEArray\r
531 #endif // !(BOARD_SIZE>=10)\r
532 \r
533 #if (BOARD_SIZE>=12)\r
534 ChessSquare CourierArray[2][BOARD_SIZE] = {\r
535     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,\r
536         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },\r
537     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,\r
538         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }\r
539 };\r
540 #else // !(BOARD_SIZE>=12)\r
541 #define CourierArray CapablancaArray\r
542 #endif // !(BOARD_SIZE>=12)\r
543 \r
544 \r
545 Board initialPosition;\r
546 \r
547 \r
548 /* Convert str to a rating. Checks for special cases of "----",\r
549 \r
550    "++++", etc. Also strips ()'s */\r
551 int\r
552 string_to_rating(str)\r
553   char *str;\r
554 {\r
555   while(*str && !isdigit(*str)) ++str;\r
556   if (!*str)\r
557     return 0;   /* One of the special "no rating" cases */\r
558   else\r
559     return atoi(str);\r
560 }\r
561 \r
562 void\r
563 ClearProgramStats()\r
564 {\r
565     /* Init programStats */\r
566     programStats.movelist[0] = 0;\r
567     programStats.depth = 0;\r
568     programStats.nr_moves = 0;\r
569     programStats.moves_left = 0;\r
570     programStats.nodes = 0;\r
571     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output\r
572     programStats.score = 0;\r
573     programStats.got_only_move = 0;\r
574     programStats.got_fail = 0;\r
575     programStats.line_is_book = 0;\r
576 }\r
577 \r
578 void\r
579 InitBackEnd1()\r
580 {\r
581     int matched, min, sec;\r
582 \r
583     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options\r
584 \r
585     GetTimeMark(&programStartTime);\r
586 \r
587     ClearProgramStats();\r
588     programStats.ok_to_send = 1;\r
589     programStats.seen_stat = 0;\r
590 \r
591     /*\r
592      * Initialize game list\r
593      */\r
594     ListNew(&gameList);\r
595 \r
596 \r
597     /*\r
598      * Internet chess server status\r
599      */\r
600     if (appData.icsActive) {\r
601         appData.matchMode = FALSE;\r
602         appData.matchGames = 0;\r
603 #if ZIPPY       \r
604         appData.noChessProgram = !appData.zippyPlay;\r
605 #else\r
606         appData.zippyPlay = FALSE;\r
607         appData.zippyTalk = FALSE;\r
608         appData.noChessProgram = TRUE;\r
609 #endif\r
610         if (*appData.icsHelper != NULLCHAR) {\r
611             appData.useTelnet = TRUE;\r
612             appData.telnetProgram = appData.icsHelper;\r
613         }\r
614     } else {\r
615         appData.zippyTalk = appData.zippyPlay = FALSE;\r
616     }\r
617 \r
618     /* [AS] Initialize pv info list [HGM] and game state */\r
619     {\r
620         int i, j;\r
621 \r
622         for( i=0; i<MAX_MOVES; i++ ) {\r
623             pvInfoList[i].depth = -1;\r
624             epStatus[i]=EP_NONE;\r
625             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
626         }\r
627     }\r
628 \r
629     /*\r
630      * Parse timeControl resource\r
631      */\r
632     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,\r
633                           appData.movesPerSession)) {\r
634         char buf[MSG_SIZ];\r
635         sprintf(buf, "bad timeControl option %s", appData.timeControl);\r
636         DisplayFatalError(buf, 0, 2);\r
637     }\r
638 \r
639     /*\r
640      * Parse searchTime resource\r
641      */\r
642     if (*appData.searchTime != NULLCHAR) {\r
643         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);\r
644         if (matched == 1) {\r
645             searchTime = min * 60;\r
646         } else if (matched == 2) {\r
647             searchTime = min * 60 + sec;\r
648         } else {\r
649             char buf[MSG_SIZ];\r
650             sprintf(buf, "bad searchTime option %s", appData.searchTime);\r
651             DisplayFatalError(buf, 0, 2);\r
652         }\r
653     }\r
654 \r
655     /* [AS] Adjudication threshold */\r
656     adjudicateLossThreshold = appData.adjudicateLossThreshold;\r
657     \r
658     first.which = "first";\r
659     second.which = "second";\r
660     first.maybeThinking = second.maybeThinking = FALSE;\r
661     first.pr = second.pr = NoProc;\r
662     first.isr = second.isr = NULL;\r
663     first.sendTime = second.sendTime = 2;\r
664     first.sendDrawOffers = 1;\r
665     if (appData.firstPlaysBlack) {\r
666         first.twoMachinesColor = "black\n";\r
667         second.twoMachinesColor = "white\n";\r
668     } else {\r
669         first.twoMachinesColor = "white\n";\r
670         second.twoMachinesColor = "black\n";\r
671     }\r
672     first.program = appData.firstChessProgram;\r
673     second.program = appData.secondChessProgram;\r
674     first.host = appData.firstHost;\r
675     second.host = appData.secondHost;\r
676     first.dir = appData.firstDirectory;\r
677     second.dir = appData.secondDirectory;\r
678     first.other = &second;\r
679     second.other = &first;\r
680     first.initString = appData.initString;\r
681     second.initString = appData.secondInitString;\r
682     first.computerString = appData.firstComputerString;\r
683     second.computerString = appData.secondComputerString;\r
684     first.useSigint = second.useSigint = TRUE;\r
685     first.useSigterm = second.useSigterm = TRUE;\r
686     first.reuse = appData.reuseFirst;\r
687     second.reuse = appData.reuseSecond;\r
688     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second\r
689     second.nps = appData.secondNPS;\r
690     first.useSetboard = second.useSetboard = FALSE;\r
691     first.useSAN = second.useSAN = FALSE;\r
692     first.usePing = second.usePing = FALSE;\r
693     first.lastPing = second.lastPing = 0;\r
694     first.lastPong = second.lastPong = 0;\r
695     first.usePlayother = second.usePlayother = FALSE;\r
696     first.useColors = second.useColors = TRUE;\r
697     first.useUsermove = second.useUsermove = FALSE;\r
698     first.sendICS = second.sendICS = FALSE;\r
699     first.sendName = second.sendName = appData.icsActive;\r
700     first.sdKludge = second.sdKludge = FALSE;\r
701     first.stKludge = second.stKludge = FALSE;\r
702     TidyProgramName(first.program, first.host, first.tidy);\r
703     TidyProgramName(second.program, second.host, second.tidy);\r
704     first.matchWins = second.matchWins = 0;\r
705     strcpy(first.variants, appData.variant);\r
706     strcpy(second.variants, appData.variant);\r
707     first.analysisSupport = second.analysisSupport = 2; /* detect */\r
708     first.analyzing = second.analyzing = FALSE;\r
709     first.initDone = second.initDone = FALSE;\r
710 \r
711     /* New features added by Tord: */\r
712     first.useFEN960 = FALSE; second.useFEN960 = FALSE;\r
713     first.useOOCastle = TRUE; second.useOOCastle = TRUE;\r
714     /* End of new features added by Tord. */\r
715 \r
716     /* [HGM] time odds: set factor for each machine */\r
717     first.timeOdds  = appData.firstTimeOdds;\r
718     second.timeOdds = appData.secondTimeOdds;\r
719     { int norm = 1;\r
720         if(appData.timeOddsMode) {\r
721             norm = first.timeOdds;\r
722             if(norm > second.timeOdds) norm = second.timeOdds;\r
723         }\r
724         first.timeOdds /= norm;\r
725         second.timeOdds /= norm;\r
726     }\r
727 \r
728     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/\r
729     first.accumulateTC = appData.firstAccumulateTC;\r
730     second.accumulateTC = appData.secondAccumulateTC;\r
731     first.maxNrOfSessions = second.maxNrOfSessions = 1;\r
732 \r
733     /* [HGM] debug */\r
734     first.debug = second.debug = FALSE;\r
735     first.supportsNPS = second.supportsNPS = UNKNOWN;\r
736 \r
737     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */\r
738     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */\r
739     first.isUCI = appData.firstIsUCI; /* [AS] */\r
740     second.isUCI = appData.secondIsUCI; /* [AS] */\r
741     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */\r
742     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */\r
743 \r
744     if (appData.firstProtocolVersion > PROTOVER ||\r
745         appData.firstProtocolVersion < 1) {\r
746       char buf[MSG_SIZ];\r
747       sprintf(buf, "protocol version %d not supported",\r
748               appData.firstProtocolVersion);\r
749       DisplayFatalError(buf, 0, 2);\r
750     } else {\r
751       first.protocolVersion = appData.firstProtocolVersion;\r
752     }\r
753 \r
754     if (appData.secondProtocolVersion > PROTOVER ||\r
755         appData.secondProtocolVersion < 1) {\r
756       char buf[MSG_SIZ];\r
757       sprintf(buf, "protocol version %d not supported",\r
758               appData.secondProtocolVersion);\r
759       DisplayFatalError(buf, 0, 2);\r
760     } else {\r
761       second.protocolVersion = appData.secondProtocolVersion;\r
762     }\r
763 \r
764     if (appData.icsActive) {\r
765         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */\r
766     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {\r
767         appData.clockMode = FALSE;\r
768         first.sendTime = second.sendTime = 0;\r
769     }\r
770     \r
771 #if ZIPPY\r
772     /* Override some settings from environment variables, for backward\r
773        compatibility.  Unfortunately it's not feasible to have the env\r
774        vars just set defaults, at least in xboard.  Ugh.\r
775     */\r
776     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {\r
777       ZippyInit();\r
778     }\r
779 #endif\r
780     \r
781     if (appData.noChessProgram) {\r
782         programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)\r
783                                         + strlen(PATCHLEVEL));\r
784         sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);\r
785     } else {\r
786 #if 0\r
787         char *p, *q;\r
788         q = first.program;\r
789         while (*q != ' ' && *q != NULLCHAR) q++;\r
790         p = q;\r
791         while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] bckslash added */\r
792         programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)\r
793                                         + strlen(PATCHLEVEL) + (q - p));\r
794         sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);\r
795         strncat(programVersion, p, q - p);\r
796 #else\r
797         /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */\r
798         programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)\r
799                                         + strlen(PATCHLEVEL) + strlen(first.tidy));\r
800         sprintf(programVersion, "%s %s.%s + %s", PRODUCT, VERSION, PATCHLEVEL, first.tidy);\r
801 #endif\r
802     }\r
803 \r
804     if (!appData.icsActive) {\r
805       char buf[MSG_SIZ];\r
806       /* Check for variants that are supported only in ICS mode,\r
807          or not at all.  Some that are accepted here nevertheless\r
808          have bugs; see comments below.\r
809       */\r
810       VariantClass variant = StringToVariant(appData.variant);\r
811       switch (variant) {\r
812       case VariantBughouse:     /* need four players and two boards */\r
813       case VariantKriegspiel:   /* need to hide pieces and move details */\r
814       /* case VariantFischeRandom: (Fabien: moved below) */\r
815         sprintf(buf, "Variant %s supported only in ICS mode", appData.variant);\r
816         DisplayFatalError(buf, 0, 2);\r
817         return;\r
818 \r
819       case VariantUnknown:\r
820       case VariantLoadable:\r
821       case Variant29:\r
822       case Variant30:\r
823       case Variant31:\r
824       case Variant32:\r
825       case Variant33:\r
826       case Variant34:\r
827       case Variant35:\r
828       case Variant36:\r
829       default:\r
830         sprintf(buf, "Unknown variant name %s", appData.variant);\r
831         DisplayFatalError(buf, 0, 2);\r
832         return;\r
833 \r
834       case VariantXiangqi:    /* [HGM] repetition rules not implemented */\r
835       case VariantFairy:      /* [HGM] TestLegality definitely off! */\r
836       case VariantGothic:     /* [HGM] should work */\r
837       case VariantCapablanca: /* [HGM] should work */\r
838       case VariantCourier:    /* [HGM] initial forced moves not implemented */\r
839       case VariantShogi:      /* [HGM] drops not tested for legality */\r
840       case VariantKnightmate: /* [HGM] should work */\r
841       case VariantCylinder:   /* [HGM] untested */\r
842       case VariantFalcon:     /* [HGM] untested */\r
843       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)\r
844                                  offboard interposition not understood */\r
845       case VariantNormal:     /* definitely works! */\r
846       case VariantWildCastle: /* pieces not automatically shuffled */\r
847       case VariantNoCastle:   /* pieces not automatically shuffled */\r
848       case VariantFischeRandom: /* [HGM] works and shuffles pieces */\r
849       case VariantLosers:     /* should work except for win condition,\r
850                                  and doesn't know captures are mandatory */\r
851       case VariantSuicide:    /* should work except for win condition,\r
852                                  and doesn't know captures are mandatory */\r
853       case VariantGiveaway:   /* should work except for win condition,\r
854                                  and doesn't know captures are mandatory */\r
855       case VariantTwoKings:   /* should work */\r
856       case VariantAtomic:     /* should work except for win condition */\r
857       case Variant3Check:     /* should work except for win condition */\r
858       case VariantShatranj:   /* should work except for all win conditions */\r
859       case VariantBerolina:   /* might work if TestLegality is off */\r
860       case VariantCapaRandom: /* should work */\r
861       case VariantJanus:      /* should work */\r
862       case VariantSuper:      /* experimental */\r
863         break;\r
864       }\r
865     }\r
866 \r
867     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard\r
868     InitEngineUCI( installDir, &second );\r
869 }\r
870 \r
871 int NextIntegerFromString( char ** str, long * value )\r
872 {\r
873     int result = -1;\r
874     char * s = *str;\r
875 \r
876     while( *s == ' ' || *s == '\t' ) {\r
877         s++;\r
878     }\r
879 \r
880     *value = 0;\r
881 \r
882     if( *s >= '0' && *s <= '9' ) {\r
883         while( *s >= '0' && *s <= '9' ) {\r
884             *value = *value * 10 + (*s - '0');\r
885             s++;\r
886         }\r
887 \r
888         result = 0;\r
889     }\r
890 \r
891     *str = s;\r
892 \r
893     return result;\r
894 }\r
895 \r
896 int NextTimeControlFromString( char ** str, long * value )\r
897 {\r
898     long temp;\r
899     int result = NextIntegerFromString( str, &temp );\r
900 \r
901     if( result == 0 ) {\r
902         *value = temp * 60; /* Minutes */\r
903         if( **str == ':' ) {\r
904             (*str)++;\r
905             result = NextIntegerFromString( str, &temp );\r
906             *value += temp; /* Seconds */\r
907         }\r
908     }\r
909 \r
910     return result;\r
911 }\r
912 \r
913 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)\r
914 {   /* [HGM] routine added to read '+moves/time' for secondary time control */\r
915     int result = -1; long temp, temp2;\r
916 \r
917     if(**str != '+') return -1; // old params remain in force!\r
918     (*str)++;\r
919     if( NextTimeControlFromString( str, &temp ) ) return -1;\r
920 \r
921     if(**str != '/') {\r
922         /* time only: incremental or sudden-death time control */\r
923         if(**str == '+') { /* increment follows; read it */\r
924             (*str)++;\r
925             if(result = NextIntegerFromString( str, &temp2)) return -1;\r
926             *inc = temp2 * 1000;\r
927         } else *inc = 0;\r
928         *moves = 0; *tc = temp * 1000; \r
929         return 0;\r
930     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */\r
931 \r
932     (*str)++; /* classical time control */\r
933     result = NextTimeControlFromString( str, &temp2);\r
934     if(result == 0) {\r
935         *moves = temp/60;\r
936         *tc    = temp2 * 1000;\r
937         *inc   = 0;\r
938     }\r
939     return result;\r
940 }\r
941 \r
942 int GetTimeQuota(int movenr)\r
943 {   /* [HGM] get time to add from the multi-session time-control string */\r
944     int moves=1; /* kludge to force reading of first session */\r
945     long time, increment;\r
946     char *s = fullTimeControlString;\r
947 \r
948     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);\r
949     do {\r
950         if(moves) NextSessionFromString(&s, &moves, &time, &increment);\r
951         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);\r
952         if(movenr == -1) return time;    /* last move before new session     */\r
953         if(!moves) return increment;     /* current session is incremental   */\r
954         if(movenr >= 0) movenr -= moves; /* we already finished this session */\r
955     } while(movenr >= -1);               /* try again for next session       */\r
956 \r
957     return 0; // no new time quota on this move\r
958 }\r
959 \r
960 int\r
961 ParseTimeControl(tc, ti, mps)\r
962      char *tc;\r
963      int ti;\r
964      int mps;\r
965 {\r
966 #if 0\r
967     int matched, min, sec;\r
968 \r
969     matched = sscanf(tc, "%d:%d", &min, &sec);\r
970     if (matched == 1) {\r
971         timeControl = min * 60 * 1000;\r
972     } else if (matched == 2) {\r
973         timeControl = (min * 60 + sec) * 1000;\r
974     } else {\r
975         return FALSE;\r
976     }\r
977 #else\r
978     long tc1;\r
979     long tc2;\r
980     char buf[MSG_SIZ];\r
981 \r
982     if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;\r
983     if(ti > 0) {\r
984         if(mps)\r
985              sprintf(buf, "+%d/%s+%d", mps, tc, ti);\r
986         else sprintf(buf, "+%s+%d", tc, ti);\r
987     } else {\r
988         if(mps)\r
989              sprintf(buf, "+%d/%s", mps, tc);\r
990         else sprintf(buf, "+%s", tc);\r
991     }\r
992     fullTimeControlString = StrSave(buf);\r
993 \r
994     if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {\r
995         return FALSE;\r
996     }\r
997 \r
998     if( *tc == '/' ) {\r
999         /* Parse second time control */\r
1000         tc++;\r
1001 \r
1002         if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {\r
1003             return FALSE;\r
1004         }\r
1005 \r
1006         if( tc2 == 0 ) {\r
1007             return FALSE;\r
1008         }\r
1009 \r
1010         timeControl_2 = tc2 * 1000;\r
1011     }\r
1012     else {\r
1013         timeControl_2 = 0;\r
1014     }\r
1015 \r
1016     if( tc1 == 0 ) {\r
1017         return FALSE;\r
1018     }\r
1019 \r
1020     timeControl = tc1 * 1000;\r
1021 #endif\r
1022 \r
1023     if (ti >= 0) {\r
1024         timeIncrement = ti * 1000;  /* convert to ms */\r
1025         movesPerSession = 0;\r
1026     } else {\r
1027         timeIncrement = 0;\r
1028         movesPerSession = mps;\r
1029     }\r
1030     return TRUE;\r
1031 }\r
1032 \r
1033 void\r
1034 InitBackEnd2()\r
1035 {\r
1036     if (appData.debugMode) {\r
1037         fprintf(debugFP, "%s\n", programVersion);\r
1038     }\r
1039 \r
1040     if (appData.matchGames > 0) {\r
1041         appData.matchMode = TRUE;\r
1042     } else if (appData.matchMode) {\r
1043         appData.matchGames = 1;\r
1044     }\r
1045     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */\r
1046         appData.matchGames = appData.sameColorGames;\r
1047     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */\r
1048         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;\r
1049         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;\r
1050     }\r
1051     Reset(TRUE, FALSE);\r
1052     if (appData.noChessProgram || first.protocolVersion == 1) {\r
1053       InitBackEnd3();\r
1054     } else {\r
1055       /* kludge: allow timeout for initial "feature" commands */\r
1056       FreezeUI();\r
1057       DisplayMessage("", "Starting chess program");\r
1058       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);\r
1059     }\r
1060 }\r
1061 \r
1062 void\r
1063 InitBackEnd3 P((void))\r
1064 {\r
1065     GameMode initialMode;\r
1066     char buf[MSG_SIZ];\r
1067     int err;\r
1068 \r
1069     InitChessProgram(&first, startedFromSetupPosition);\r
1070 \r
1071     if (appData.icsActive) {\r
1072         err = establish();\r
1073         if (err != 0) {\r
1074             if (*appData.icsCommPort != NULLCHAR) {\r
1075                 sprintf(buf, "Could not open comm port %s",  \r
1076                         appData.icsCommPort);\r
1077             } else {\r
1078                 sprintf(buf, "Could not connect to host %s, port %s",  \r
1079                         appData.icsHost, appData.icsPort);\r
1080             }\r
1081             DisplayFatalError(buf, err, 1);\r
1082             return;\r
1083         }\r
1084         SetICSMode();\r
1085         telnetISR =\r
1086           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);\r
1087         fromUserISR =\r
1088           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);\r
1089     } else if (appData.noChessProgram) {\r
1090         SetNCPMode();\r
1091     } else {\r
1092         SetGNUMode();\r
1093     }\r
1094 \r
1095     if (*appData.cmailGameName != NULLCHAR) {\r
1096         SetCmailMode();\r
1097         OpenLoopback(&cmailPR);\r
1098         cmailISR =\r
1099           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);\r
1100     }\r
1101     \r
1102     ThawUI();\r
1103     DisplayMessage("", "");\r
1104     if (StrCaseCmp(appData.initialMode, "") == 0) {\r
1105       initialMode = BeginningOfGame;\r
1106     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {\r
1107       initialMode = TwoMachinesPlay;\r
1108     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {\r
1109       initialMode = AnalyzeFile; \r
1110     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {\r
1111       initialMode = AnalyzeMode;\r
1112     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {\r
1113       initialMode = MachinePlaysWhite;\r
1114     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {\r
1115       initialMode = MachinePlaysBlack;\r
1116     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {\r
1117       initialMode = EditGame;\r
1118     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {\r
1119       initialMode = EditPosition;\r
1120     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {\r
1121       initialMode = Training;\r
1122     } else {\r
1123       sprintf(buf, "Unknown initialMode %s", appData.initialMode);\r
1124       DisplayFatalError(buf, 0, 2);\r
1125       return;\r
1126     }\r
1127 \r
1128     if (appData.matchMode) {\r
1129         /* Set up machine vs. machine match */\r
1130         if (appData.noChessProgram) {\r
1131             DisplayFatalError("Can't have a match with no chess programs",\r
1132                               0, 2);\r
1133             return;\r
1134         }\r
1135         matchMode = TRUE;\r
1136         matchGame = 1;\r
1137         if (*appData.loadGameFile != NULLCHAR) {\r
1138             int index = appData.loadGameIndex; // [HGM] autoinc\r
1139             if(index<0) lastIndex = index = 1;\r
1140             if (!LoadGameFromFile(appData.loadGameFile,\r
1141                                   index,\r
1142                                   appData.loadGameFile, FALSE)) {\r
1143                 DisplayFatalError("Bad game file", 0, 1);\r
1144                 return;\r
1145             }\r
1146         } else if (*appData.loadPositionFile != NULLCHAR) {\r
1147             int index = appData.loadPositionIndex; // [HGM] autoinc\r
1148             if(index<0) lastIndex = index = 1;\r
1149             if (!LoadPositionFromFile(appData.loadPositionFile,\r
1150                                       index,\r
1151                                       appData.loadPositionFile)) {\r
1152                 DisplayFatalError("Bad position file", 0, 1);\r
1153                 return;\r
1154             }\r
1155         }\r
1156         TwoMachinesEvent();\r
1157     } else if (*appData.cmailGameName != NULLCHAR) {\r
1158         /* Set up cmail mode */\r
1159         ReloadCmailMsgEvent(TRUE);\r
1160     } else {\r
1161         /* Set up other modes */\r
1162         if (initialMode == AnalyzeFile) {\r
1163           if (*appData.loadGameFile == NULLCHAR) {\r
1164             DisplayFatalError("AnalyzeFile mode requires a game file", 0, 1);\r
1165             return;\r
1166           }\r
1167         }\r
1168         if (*appData.loadGameFile != NULLCHAR) {\r
1169             (void) LoadGameFromFile(appData.loadGameFile,\r
1170                                     appData.loadGameIndex,\r
1171                                     appData.loadGameFile, TRUE);\r
1172         } else if (*appData.loadPositionFile != NULLCHAR) {\r
1173             (void) LoadPositionFromFile(appData.loadPositionFile,\r
1174                                         appData.loadPositionIndex,\r
1175                                         appData.loadPositionFile);\r
1176             /* [HGM] try to make self-starting even after FEN load */\r
1177             /* to allow automatic setup of fairy variants with wtm */\r
1178             if(initialMode == BeginningOfGame && !blackPlaysFirst) {\r
1179                 gameMode = BeginningOfGame;\r
1180                 setboardSpoiledMachineBlack = 1;\r
1181             }\r
1182             /* [HGM] loadPos: make that every new game uses the setup */\r
1183             /* from file as long as we do not switch variant          */\r
1184             if(!blackPlaysFirst) { int i;\r
1185                 startedFromPositionFile = TRUE;\r
1186                 CopyBoard(filePosition, boards[0]);\r
1187                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];\r
1188             }\r
1189         }\r
1190         if (initialMode == AnalyzeMode) {\r
1191           if (appData.noChessProgram) {\r
1192             DisplayFatalError("Analysis mode requires a chess engine", 0, 2);\r
1193             return;\r
1194           }\r
1195           if (appData.icsActive) {\r
1196             DisplayFatalError("Analysis mode does not work with ICS mode",0,2);\r
1197             return;\r
1198           }\r
1199           AnalyzeModeEvent();\r
1200         } else if (initialMode == AnalyzeFile) {\r
1201           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent\r
1202           ShowThinkingEvent();\r
1203           AnalyzeFileEvent();\r
1204           AnalysisPeriodicEvent(1);\r
1205         } else if (initialMode == MachinePlaysWhite) {\r
1206           if (appData.noChessProgram) {\r
1207             DisplayFatalError("MachineWhite mode requires a chess engine",\r
1208                               0, 2);\r
1209             return;\r
1210           }\r
1211           if (appData.icsActive) {\r
1212             DisplayFatalError("MachineWhite mode does not work with ICS mode",\r
1213                               0, 2);\r
1214             return;\r
1215           }\r
1216           MachineWhiteEvent();\r
1217         } else if (initialMode == MachinePlaysBlack) {\r
1218           if (appData.noChessProgram) {\r
1219             DisplayFatalError("MachineBlack mode requires a chess engine",\r
1220                               0, 2);\r
1221             return;\r
1222           }\r
1223           if (appData.icsActive) {\r
1224             DisplayFatalError("MachineBlack mode does not work with ICS mode",\r
1225                               0, 2);\r
1226             return;\r
1227           }\r
1228           MachineBlackEvent();\r
1229         } else if (initialMode == TwoMachinesPlay) {\r
1230           if (appData.noChessProgram) {\r
1231             DisplayFatalError("TwoMachines mode requires a chess engine",\r
1232                               0, 2);\r
1233             return;\r
1234           }\r
1235           if (appData.icsActive) {\r
1236             DisplayFatalError("TwoMachines mode does not work with ICS mode",\r
1237                               0, 2);\r
1238             return;\r
1239           }\r
1240           TwoMachinesEvent();\r
1241         } else if (initialMode == EditGame) {\r
1242           EditGameEvent();\r
1243         } else if (initialMode == EditPosition) {\r
1244           EditPositionEvent();\r
1245         } else if (initialMode == Training) {\r
1246           if (*appData.loadGameFile == NULLCHAR) {\r
1247             DisplayFatalError("Training mode requires a game file", 0, 2);\r
1248             return;\r
1249           }\r
1250           TrainingEvent();\r
1251         }\r
1252     }\r
1253 }\r
1254 \r
1255 /*\r
1256  * Establish will establish a contact to a remote host.port.\r
1257  * Sets icsPR to a ProcRef for a process (or pseudo-process)\r
1258  *  used to talk to the host.\r
1259  * Returns 0 if okay, error code if not.\r
1260  */\r
1261 int\r
1262 establish()\r
1263 {\r
1264     char buf[MSG_SIZ];\r
1265 \r
1266     if (*appData.icsCommPort != NULLCHAR) {\r
1267         /* Talk to the host through a serial comm port */\r
1268         return OpenCommPort(appData.icsCommPort, &icsPR);\r
1269 \r
1270     } else if (*appData.gateway != NULLCHAR) {\r
1271         if (*appData.remoteShell == NULLCHAR) {\r
1272             /* Use the rcmd protocol to run telnet program on a gateway host */\r
1273             sprintf(buf, "%s %s %s",\r
1274                     appData.telnetProgram, appData.icsHost, appData.icsPort);\r
1275             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);\r
1276 \r
1277         } else {\r
1278             /* Use the rsh program to run telnet program on a gateway host */\r
1279             if (*appData.remoteUser == NULLCHAR) {\r
1280                 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,\r
1281                         appData.gateway, appData.telnetProgram,\r
1282                         appData.icsHost, appData.icsPort);\r
1283             } else {\r
1284                 sprintf(buf, "%s %s -l %s %s %s %s",\r
1285                         appData.remoteShell, appData.gateway, \r
1286                         appData.remoteUser, appData.telnetProgram,\r
1287                         appData.icsHost, appData.icsPort);\r
1288             }\r
1289             return StartChildProcess(buf, "", &icsPR);\r
1290 \r
1291         }\r
1292     } else if (appData.useTelnet) {\r
1293         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);\r
1294 \r
1295     } else {\r
1296         /* TCP socket interface differs somewhat between\r
1297            Unix and NT; handle details in the front end.\r
1298            */\r
1299         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);\r
1300     }\r
1301 }\r
1302 \r
1303 void\r
1304 show_bytes(fp, buf, count)\r
1305      FILE *fp;\r
1306      char *buf;\r
1307      int count;\r
1308 {\r
1309     while (count--) {\r
1310         if (*buf < 040 || *(unsigned char *) buf > 0177) {\r
1311             fprintf(fp, "\\%03o", *buf & 0xff);\r
1312         } else {\r
1313             putc(*buf, fp);\r
1314         }\r
1315         buf++;\r
1316     }\r
1317     fflush(fp);\r
1318 }\r
1319 \r
1320 /* Returns an errno value */\r
1321 int\r
1322 OutputMaybeTelnet(pr, message, count, outError)\r
1323      ProcRef pr;\r
1324      char *message;\r
1325      int count;\r
1326      int *outError;\r
1327 {\r
1328     char buf[8192], *p, *q, *buflim;\r
1329     int left, newcount, outcount;\r
1330 \r
1331     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||\r
1332         *appData.gateway != NULLCHAR) {\r
1333         if (appData.debugMode) {\r
1334             fprintf(debugFP, ">ICS: ");\r
1335             show_bytes(debugFP, message, count);\r
1336             fprintf(debugFP, "\n");\r
1337         }\r
1338         return OutputToProcess(pr, message, count, outError);\r
1339     }\r
1340 \r
1341     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */\r
1342     p = message;\r
1343     q = buf;\r
1344     left = count;\r
1345     newcount = 0;\r
1346     while (left) {\r
1347         if (q >= buflim) {\r
1348             if (appData.debugMode) {\r
1349                 fprintf(debugFP, ">ICS: ");\r
1350                 show_bytes(debugFP, buf, newcount);\r
1351                 fprintf(debugFP, "\n");\r
1352             }\r
1353             outcount = OutputToProcess(pr, buf, newcount, outError);\r
1354             if (outcount < newcount) return -1; /* to be sure */\r
1355             q = buf;\r
1356             newcount = 0;\r
1357         }\r
1358         if (*p == '\n') {\r
1359             *q++ = '\r';\r
1360             newcount++;\r
1361         } else if (((unsigned char) *p) == TN_IAC) {\r
1362             *q++ = (char) TN_IAC;\r
1363             newcount ++;\r
1364         }\r
1365         *q++ = *p++;\r
1366         newcount++;\r
1367         left--;\r
1368     }\r
1369     if (appData.debugMode) {\r
1370         fprintf(debugFP, ">ICS: ");\r
1371         show_bytes(debugFP, buf, newcount);\r
1372         fprintf(debugFP, "\n");\r
1373     }\r
1374     outcount = OutputToProcess(pr, buf, newcount, outError);\r
1375     if (outcount < newcount) return -1; /* to be sure */\r
1376     return count;\r
1377 }\r
1378 \r
1379 void\r
1380 read_from_player(isr, closure, message, count, error)\r
1381      InputSourceRef isr;\r
1382      VOIDSTAR closure;\r
1383      char *message;\r
1384      int count;\r
1385      int error;\r
1386 {\r
1387     int outError, outCount;\r
1388     static int gotEof = 0;\r
1389 \r
1390     /* Pass data read from player on to ICS */\r
1391     if (count > 0) {\r
1392         gotEof = 0;\r
1393         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);\r
1394         if (outCount < count) {\r
1395             DisplayFatalError("Error writing to ICS", outError, 1);\r
1396         }\r
1397     } else if (count < 0) {\r
1398         RemoveInputSource(isr);\r
1399         DisplayFatalError("Error reading from keyboard", error, 1);\r
1400     } else if (gotEof++ > 0) {\r
1401         RemoveInputSource(isr);\r
1402         DisplayFatalError("Got end of file from keyboard", 0, 0);\r
1403     }\r
1404 }\r
1405 \r
1406 void\r
1407 SendToICS(s)\r
1408      char *s;\r
1409 {\r
1410     int count, outCount, outError;\r
1411 \r
1412     if (icsPR == NULL) return;\r
1413 \r
1414     count = strlen(s);\r
1415     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);\r
1416     if (outCount < count) {\r
1417         DisplayFatalError("Error writing to ICS", outError, 1);\r
1418     }\r
1419 }\r
1420 \r
1421 /* This is used for sending logon scripts to the ICS. Sending\r
1422    without a delay causes problems when using timestamp on ICC\r
1423    (at least on my machine). */\r
1424 void\r
1425 SendToICSDelayed(s,msdelay)\r
1426      char *s;\r
1427      long msdelay;\r
1428 {\r
1429     int count, outCount, outError;\r
1430 \r
1431     if (icsPR == NULL) return;\r
1432 \r
1433     count = strlen(s);\r
1434     if (appData.debugMode) {\r
1435         fprintf(debugFP, ">ICS: ");\r
1436         show_bytes(debugFP, s, count);\r
1437         fprintf(debugFP, "\n");\r
1438     }\r
1439     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,\r
1440                                       msdelay);\r
1441     if (outCount < count) {\r
1442         DisplayFatalError("Error writing to ICS", outError, 1);\r
1443     }\r
1444 }\r
1445 \r
1446 \r
1447 /* Remove all highlighting escape sequences in s\r
1448    Also deletes any suffix starting with '(' \r
1449    */\r
1450 char *\r
1451 StripHighlightAndTitle(s)\r
1452      char *s;\r
1453 {\r
1454     static char retbuf[MSG_SIZ];\r
1455     char *p = retbuf;\r
1456 \r
1457     while (*s != NULLCHAR) {\r
1458         while (*s == '\033') {\r
1459             while (*s != NULLCHAR && !isalpha(*s)) s++;\r
1460             if (*s != NULLCHAR) s++;\r
1461         }\r
1462         while (*s != NULLCHAR && *s != '\033') {\r
1463             if (*s == '(' || *s == '[') {\r
1464                 *p = NULLCHAR;\r
1465                 return retbuf;\r
1466             }\r
1467             *p++ = *s++;\r
1468         }\r
1469     }\r
1470     *p = NULLCHAR;\r
1471     return retbuf;\r
1472 }\r
1473 \r
1474 /* Remove all highlighting escape sequences in s */\r
1475 char *\r
1476 StripHighlight(s)\r
1477      char *s;\r
1478 {\r
1479     static char retbuf[MSG_SIZ];\r
1480     char *p = retbuf;\r
1481 \r
1482     while (*s != NULLCHAR) {\r
1483         while (*s == '\033') {\r
1484             while (*s != NULLCHAR && !isalpha(*s)) s++;\r
1485             if (*s != NULLCHAR) s++;\r
1486         }\r
1487         while (*s != NULLCHAR && *s != '\033') {\r
1488             *p++ = *s++;\r
1489         }\r
1490     }\r
1491     *p = NULLCHAR;\r
1492     return retbuf;\r
1493 }\r
1494 \r
1495 char *variantNames[] = VARIANT_NAMES;\r
1496 char *\r
1497 VariantName(v)\r
1498      VariantClass v;\r
1499 {\r
1500     return variantNames[v];\r
1501 }\r
1502 \r
1503 \r
1504 /* Identify a variant from the strings the chess servers use or the\r
1505    PGN Variant tag names we use. */\r
1506 VariantClass\r
1507 StringToVariant(e)\r
1508      char *e;\r
1509 {\r
1510     char *p;\r
1511     int wnum = -1;\r
1512     VariantClass v = VariantNormal;\r
1513     int i, found = FALSE;\r
1514     char buf[MSG_SIZ];\r
1515 \r
1516     if (!e) return v;\r
1517 \r
1518     /* [HGM] skip over optional board-size prefixes */\r
1519     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||\r
1520         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {\r
1521         while( *e++ != '_');\r
1522     }\r
1523 \r
1524     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {\r
1525       if (StrCaseStr(e, variantNames[i])) {\r
1526         v = (VariantClass) i;\r
1527         found = TRUE;\r
1528         break;\r
1529       }\r
1530     }\r
1531 \r
1532     if (!found) {\r
1533       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))\r
1534           || StrCaseStr(e, "wild/fr")) {\r
1535         v = VariantFischeRandom;\r
1536       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||\r
1537                  (i = 1, p = StrCaseStr(e, "w"))) {\r
1538         p += i;\r
1539         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;\r
1540         if (isdigit(*p)) {\r
1541           wnum = atoi(p);\r
1542         } else {\r
1543           wnum = -1;\r
1544         }\r
1545         switch (wnum) {\r
1546         case 0: /* FICS only, actually */\r
1547         case 1:\r
1548           /* Castling legal even if K starts on d-file */\r
1549           v = VariantWildCastle;\r
1550           break;\r
1551         case 2:\r
1552         case 3:\r
1553         case 4:\r
1554           /* Castling illegal even if K & R happen to start in\r
1555              normal positions. */\r
1556           v = VariantNoCastle;\r
1557           break;\r
1558         case 5:\r
1559         case 7:\r
1560         case 8:\r
1561         case 10:\r
1562         case 11:\r
1563         case 12:\r
1564         case 13:\r
1565         case 14:\r
1566         case 15:\r
1567         case 18:\r
1568         case 19:\r
1569           /* Castling legal iff K & R start in normal positions */\r
1570           v = VariantNormal;\r
1571           break;\r
1572         case 6:\r
1573         case 20:\r
1574         case 21:\r
1575           /* Special wilds for position setup; unclear what to do here */\r
1576           v = VariantLoadable;\r
1577           break;\r
1578         case 9:\r
1579           /* Bizarre ICC game */\r
1580           v = VariantTwoKings;\r
1581           break;\r
1582         case 16:\r
1583           v = VariantKriegspiel;\r
1584           break;\r
1585         case 17:\r
1586           v = VariantLosers;\r
1587           break;\r
1588         case 22:\r
1589           v = VariantFischeRandom;\r
1590           break;\r
1591         case 23:\r
1592           v = VariantCrazyhouse;\r
1593           break;\r
1594         case 24:\r
1595           v = VariantBughouse;\r
1596           break;\r
1597         case 25:\r
1598           v = Variant3Check;\r
1599           break;\r
1600         case 26:\r
1601           /* Not quite the same as FICS suicide! */\r
1602           v = VariantGiveaway;\r
1603           break;\r
1604         case 27:\r
1605           v = VariantAtomic;\r
1606           break;\r
1607         case 28:\r
1608           v = VariantShatranj;\r
1609           break;\r
1610 \r
1611         /* Temporary names for future ICC types.  The name *will* change in \r
1612            the next xboard/WinBoard release after ICC defines it. */\r
1613         case 29:\r
1614           v = Variant29;\r
1615           break;\r
1616         case 30:\r
1617           v = Variant30;\r
1618           break;\r
1619         case 31:\r
1620           v = Variant31;\r
1621           break;\r
1622         case 32:\r
1623           v = Variant32;\r
1624           break;\r
1625         case 33:\r
1626           v = Variant33;\r
1627           break;\r
1628         case 34:\r
1629           v = Variant34;\r
1630           break;\r
1631         case 35:\r
1632           v = Variant35;\r
1633           break;\r
1634         case 36:\r
1635           v = Variant36;\r
1636           break;\r
1637         case 37:\r
1638           v = VariantShogi;\r
1639           break;\r
1640         case 38:\r
1641           v = VariantXiangqi;\r
1642           break;\r
1643         case 39:\r
1644           v = VariantCourier;\r
1645           break;\r
1646         case 40:\r
1647           v = VariantGothic;\r
1648           break;\r
1649         case 41:\r
1650           v = VariantCapablanca;\r
1651           break;\r
1652         case 42:\r
1653           v = VariantKnightmate;\r
1654           break;\r
1655         case 43:\r
1656           v = VariantFairy;\r
1657           break;\r
1658         case 44:\r
1659           v = VariantCylinder;\r
1660           break;\r
1661         case 45:\r
1662           v = VariantFalcon;\r
1663           break;\r
1664         case 46:\r
1665           v = VariantCapaRandom;\r
1666           break;\r
1667         case 47:\r
1668           v = VariantBerolina;\r
1669           break;\r
1670         case 48:\r
1671           v = VariantJanus;\r
1672           break;\r
1673         case 49:\r
1674           v = VariantSuper;\r
1675           break;\r
1676         case -1:\r
1677           /* Found "wild" or "w" in the string but no number;\r
1678              must assume it's normal chess. */\r
1679           v = VariantNormal;\r
1680           break;\r
1681         default:\r
1682           sprintf(buf, "Unknown wild type %d", wnum);\r
1683           DisplayError(buf, 0);\r
1684           v = VariantUnknown;\r
1685           break;\r
1686         }\r
1687       }\r
1688     }\r
1689     if (appData.debugMode) {\r
1690       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",\r
1691               e, wnum, VariantName(v));\r
1692     }\r
1693     return v;\r
1694 }\r
1695 \r
1696 static int leftover_start = 0, leftover_len = 0;\r
1697 char star_match[STAR_MATCH_N][MSG_SIZ];\r
1698 \r
1699 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,\r
1700    advance *index beyond it, and set leftover_start to the new value of\r
1701    *index; else return FALSE.  If pattern contains the character '*', it\r
1702    matches any sequence of characters not containing '\r', '\n', or the\r
1703    character following the '*' (if any), and the matched sequence(s) are\r
1704    copied into star_match.\r
1705    */\r
1706 int\r
1707 looking_at(buf, index, pattern)\r
1708      char *buf;\r
1709      int *index;\r
1710      char *pattern;\r
1711 {\r
1712     char *bufp = &buf[*index], *patternp = pattern;\r
1713     int star_count = 0;\r
1714     char *matchp = star_match[0];\r
1715     \r
1716     for (;;) {\r
1717         if (*patternp == NULLCHAR) {\r
1718             *index = leftover_start = bufp - buf;\r
1719             *matchp = NULLCHAR;\r
1720             return TRUE;\r
1721         }\r
1722         if (*bufp == NULLCHAR) return FALSE;\r
1723         if (*patternp == '*') {\r
1724             if (*bufp == *(patternp + 1)) {\r
1725                 *matchp = NULLCHAR;\r
1726                 matchp = star_match[++star_count];\r
1727                 patternp += 2;\r
1728                 bufp++;\r
1729                 continue;\r
1730             } else if (*bufp == '\n' || *bufp == '\r') {\r
1731                 patternp++;\r
1732                 if (*patternp == NULLCHAR)\r
1733                   continue;\r
1734                 else\r
1735                   return FALSE;\r
1736             } else {\r
1737                 *matchp++ = *bufp++;\r
1738                 continue;\r
1739             }\r
1740         }\r
1741         if (*patternp != *bufp) return FALSE;\r
1742         patternp++;\r
1743         bufp++;\r
1744     }\r
1745 }\r
1746 \r
1747 void\r
1748 SendToPlayer(data, length)\r
1749      char *data;\r
1750      int length;\r
1751 {\r
1752     int error, outCount;\r
1753     outCount = OutputToProcess(NoProc, data, length, &error);\r
1754     if (outCount < length) {\r
1755         DisplayFatalError("Error writing to display", error, 1);\r
1756     }\r
1757 }\r
1758 \r
1759 void\r
1760 PackHolding(packed, holding)\r
1761      char packed[];\r
1762      char *holding;\r
1763 {\r
1764     char *p = holding;\r
1765     char *q = packed;\r
1766     int runlength = 0;\r
1767     int curr = 9999;\r
1768     do {\r
1769         if (*p == curr) {\r
1770             runlength++;\r
1771         } else {\r
1772             switch (runlength) {\r
1773               case 0:\r
1774                 break;\r
1775               case 1:\r
1776                 *q++ = curr;\r
1777                 break;\r
1778               case 2:\r
1779                 *q++ = curr;\r
1780                 *q++ = curr;\r
1781                 break;\r
1782               default:\r
1783                 sprintf(q, "%d", runlength);\r
1784                 while (*q) q++;\r
1785                 *q++ = curr;\r
1786                 break;\r
1787             }\r
1788             runlength = 1;\r
1789             curr = *p;\r
1790         }\r
1791     } while (*p++);\r
1792     *q = NULLCHAR;\r
1793 }\r
1794 \r
1795 /* Telnet protocol requests from the front end */\r
1796 void\r
1797 TelnetRequest(ddww, option)\r
1798      unsigned char ddww, option;\r
1799 {\r
1800     unsigned char msg[3];\r
1801     int outCount, outError;\r
1802 \r
1803     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;\r
1804 \r
1805     if (appData.debugMode) {\r
1806         char buf1[8], buf2[8], *ddwwStr, *optionStr;\r
1807         switch (ddww) {\r
1808           case TN_DO:\r
1809             ddwwStr = "DO";\r
1810             break;\r
1811           case TN_DONT:\r
1812             ddwwStr = "DONT";\r
1813             break;\r
1814           case TN_WILL:\r
1815             ddwwStr = "WILL";\r
1816             break;\r
1817           case TN_WONT:\r
1818             ddwwStr = "WONT";\r
1819             break;\r
1820           default:\r
1821             ddwwStr = buf1;\r
1822             sprintf(buf1, "%d", ddww);\r
1823             break;\r
1824         }\r
1825         switch (option) {\r
1826           case TN_ECHO:\r
1827             optionStr = "ECHO";\r
1828             break;\r
1829           default:\r
1830             optionStr = buf2;\r
1831             sprintf(buf2, "%d", option);\r
1832             break;\r
1833         }\r
1834         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);\r
1835     }\r
1836     msg[0] = TN_IAC;\r
1837     msg[1] = ddww;\r
1838     msg[2] = option;\r
1839     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);\r
1840     if (outCount < 3) {\r
1841         DisplayFatalError("Error writing to ICS", outError, 1);\r
1842     }\r
1843 }\r
1844 \r
1845 void\r
1846 DoEcho()\r
1847 {\r
1848     if (!appData.icsActive) return;\r
1849     TelnetRequest(TN_DO, TN_ECHO);\r
1850 }\r
1851 \r
1852 void\r
1853 DontEcho()\r
1854 {\r
1855     if (!appData.icsActive) return;\r
1856     TelnetRequest(TN_DONT, TN_ECHO);\r
1857 }\r
1858 \r
1859 void\r
1860 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)\r
1861 {\r
1862     /* put the holdings sent to us by the server on the board holdings area */\r
1863     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;\r
1864     char p;\r
1865     ChessSquare piece;\r
1866 \r
1867     if(gameInfo.holdingsWidth < 2)  return;\r
1868 \r
1869     if( (int)lowestPiece >= BlackPawn ) {\r
1870         holdingsColumn = 0;\r
1871         countsColumn = 1;\r
1872         holdingsStartRow = BOARD_HEIGHT-1;\r
1873         direction = -1;\r
1874     } else {\r
1875         holdingsColumn = BOARD_WIDTH-1;\r
1876         countsColumn = BOARD_WIDTH-2;\r
1877         holdingsStartRow = 0;\r
1878         direction = 1;\r
1879     }\r
1880 \r
1881     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */\r
1882         board[i][holdingsColumn] = EmptySquare;\r
1883         board[i][countsColumn]   = (ChessSquare) 0;\r
1884     }\r
1885     while( (p=*holdings++) != NULLCHAR ) {\r
1886         piece = CharToPiece( ToUpper(p) );\r
1887         if(piece == EmptySquare) continue;\r
1888         /*j = (int) piece - (int) WhitePawn;*/\r
1889         j = PieceToNumber(piece);\r
1890         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */\r
1891         if(j < 0) continue;               /* should not happen */\r
1892         piece = (ChessSquare) ( j + (int)lowestPiece );\r
1893         board[holdingsStartRow+j*direction][holdingsColumn] = piece;\r
1894         board[holdingsStartRow+j*direction][countsColumn]++;\r
1895     }\r
1896 \r
1897 }\r
1898 \r
1899 \r
1900 void\r
1901 VariantSwitch(Board board, VariantClass newVariant)\r
1902 {\r
1903    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;\r
1904    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;\r
1905    Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;\r
1906 \r
1907    startedFromPositionFile = FALSE;\r
1908    if(gameInfo.variant == newVariant) return;\r
1909 \r
1910    /* [HGM] This routine is called each time an assignment is made to\r
1911     * gameInfo.variant during a game, to make sure the board sizes\r
1912     * are set to match the new variant. If that means adding or deleting\r
1913     * holdings, we shift the playing board accordingly\r
1914     * This kludge is needed because in ICS observe mode, we get boards\r
1915     * of an ongoing game without knowing the variant, and learn about the\r
1916     * latter only later. This can be because of the move list we requested,\r
1917     * in which case the game history is refilled from the beginning anyway,\r
1918     * but also when receiving holdings of a crazyhouse game. In the latter\r
1919     * case we want to add those holdings to the already received position.\r
1920     */\r
1921 \r
1922 \r
1923   if (appData.debugMode) {\r
1924     fprintf(debugFP, "Switch board from %s to %s\n",\r
1925                VariantName(gameInfo.variant), VariantName(newVariant));\r
1926     setbuf(debugFP, NULL);\r
1927   }\r
1928     shuffleOpenings = 0;       /* [HGM] shuffle */\r
1929     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */\r
1930     switch(newVariant) {\r
1931             case VariantShogi:\r
1932               newWidth = 9;  newHeight = 9;\r
1933               gameInfo.holdingsSize = 7;\r
1934             case VariantBughouse:\r
1935             case VariantCrazyhouse:\r
1936               newHoldingsWidth = 2; break;\r
1937             default:\r
1938               newHoldingsWidth = gameInfo.holdingsSize = 0;\r
1939     }\r
1940 \r
1941     if(newWidth  != gameInfo.boardWidth  ||\r
1942        newHeight != gameInfo.boardHeight ||\r
1943        newHoldingsWidth != gameInfo.holdingsWidth ) {\r
1944 \r
1945         /* shift position to new playing area, if needed */\r
1946         if(newHoldingsWidth > gameInfo.holdingsWidth) {\r
1947            for(i=0; i<BOARD_HEIGHT; i++) \r
1948                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)\r
1949                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =\r
1950                                                      board[i][j];\r
1951            for(i=0; i<newHeight; i++) {\r
1952                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;\r
1953                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;\r
1954            }\r
1955         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {\r
1956            for(i=0; i<BOARD_HEIGHT; i++)\r
1957                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
1958                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =\r
1959                                                  board[i][j];\r
1960         }\r
1961 \r
1962         gameInfo.boardWidth  = newWidth;\r
1963         gameInfo.boardHeight = newHeight;\r
1964         gameInfo.holdingsWidth = newHoldingsWidth;\r
1965         gameInfo.variant = newVariant;\r
1966         InitDrawingSizes(-2, 0);\r
1967 \r
1968         /* [HGM] The following should definitely be solved in a better way */\r
1969 #if 0\r
1970         CopyBoard(board, tempBoard); /* save position in case it is board[0] */\r
1971         for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];\r
1972         saveEP = epStatus[0];\r
1973 #endif\r
1974         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */\r
1975 #if 0\r
1976         epStatus[0] = saveEP;\r
1977         for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];\r
1978         CopyBoard(tempBoard, board); /* restore position received from ICS   */\r
1979 #endif\r
1980     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }\r
1981 \r
1982     forwardMostMove = oldForwardMostMove;\r
1983     backwardMostMove = oldBackwardMostMove;\r
1984     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */\r
1985 }\r
1986 \r
1987 static int loggedOn = FALSE;\r
1988 \r
1989 /*-- Game start info cache: --*/\r
1990 int gs_gamenum;\r
1991 char gs_kind[MSG_SIZ];\r
1992 static char player1Name[128] = "";\r
1993 static char player2Name[128] = "";\r
1994 static int player1Rating = -1;\r
1995 static int player2Rating = -1;\r
1996 /*----------------------------*/\r
1997 \r
1998 ColorClass curColor = ColorNormal;\r
1999 \r
2000 void\r
2001 read_from_ics(isr, closure, data, count, error)\r
2002      InputSourceRef isr;\r
2003      VOIDSTAR closure;\r
2004      char *data;\r
2005      int count;\r
2006      int error;\r
2007 {\r
2008 #define BUF_SIZE 8192\r
2009 #define STARTED_NONE 0\r
2010 #define STARTED_MOVES 1\r
2011 #define STARTED_BOARD 2\r
2012 #define STARTED_OBSERVE 3\r
2013 #define STARTED_HOLDINGS 4\r
2014 #define STARTED_CHATTER 5\r
2015 #define STARTED_COMMENT 6\r
2016 #define STARTED_MOVES_NOHIDE 7\r
2017     \r
2018     static int started = STARTED_NONE;\r
2019     static char parse[20000];\r
2020     static int parse_pos = 0;\r
2021     static char buf[BUF_SIZE + 1];\r
2022     static int firstTime = TRUE, intfSet = FALSE;\r
2023     static ColorClass prevColor = ColorNormal;\r
2024     static int savingComment = FALSE;\r
2025     char str[500];\r
2026     int i, oldi;\r
2027     int buf_len;\r
2028     int next_out;\r
2029     int tkind;\r
2030     char *p;\r
2031 \r
2032 #ifdef WIN32\r
2033     if (appData.debugMode) {\r
2034       if (!error) {\r
2035         fprintf(debugFP, "<ICS: ");\r
2036         show_bytes(debugFP, data, count);\r
2037         fprintf(debugFP, "\n");\r
2038       }\r
2039     }\r
2040 #endif\r
2041 \r
2042     if (appData.debugMode) { int f = forwardMostMove;\r
2043         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,\r
2044                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
2045     }\r
2046     if (count > 0) {\r
2047         /* If last read ended with a partial line that we couldn't parse,\r
2048            prepend it to the new read and try again. */\r
2049         if (leftover_len > 0) {\r
2050             for (i=0; i<leftover_len; i++)\r
2051               buf[i] = buf[leftover_start + i];\r
2052         }\r
2053 \r
2054         /* Copy in new characters, removing nulls and \r's */\r
2055         buf_len = leftover_len;\r
2056         for (i = 0; i < count; i++) {\r
2057             if (data[i] != NULLCHAR && data[i] != '\r')\r
2058               buf[buf_len++] = data[i];\r
2059             if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && \r
2060                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') \r
2061                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous\r
2062         }\r
2063 \r
2064         buf[buf_len] = NULLCHAR;\r
2065         next_out = leftover_len;\r
2066         leftover_start = 0;\r
2067         \r
2068         i = 0;\r
2069         while (i < buf_len) {\r
2070             /* Deal with part of the TELNET option negotiation\r
2071                protocol.  We refuse to do anything beyond the\r
2072                defaults, except that we allow the WILL ECHO option,\r
2073                which ICS uses to turn off password echoing when we are\r
2074                directly connected to it.  We reject this option\r
2075                if localLineEditing mode is on (always on in xboard)\r
2076                and we are talking to port 23, which might be a real\r
2077                telnet server that will try to keep WILL ECHO on permanently.\r
2078              */\r
2079             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {\r
2080                 static int remoteEchoOption = FALSE; /* telnet ECHO option */\r
2081                 unsigned char option;\r
2082                 oldi = i;\r
2083                 switch ((unsigned char) buf[++i]) {\r
2084                   case TN_WILL:\r
2085                     if (appData.debugMode)\r
2086                       fprintf(debugFP, "\n<WILL ");\r
2087                     switch (option = (unsigned char) buf[++i]) {\r
2088                       case TN_ECHO:\r
2089                         if (appData.debugMode)\r
2090                           fprintf(debugFP, "ECHO ");\r
2091                         /* Reply only if this is a change, according\r
2092                            to the protocol rules. */\r
2093                         if (remoteEchoOption) break;\r
2094                         if (appData.localLineEditing &&\r
2095                             atoi(appData.icsPort) == TN_PORT) {\r
2096                             TelnetRequest(TN_DONT, TN_ECHO);\r
2097                         } else {\r
2098                             EchoOff();\r
2099                             TelnetRequest(TN_DO, TN_ECHO);\r
2100                             remoteEchoOption = TRUE;\r
2101                         }\r
2102                         break;\r
2103                       default:\r
2104                         if (appData.debugMode)\r
2105                           fprintf(debugFP, "%d ", option);\r
2106                         /* Whatever this is, we don't want it. */\r
2107                         TelnetRequest(TN_DONT, option);\r
2108                         break;\r
2109                     }\r
2110                     break;\r
2111                   case TN_WONT:\r
2112                     if (appData.debugMode)\r
2113                       fprintf(debugFP, "\n<WONT ");\r
2114                     switch (option = (unsigned char) buf[++i]) {\r
2115                       case TN_ECHO:\r
2116                         if (appData.debugMode)\r
2117                           fprintf(debugFP, "ECHO ");\r
2118                         /* Reply only if this is a change, according\r
2119                            to the protocol rules. */\r
2120                         if (!remoteEchoOption) break;\r
2121                         EchoOn();\r
2122                         TelnetRequest(TN_DONT, TN_ECHO);\r
2123                         remoteEchoOption = FALSE;\r
2124                         break;\r
2125                       default:\r
2126                         if (appData.debugMode)\r
2127                           fprintf(debugFP, "%d ", (unsigned char) option);\r
2128                         /* Whatever this is, it must already be turned\r
2129                            off, because we never agree to turn on\r
2130                            anything non-default, so according to the\r
2131                            protocol rules, we don't reply. */\r
2132                         break;\r
2133                     }\r
2134                     break;\r
2135                   case TN_DO:\r
2136                     if (appData.debugMode)\r
2137                       fprintf(debugFP, "\n<DO ");\r
2138                     switch (option = (unsigned char) buf[++i]) {\r
2139                       default:\r
2140                         /* Whatever this is, we refuse to do it. */\r
2141                         if (appData.debugMode)\r
2142                           fprintf(debugFP, "%d ", option);\r
2143                         TelnetRequest(TN_WONT, option);\r
2144                         break;\r
2145                     }\r
2146                     break;\r
2147                   case TN_DONT:\r
2148                     if (appData.debugMode)\r
2149                       fprintf(debugFP, "\n<DONT ");\r
2150                     switch (option = (unsigned char) buf[++i]) {\r
2151                       default:\r
2152                         if (appData.debugMode)\r
2153                           fprintf(debugFP, "%d ", option);\r
2154                         /* Whatever this is, we are already not doing\r
2155                            it, because we never agree to do anything\r
2156                            non-default, so according to the protocol\r
2157                            rules, we don't reply. */\r
2158                         break;\r
2159                     }\r
2160                     break;\r
2161                   case TN_IAC:\r
2162                     if (appData.debugMode)\r
2163                       fprintf(debugFP, "\n<IAC ");\r
2164                     /* Doubled IAC; pass it through */\r
2165                     i--;\r
2166                     break;\r
2167                   default:\r
2168                     if (appData.debugMode)\r
2169                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);\r
2170                     /* Drop all other telnet commands on the floor */\r
2171                     break;\r
2172                 }\r
2173                 if (oldi > next_out)\r
2174                   SendToPlayer(&buf[next_out], oldi - next_out);\r
2175                 if (++i > next_out)\r
2176                   next_out = i;\r
2177                 continue;\r
2178             }\r
2179                 \r
2180             /* OK, this at least will *usually* work */\r
2181             if (!loggedOn && looking_at(buf, &i, "ics%")) {\r
2182                 loggedOn = TRUE;\r
2183             }\r
2184             \r
2185             if (loggedOn && !intfSet) {\r
2186                 if (ics_type == ICS_ICC) {\r
2187                   sprintf(str,\r
2188                           "/set-quietly interface %s\n/set-quietly style 12\n",\r
2189                           programVersion);\r
2190 \r
2191                 } else if (ics_type == ICS_CHESSNET) {\r
2192                   sprintf(str, "/style 12\n");\r
2193                 } else {\r
2194                   strcpy(str, "alias $ @\n$set interface ");\r
2195                   strcat(str, programVersion);\r
2196                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");\r
2197 #ifdef WIN32\r
2198                   strcat(str, "$iset nohighlight 1\n");\r
2199 #endif\r
2200                   strcat(str, "$iset lock 1\n$style 12\n");\r
2201                 }\r
2202                 SendToICS(str);\r
2203                 intfSet = TRUE;\r
2204             }\r
2205 \r
2206             if (started == STARTED_COMMENT) {\r
2207                 /* Accumulate characters in comment */\r
2208                 parse[parse_pos++] = buf[i];\r
2209                 if (buf[i] == '\n') {\r
2210                     parse[parse_pos] = NULLCHAR;\r
2211                     AppendComment(forwardMostMove, StripHighlight(parse));\r
2212                     started = STARTED_NONE;\r
2213                 } else {\r
2214                     /* Don't match patterns against characters in chatter */\r
2215                     i++;\r
2216                     continue;\r
2217                 }\r
2218             }\r
2219             if (started == STARTED_CHATTER) {\r
2220                 if (buf[i] != '\n') {\r
2221                     /* Don't match patterns against characters in chatter */\r
2222                     i++;\r
2223                     continue;\r
2224                 }\r
2225                 started = STARTED_NONE;\r
2226             }\r
2227 \r
2228             /* Kludge to deal with rcmd protocol */\r
2229             if (firstTime && looking_at(buf, &i, "\001*")) {\r
2230                 DisplayFatalError(&buf[1], 0, 1);\r
2231                 continue;\r
2232             } else {\r
2233                 firstTime = FALSE;\r
2234             }\r
2235 \r
2236             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {\r
2237                 ics_type = ICS_ICC;\r
2238                 ics_prefix = "/";\r
2239                 if (appData.debugMode)\r
2240                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2241                 continue;\r
2242             }\r
2243             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {\r
2244                 ics_type = ICS_FICS;\r
2245                 ics_prefix = "$";\r
2246                 if (appData.debugMode)\r
2247                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2248                 continue;\r
2249             }\r
2250             if (!loggedOn && looking_at(buf, &i, "chess.net")) {\r
2251                 ics_type = ICS_CHESSNET;\r
2252                 ics_prefix = "/";\r
2253                 if (appData.debugMode)\r
2254                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2255                 continue;\r
2256             }\r
2257 \r
2258             if (!loggedOn &&\r
2259                 (looking_at(buf, &i, "\"*\" is *a registered name") ||\r
2260                  looking_at(buf, &i, "Logging you in as \"*\"") ||\r
2261                  looking_at(buf, &i, "will be \"*\""))) {\r
2262               strcpy(ics_handle, star_match[0]);\r
2263               continue;\r
2264             }\r
2265 \r
2266             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {\r
2267               char buf[MSG_SIZ];\r
2268               sprintf(buf, "%s@%s", ics_handle, appData.icsHost);\r
2269               DisplayIcsInteractionTitle(buf);\r
2270               have_set_title = TRUE;\r
2271             }\r
2272 \r
2273             /* skip finger notes */\r
2274             if (started == STARTED_NONE &&\r
2275                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||\r
2276                  (buf[i] == '1' && buf[i+1] == '0')) &&\r
2277                 buf[i+2] == ':' && buf[i+3] == ' ') {\r
2278               started = STARTED_CHATTER;\r
2279               i += 3;\r
2280               continue;\r
2281             }\r
2282 \r
2283             /* skip formula vars */\r
2284             if (started == STARTED_NONE &&\r
2285                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {\r
2286               started = STARTED_CHATTER;\r
2287               i += 3;\r
2288               continue;\r
2289             }\r
2290 \r
2291             oldi = i;\r
2292             if (appData.zippyTalk || appData.zippyPlay) {\r
2293 #if ZIPPY\r
2294                 if (ZippyControl(buf, &i) ||\r
2295                     ZippyConverse(buf, &i) ||\r
2296                     (appData.zippyPlay && ZippyMatch(buf, &i))) {\r
2297                     loggedOn = TRUE;\r
2298                     continue;\r
2299                 }\r
2300 #endif\r
2301             } else {\r
2302                 if (/* Don't color "message" or "messages" output */\r
2303                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||\r
2304                     looking_at(buf, &i, "*. * at *:*: ") ||\r
2305                     looking_at(buf, &i, "--* (*:*): ") ||\r
2306                     /* Regular tells and says */\r
2307                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||\r
2308                     looking_at(buf, &i, "* (your partner) tells you: ") ||\r
2309                     looking_at(buf, &i, "* says: ") ||\r
2310                     /* Message notifications (same color as tells) */\r
2311                     looking_at(buf, &i, "* has left a message ") ||\r
2312                     looking_at(buf, &i, "* just sent you a message:\n") ||\r
2313                     /* Whispers and kibitzes */\r
2314                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||\r
2315                     looking_at(buf, &i, "* kibitzes: ") ||\r
2316                     /* Channel tells */\r
2317                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {\r
2318 \r
2319                   if (tkind == 1 && strchr(star_match[0], ':')) {\r
2320                       /* Avoid "tells you:" spoofs in channels */\r
2321                      tkind = 3;\r
2322                   }\r
2323                   if (star_match[0][0] == NULLCHAR ||\r
2324                       strchr(star_match[0], ' ') ||\r
2325                       (tkind == 3 && strchr(star_match[1], ' '))) {\r
2326                     /* Reject bogus matches */\r
2327                     i = oldi;\r
2328                   } else {\r
2329                     if (appData.colorize) {\r
2330                       if (oldi > next_out) {\r
2331                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2332                         next_out = oldi;\r
2333                       }\r
2334                       switch (tkind) {\r
2335                       case 1:\r
2336                         Colorize(ColorTell, FALSE);\r
2337                         curColor = ColorTell;\r
2338                         break;\r
2339                       case 2:\r
2340                         Colorize(ColorKibitz, FALSE);\r
2341                         curColor = ColorKibitz;\r
2342                         break;\r
2343                       case 3:\r
2344                         p = strrchr(star_match[1], '(');\r
2345                         if (p == NULL) {\r
2346                           p = star_match[1];\r
2347                         } else {\r
2348                           p++;\r
2349                         }\r
2350                         if (atoi(p) == 1) {\r
2351                           Colorize(ColorChannel1, FALSE);\r
2352                           curColor = ColorChannel1;\r
2353                         } else {\r
2354                           Colorize(ColorChannel, FALSE);\r
2355                           curColor = ColorChannel;\r
2356                         }\r
2357                         break;\r
2358                       case 5:\r
2359                         curColor = ColorNormal;\r
2360                         break;\r
2361                       }\r
2362                     }\r
2363                     if (started == STARTED_NONE && appData.autoComment &&\r
2364                         (gameMode == IcsObserving ||\r
2365                          gameMode == IcsPlayingWhite ||\r
2366                          gameMode == IcsPlayingBlack)) {\r
2367                       parse_pos = i - oldi;\r
2368                       memcpy(parse, &buf[oldi], parse_pos);\r
2369                       parse[parse_pos] = NULLCHAR;\r
2370                       started = STARTED_COMMENT;\r
2371                       savingComment = TRUE;\r
2372                     } else {\r
2373                       started = STARTED_CHATTER;\r
2374                       savingComment = FALSE;\r
2375                     }\r
2376                     loggedOn = TRUE;\r
2377                     continue;\r
2378                   }\r
2379                 }\r
2380 \r
2381                 if (looking_at(buf, &i, "* s-shouts: ") ||\r
2382                     looking_at(buf, &i, "* c-shouts: ")) {\r
2383                     if (appData.colorize) {\r
2384                         if (oldi > next_out) {\r
2385                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2386                             next_out = oldi;\r
2387                         }\r
2388                         Colorize(ColorSShout, FALSE);\r
2389                         curColor = ColorSShout;\r
2390                     }\r
2391                     loggedOn = TRUE;\r
2392                     started = STARTED_CHATTER;\r
2393                     continue;\r
2394                 }\r
2395 \r
2396                 if (looking_at(buf, &i, "--->")) {\r
2397                     loggedOn = TRUE;\r
2398                     continue;\r
2399                 }\r
2400 \r
2401                 if (looking_at(buf, &i, "* shouts: ") ||\r
2402                     looking_at(buf, &i, "--> ")) {\r
2403                     if (appData.colorize) {\r
2404                         if (oldi > next_out) {\r
2405                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2406                             next_out = oldi;\r
2407                         }\r
2408                         Colorize(ColorShout, FALSE);\r
2409                         curColor = ColorShout;\r
2410                     }\r
2411                     loggedOn = TRUE;\r
2412                     started = STARTED_CHATTER;\r
2413                     continue;\r
2414                 }\r
2415 \r
2416                 if (looking_at( buf, &i, "Challenge:")) {\r
2417                     if (appData.colorize) {\r
2418                         if (oldi > next_out) {\r
2419                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2420                             next_out = oldi;\r
2421                         }\r
2422                         Colorize(ColorChallenge, FALSE);\r
2423                         curColor = ColorChallenge;\r
2424                     }\r
2425                     loggedOn = TRUE;\r
2426                     continue;\r
2427                 }\r
2428 \r
2429                 if (looking_at(buf, &i, "* offers you") ||\r
2430                     looking_at(buf, &i, "* offers to be") ||\r
2431                     looking_at(buf, &i, "* would like to") ||\r
2432                     looking_at(buf, &i, "* requests to") ||\r
2433                     looking_at(buf, &i, "Your opponent offers") ||\r
2434                     looking_at(buf, &i, "Your opponent requests")) {\r
2435 \r
2436                     if (appData.colorize) {\r
2437                         if (oldi > next_out) {\r
2438                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2439                             next_out = oldi;\r
2440                         }\r
2441                         Colorize(ColorRequest, FALSE);\r
2442                         curColor = ColorRequest;\r
2443                     }\r
2444                     continue;\r
2445                 }\r
2446 \r
2447                 if (looking_at(buf, &i, "* (*) seeking")) {\r
2448                     if (appData.colorize) {\r
2449                         if (oldi > next_out) {\r
2450                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2451                             next_out = oldi;\r
2452                         }\r
2453                         Colorize(ColorSeek, FALSE);\r
2454                         curColor = ColorSeek;\r
2455                     }\r
2456                     continue;\r
2457                 }\r
2458             }\r
2459 \r
2460             if (looking_at(buf, &i, "\\   ")) {\r
2461                 if (prevColor != ColorNormal) {\r
2462                     if (oldi > next_out) {\r
2463                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2464                         next_out = oldi;\r
2465                     }\r
2466                     Colorize(prevColor, TRUE);\r
2467                     curColor = prevColor;\r
2468                 }\r
2469                 if (savingComment) {\r
2470                     parse_pos = i - oldi;\r
2471                     memcpy(parse, &buf[oldi], parse_pos);\r
2472                     parse[parse_pos] = NULLCHAR;\r
2473                     started = STARTED_COMMENT;\r
2474                 } else {\r
2475                     started = STARTED_CHATTER;\r
2476                 }\r
2477                 continue;\r
2478             }\r
2479 \r
2480             if (looking_at(buf, &i, "Black Strength :") ||\r
2481                 looking_at(buf, &i, "<<< style 10 board >>>") ||\r
2482                 looking_at(buf, &i, "<10>") ||\r
2483                 looking_at(buf, &i, "#@#")) {\r
2484                 /* Wrong board style */\r
2485                 loggedOn = TRUE;\r
2486                 SendToICS(ics_prefix);\r
2487                 SendToICS("set style 12\n");\r
2488                 SendToICS(ics_prefix);\r
2489                 SendToICS("refresh\n");\r
2490                 continue;\r
2491             }\r
2492             \r
2493             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {\r
2494                 ICSInitScript();\r
2495                 have_sent_ICS_logon = 1;\r
2496                 continue;\r
2497             }\r
2498               \r
2499             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && \r
2500                 (looking_at(buf, &i, "\n<12> ") ||\r
2501                  looking_at(buf, &i, "<12> "))) {\r
2502                 loggedOn = TRUE;\r
2503                 if (oldi > next_out) {\r
2504                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2505                 }\r
2506                 next_out = i;\r
2507                 started = STARTED_BOARD;\r
2508                 parse_pos = 0;\r
2509                 continue;\r
2510             }\r
2511 \r
2512             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||\r
2513                 looking_at(buf, &i, "<b1> ")) {\r
2514                 if (oldi > next_out) {\r
2515                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2516                 }\r
2517                 next_out = i;\r
2518                 started = STARTED_HOLDINGS;\r
2519                 parse_pos = 0;\r
2520                 continue;\r
2521             }\r
2522 \r
2523             if (looking_at(buf, &i, "* *vs. * *--- *")) {\r
2524                 loggedOn = TRUE;\r
2525                 /* Header for a move list -- first line */\r
2526 \r
2527                 switch (ics_getting_history) {\r
2528                   case H_FALSE:\r
2529                     switch (gameMode) {\r
2530                       case IcsIdle:\r
2531                       case BeginningOfGame:\r
2532                         /* User typed "moves" or "oldmoves" while we\r
2533                            were idle.  Pretend we asked for these\r
2534                            moves and soak them up so user can step\r
2535                            through them and/or save them.\r
2536                            */\r
2537                         Reset(FALSE, TRUE);\r
2538                         gameMode = IcsObserving;\r
2539                         ModeHighlight();\r
2540                         ics_gamenum = -1;\r
2541                         ics_getting_history = H_GOT_UNREQ_HEADER;\r
2542                         break;\r
2543                       case EditGame: /*?*/\r
2544                       case EditPosition: /*?*/\r
2545                         /* Should above feature work in these modes too? */\r
2546                         /* For now it doesn't */\r
2547                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2548                         break;\r
2549                       default:\r
2550                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2551                         break;\r
2552                     }\r
2553                     break;\r
2554                   case H_REQUESTED:\r
2555                     /* Is this the right one? */\r
2556                     if (gameInfo.white && gameInfo.black &&\r
2557                         strcmp(gameInfo.white, star_match[0]) == 0 &&\r
2558                         strcmp(gameInfo.black, star_match[2]) == 0) {\r
2559                         /* All is well */\r
2560                         ics_getting_history = H_GOT_REQ_HEADER;\r
2561                     }\r
2562                     break;\r
2563                   case H_GOT_REQ_HEADER:\r
2564                   case H_GOT_UNREQ_HEADER:\r
2565                   case H_GOT_UNWANTED_HEADER:\r
2566                   case H_GETTING_MOVES:\r
2567                     /* Should not happen */\r
2568                     DisplayError("Error gathering move list: two headers", 0);\r
2569                     ics_getting_history = H_FALSE;\r
2570                     break;\r
2571                 }\r
2572 \r
2573                 /* Save player ratings into gameInfo if needed */\r
2574                 if ((ics_getting_history == H_GOT_REQ_HEADER ||\r
2575                      ics_getting_history == H_GOT_UNREQ_HEADER) &&\r
2576                     (gameInfo.whiteRating == -1 ||\r
2577                      gameInfo.blackRating == -1)) {\r
2578 \r
2579                     gameInfo.whiteRating = string_to_rating(star_match[1]);\r
2580                     gameInfo.blackRating = string_to_rating(star_match[3]);\r
2581                     if (appData.debugMode)\r
2582                       fprintf(debugFP, "Ratings from header: W %d, B %d\n", \r
2583                               gameInfo.whiteRating, gameInfo.blackRating);\r
2584                 }\r
2585                 continue;\r
2586             }\r
2587 \r
2588             if (looking_at(buf, &i,\r
2589               "* * match, initial time: * minute*, increment: * second")) {\r
2590                 /* Header for a move list -- second line */\r
2591                 /* Initial board will follow if this is a wild game */\r
2592                 if (gameInfo.event != NULL) free(gameInfo.event);\r
2593                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);\r
2594                 gameInfo.event = StrSave(str);\r
2595                 /* [HGM] we switched variant. Translate boards if needed. */\r
2596                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));\r
2597                 continue;\r
2598             }\r
2599 \r
2600             if (looking_at(buf, &i, "Move  ")) {\r
2601                 /* Beginning of a move list */\r
2602                 switch (ics_getting_history) {\r
2603                   case H_FALSE:\r
2604                     /* Normally should not happen */\r
2605                     /* Maybe user hit reset while we were parsing */\r
2606                     break;\r
2607                   case H_REQUESTED:\r
2608                     /* Happens if we are ignoring a move list that is not\r
2609                      * the one we just requested.  Common if the user\r
2610                      * tries to observe two games without turning off\r
2611                      * getMoveList */\r
2612                     break;\r
2613                   case H_GETTING_MOVES:\r
2614                     /* Should not happen */\r
2615                     DisplayError("Error gathering move list: nested", 0);\r
2616                     ics_getting_history = H_FALSE;\r
2617                     break;\r
2618                   case H_GOT_REQ_HEADER:\r
2619                     ics_getting_history = H_GETTING_MOVES;\r
2620                     started = STARTED_MOVES;\r
2621                     parse_pos = 0;\r
2622                     if (oldi > next_out) {\r
2623                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2624                     }\r
2625                     break;\r
2626                   case H_GOT_UNREQ_HEADER:\r
2627                     ics_getting_history = H_GETTING_MOVES;\r
2628                     started = STARTED_MOVES_NOHIDE;\r
2629                     parse_pos = 0;\r
2630                     break;\r
2631                   case H_GOT_UNWANTED_HEADER:\r
2632                     ics_getting_history = H_FALSE;\r
2633                     break;\r
2634                 }\r
2635                 continue;\r
2636             }                           \r
2637             \r
2638             if (looking_at(buf, &i, "% ") ||\r
2639                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)\r
2640                  && looking_at(buf, &i, "}*"))) {\r
2641                 savingComment = FALSE;\r
2642                 switch (started) {\r
2643                   case STARTED_MOVES:\r
2644                   case STARTED_MOVES_NOHIDE:\r
2645                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);\r
2646                     parse[parse_pos + i - oldi] = NULLCHAR;\r
2647                     ParseGameHistory(parse);\r
2648 #if ZIPPY\r
2649                     if (appData.zippyPlay && first.initDone) {\r
2650                         FeedMovesToProgram(&first, forwardMostMove);\r
2651                         if (gameMode == IcsPlayingWhite) {\r
2652                             if (WhiteOnMove(forwardMostMove)) {\r
2653                                 if (first.sendTime) {\r
2654                                   if (first.useColors) {\r
2655                                     SendToProgram("black\n", &first); \r
2656                                   }\r
2657                                   SendTimeRemaining(&first, TRUE);\r
2658                                 }\r
2659                                 if (first.useColors) {\r
2660                                   SendToProgram("white\ngo\n", &first);\r
2661                                 } else {\r
2662                                   SendToProgram("go\n", &first);\r
2663                                 }\r
2664                                 first.maybeThinking = TRUE;\r
2665                             } else {\r
2666                                 if (first.usePlayother) {\r
2667                                   if (first.sendTime) {\r
2668                                     SendTimeRemaining(&first, TRUE);\r
2669                                   }\r
2670                                   SendToProgram("playother\n", &first);\r
2671                                   firstMove = FALSE;\r
2672                                 } else {\r
2673                                   firstMove = TRUE;\r
2674                                 }\r
2675                             }\r
2676                         } else if (gameMode == IcsPlayingBlack) {\r
2677                             if (!WhiteOnMove(forwardMostMove)) {\r
2678                                 if (first.sendTime) {\r
2679                                   if (first.useColors) {\r
2680                                     SendToProgram("white\n", &first);\r
2681                                   }\r
2682                                   SendTimeRemaining(&first, FALSE);\r
2683                                 }\r
2684                                 if (first.useColors) {\r
2685                                   SendToProgram("black\ngo\n", &first);\r
2686                                 } else {\r
2687                                   SendToProgram("go\n", &first);\r
2688                                 }\r
2689                                 first.maybeThinking = TRUE;\r
2690                             } else {\r
2691                                 if (first.usePlayother) {\r
2692                                   if (first.sendTime) {\r
2693                                     SendTimeRemaining(&first, FALSE);\r
2694                                   }\r
2695                                   SendToProgram("playother\n", &first);\r
2696                                   firstMove = FALSE;\r
2697                                 } else {\r
2698                                   firstMove = TRUE;\r
2699                                 }\r
2700                             }\r
2701                         }                       \r
2702                     }\r
2703 #endif\r
2704                     if (gameMode == IcsObserving && ics_gamenum == -1) {\r
2705                         /* Moves came from oldmoves or moves command\r
2706                            while we weren't doing anything else.\r
2707                            */\r
2708                         currentMove = forwardMostMove;\r
2709                         ClearHighlights();/*!!could figure this out*/\r
2710                         flipView = appData.flipView;\r
2711                         DrawPosition(FALSE, boards[currentMove]);\r
2712                         DisplayBothClocks();\r
2713                         sprintf(str, "%s vs. %s",\r
2714                                 gameInfo.white, gameInfo.black);\r
2715                         DisplayTitle(str);\r
2716                         gameMode = IcsIdle;\r
2717                     } else {\r
2718                         /* Moves were history of an active game */\r
2719                         if (gameInfo.resultDetails != NULL) {\r
2720                             free(gameInfo.resultDetails);\r
2721                             gameInfo.resultDetails = NULL;\r
2722                         }\r
2723                     }\r
2724                     HistorySet(parseList, backwardMostMove,\r
2725                                forwardMostMove, currentMove-1);\r
2726                     DisplayMove(currentMove - 1);\r
2727                     if (started == STARTED_MOVES) next_out = i;\r
2728                     started = STARTED_NONE;\r
2729                     ics_getting_history = H_FALSE;\r
2730                     break;\r
2731 \r
2732                   case STARTED_OBSERVE:\r
2733                     started = STARTED_NONE;\r
2734                     SendToICS(ics_prefix);\r
2735                     SendToICS("refresh\n");\r
2736                     break;\r
2737 \r
2738                   default:\r
2739                     break;\r
2740                 }\r
2741                 continue;\r
2742             }\r
2743             \r
2744             if ((started == STARTED_MOVES || started == STARTED_BOARD ||\r
2745                  started == STARTED_HOLDINGS ||\r
2746                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {\r
2747                 /* Accumulate characters in move list or board */\r
2748                 parse[parse_pos++] = buf[i];\r
2749             }\r
2750             \r
2751             /* Start of game messages.  Mostly we detect start of game\r
2752                when the first board image arrives.  On some versions\r
2753                of the ICS, though, we need to do a "refresh" after starting\r
2754                to observe in order to get the current board right away. */\r
2755             if (looking_at(buf, &i, "Adding game * to observation list")) {\r
2756                 started = STARTED_OBSERVE;\r
2757                 continue;\r
2758             }\r
2759 \r
2760             /* Handle auto-observe */\r
2761             if (appData.autoObserve &&\r
2762                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&\r
2763                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {\r
2764                 char *player;\r
2765                 /* Choose the player that was highlighted, if any. */\r
2766                 if (star_match[0][0] == '\033' ||\r
2767                     star_match[1][0] != '\033') {\r
2768                     player = star_match[0];\r
2769                 } else {\r
2770                     player = star_match[2];\r
2771                 }\r
2772                 sprintf(str, "%sobserve %s\n",\r
2773                         ics_prefix, StripHighlightAndTitle(player));\r
2774                 SendToICS(str);\r
2775 \r
2776                 /* Save ratings from notify string */\r
2777                 strcpy(player1Name, star_match[0]);\r
2778                 player1Rating = string_to_rating(star_match[1]);\r
2779                 strcpy(player2Name, star_match[2]);\r
2780                 player2Rating = string_to_rating(star_match[3]);\r
2781 \r
2782                 if (appData.debugMode)\r
2783                   fprintf(debugFP, \r
2784                           "Ratings from 'Game notification:' %s %d, %s %d\n",\r
2785                           player1Name, player1Rating,\r
2786                           player2Name, player2Rating);\r
2787 \r
2788                 continue;\r
2789             }\r
2790 \r
2791             /* Deal with automatic examine mode after a game,\r
2792                and with IcsObserving -> IcsExamining transition */\r
2793             if (looking_at(buf, &i, "Entering examine mode for game *") ||\r
2794                 looking_at(buf, &i, "has made you an examiner of game *")) {\r
2795 \r
2796                 int gamenum = atoi(star_match[0]);\r
2797                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&\r
2798                     gamenum == ics_gamenum) {\r
2799                     /* We were already playing or observing this game;\r
2800                        no need to refetch history */\r
2801                     gameMode = IcsExamining;\r
2802                     if (pausing) {\r
2803                         pauseExamForwardMostMove = forwardMostMove;\r
2804                     } else if (currentMove < forwardMostMove) {\r
2805                         ForwardInner(forwardMostMove);\r
2806                     }\r
2807                 } else {\r
2808                     /* I don't think this case really can happen */\r
2809                     SendToICS(ics_prefix);\r
2810                     SendToICS("refresh\n");\r
2811                 }\r
2812                 continue;\r
2813             }    \r
2814             \r
2815             /* Error messages */\r
2816             if (ics_user_moved) {\r
2817                 if (looking_at(buf, &i, "Illegal move") ||\r
2818                     looking_at(buf, &i, "Not a legal move") ||\r
2819                     looking_at(buf, &i, "Your king is in check") ||\r
2820                     looking_at(buf, &i, "It isn't your turn") ||\r
2821                     looking_at(buf, &i, "It is not your move")) {\r
2822                     /* Illegal move */\r
2823                     ics_user_moved = 0;\r
2824                     if (forwardMostMove > backwardMostMove) {\r
2825                         currentMove = --forwardMostMove;\r
2826                         DisplayMove(currentMove - 1); /* before DMError */\r
2827                         DisplayMoveError("Illegal move (rejected by ICS)");\r
2828                         DrawPosition(FALSE, boards[currentMove]);\r
2829                         SwitchClocks();\r
2830                         DisplayBothClocks();\r
2831                     }\r
2832                     continue;\r
2833                 }\r
2834             }\r
2835 \r
2836             if (looking_at(buf, &i, "still have time") ||\r
2837                 looking_at(buf, &i, "not out of time") ||\r
2838                 looking_at(buf, &i, "either player is out of time") ||\r
2839                 looking_at(buf, &i, "has timeseal; checking")) {\r
2840                 /* We must have called his flag a little too soon */\r
2841                 whiteFlag = blackFlag = FALSE;\r
2842                 continue;\r
2843             }\r
2844 \r
2845             if (looking_at(buf, &i, "added * seconds to") ||\r
2846                 looking_at(buf, &i, "seconds were added to")) {\r
2847                 /* Update the clocks */\r
2848                 SendToICS(ics_prefix);\r
2849                 SendToICS("refresh\n");\r
2850                 continue;\r
2851             }\r
2852 \r
2853             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {\r
2854                 ics_clock_paused = TRUE;\r
2855                 StopClocks();\r
2856                 continue;\r
2857             }\r
2858 \r
2859             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {\r
2860                 ics_clock_paused = FALSE;\r
2861                 StartClocks();\r
2862                 continue;\r
2863             }\r
2864 \r
2865             /* Grab player ratings from the Creating: message.\r
2866                Note we have to check for the special case when\r
2867                the ICS inserts things like [white] or [black]. */\r
2868             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||\r
2869                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {\r
2870                 /* star_matches:\r
2871                    0    player 1 name (not necessarily white)\r
2872                    1    player 1 rating\r
2873                    2    empty, white, or black (IGNORED)\r
2874                    3    player 2 name (not necessarily black)\r
2875                    4    player 2 rating\r
2876                    \r
2877                    The names/ratings are sorted out when the game\r
2878                    actually starts (below).\r
2879                 */\r
2880                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));\r
2881                 player1Rating = string_to_rating(star_match[1]);\r
2882                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));\r
2883                 player2Rating = string_to_rating(star_match[4]);\r
2884 \r
2885                 if (appData.debugMode)\r
2886                   fprintf(debugFP, \r
2887                           "Ratings from 'Creating:' %s %d, %s %d\n",\r
2888                           player1Name, player1Rating,\r
2889                           player2Name, player2Rating);\r
2890 \r
2891                 continue;\r
2892             }\r
2893             \r
2894             /* Improved generic start/end-of-game messages */\r
2895             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||\r
2896                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){\r
2897                 /* If tkind == 0: */\r
2898                 /* star_match[0] is the game number */\r
2899                 /*           [1] is the white player's name */\r
2900                 /*           [2] is the black player's name */\r
2901                 /* For end-of-game: */\r
2902                 /*           [3] is the reason for the game end */\r
2903                 /*           [4] is a PGN end game-token, preceded by " " */\r
2904                 /* For start-of-game: */\r
2905                 /*           [3] begins with "Creating" or "Continuing" */\r
2906                 /*           [4] is " *" or empty (don't care). */\r
2907                 int gamenum = atoi(star_match[0]);\r
2908                 char *whitename, *blackname, *why, *endtoken;\r
2909                 ChessMove endtype = (ChessMove) 0;\r
2910 \r
2911                 if (tkind == 0) {\r
2912                   whitename = star_match[1];\r
2913                   blackname = star_match[2];\r
2914                   why = star_match[3];\r
2915                   endtoken = star_match[4];\r
2916                 } else {\r
2917                   whitename = star_match[1];\r
2918                   blackname = star_match[3];\r
2919                   why = star_match[5];\r
2920                   endtoken = star_match[6];\r
2921                 }\r
2922 \r
2923                 /* Game start messages */\r
2924                 if (strncmp(why, "Creating ", 9) == 0 ||\r
2925                     strncmp(why, "Continuing ", 11) == 0) {\r
2926                     gs_gamenum = gamenum;\r
2927                     strcpy(gs_kind, strchr(why, ' ') + 1);\r
2928 #if ZIPPY\r
2929                     if (appData.zippyPlay) {\r
2930                         ZippyGameStart(whitename, blackname);\r
2931                     }\r
2932 #endif /*ZIPPY*/\r
2933                     continue;\r
2934                 }\r
2935 \r
2936                 /* Game end messages */\r
2937                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||\r
2938                     ics_gamenum != gamenum) {\r
2939                     continue;\r
2940                 }\r
2941                 while (endtoken[0] == ' ') endtoken++;\r
2942                 switch (endtoken[0]) {\r
2943                   case '*':\r
2944                   default:\r
2945                     endtype = GameUnfinished;\r
2946                     break;\r
2947                   case '0':\r
2948                     endtype = BlackWins;\r
2949                     break;\r
2950                   case '1':\r
2951                     if (endtoken[1] == '/')\r
2952                       endtype = GameIsDrawn;\r
2953                     else\r
2954                       endtype = WhiteWins;\r
2955                     break;\r
2956                 }\r
2957                 GameEnds(endtype, why, GE_ICS);\r
2958 #if ZIPPY\r
2959                 if (appData.zippyPlay && first.initDone) {\r
2960                     ZippyGameEnd(endtype, why);\r
2961                     if (first.pr == NULL) {\r
2962                       /* Start the next process early so that we'll\r
2963                          be ready for the next challenge */\r
2964                       StartChessProgram(&first);\r
2965                     }\r
2966                     /* Send "new" early, in case this command takes\r
2967                        a long time to finish, so that we'll be ready\r
2968                        for the next challenge. */\r
2969                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'\r
2970                     Reset(TRUE, TRUE);\r
2971                 }\r
2972 #endif /*ZIPPY*/\r
2973                 continue;\r
2974             }\r
2975 \r
2976             if (looking_at(buf, &i, "Removing game * from observation") ||\r
2977                 looking_at(buf, &i, "no longer observing game *") ||\r
2978                 looking_at(buf, &i, "Game * (*) has no examiners")) {\r
2979                 if (gameMode == IcsObserving &&\r
2980                     atoi(star_match[0]) == ics_gamenum)\r
2981                   {\r
2982                       StopClocks();\r
2983                       gameMode = IcsIdle;\r
2984                       ics_gamenum = -1;\r
2985                       ics_user_moved = FALSE;\r
2986                   }\r
2987                 continue;\r
2988             }\r
2989 \r
2990             if (looking_at(buf, &i, "no longer examining game *")) {\r
2991                 if (gameMode == IcsExamining &&\r
2992                     atoi(star_match[0]) == ics_gamenum)\r
2993                   {\r
2994                       gameMode = IcsIdle;\r
2995                       ics_gamenum = -1;\r
2996                       ics_user_moved = FALSE;\r
2997                   }\r
2998                 continue;\r
2999             }\r
3000 \r
3001             /* Advance leftover_start past any newlines we find,\r
3002                so only partial lines can get reparsed */\r
3003             if (looking_at(buf, &i, "\n")) {\r
3004                 prevColor = curColor;\r
3005                 if (curColor != ColorNormal) {\r
3006                     if (oldi > next_out) {\r
3007                         SendToPlayer(&buf[next_out], oldi - next_out);\r
3008                         next_out = oldi;\r
3009                     }\r
3010                     Colorize(ColorNormal, FALSE);\r
3011                     curColor = ColorNormal;\r
3012                 }\r
3013                 if (started == STARTED_BOARD) {\r
3014                     started = STARTED_NONE;\r
3015                     parse[parse_pos] = NULLCHAR;\r
3016                     ParseBoard12(parse);\r
3017                     ics_user_moved = 0;\r
3018 \r
3019                     /* Send premove here */\r
3020                     if (appData.premove) {\r
3021                       char str[MSG_SIZ];\r
3022                       if (currentMove == 0 &&\r
3023                           gameMode == IcsPlayingWhite &&\r
3024                           appData.premoveWhite) {\r
3025                         sprintf(str, "%s%s\n", ics_prefix,\r
3026                                 appData.premoveWhiteText);\r
3027                         if (appData.debugMode)\r
3028                           fprintf(debugFP, "Sending premove:\n");\r
3029                         SendToICS(str);\r
3030                       } else if (currentMove == 1 &&\r
3031                                  gameMode == IcsPlayingBlack &&\r
3032                                  appData.premoveBlack) {\r
3033                         sprintf(str, "%s%s\n", ics_prefix,\r
3034                                 appData.premoveBlackText);\r
3035                         if (appData.debugMode)\r
3036                           fprintf(debugFP, "Sending premove:\n");\r
3037                         SendToICS(str);\r
3038                       } else if (gotPremove) {\r
3039                         gotPremove = 0;\r
3040                         ClearPremoveHighlights();\r
3041                         if (appData.debugMode)\r
3042                           fprintf(debugFP, "Sending premove:\n");\r
3043                           UserMoveEvent(premoveFromX, premoveFromY, \r
3044                                         premoveToX, premoveToY, \r
3045                                         premovePromoChar);\r
3046                       }\r
3047                     }\r
3048 \r
3049                     /* Usually suppress following prompt */\r
3050                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {\r
3051                         if (looking_at(buf, &i, "*% ")) {\r
3052                             savingComment = FALSE;\r
3053                         }\r
3054                     }\r
3055                     next_out = i;\r
3056                 } else if (started == STARTED_HOLDINGS) {\r
3057                     int gamenum;\r
3058                     char new_piece[MSG_SIZ];\r
3059                     started = STARTED_NONE;\r
3060                     parse[parse_pos] = NULLCHAR;\r
3061                     if (appData.debugMode)\r
3062                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",\r
3063                                                         parse, currentMove);\r
3064                     if (sscanf(parse, " game %d", &gamenum) == 1 &&\r
3065                         gamenum == ics_gamenum) {\r
3066                         if (gameInfo.variant == VariantNormal) {\r
3067                           /* [HGM] We seem to switch variant during a game!\r
3068                            * Presumably no holdings were displayed, so we have\r
3069                            * to move the position two files to the right to\r
3070                            * create room for them!\r
3071                            */\r
3072                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */\r
3073                           /* Get a move list just to see the header, which\r
3074                              will tell us whether this is really bug or zh */\r
3075                           if (ics_getting_history == H_FALSE) {\r
3076                             ics_getting_history = H_REQUESTED;\r
3077                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3078                             SendToICS(str);\r
3079                           }\r
3080                         }\r
3081                         new_piece[0] = NULLCHAR;\r
3082                         sscanf(parse, "game %d white [%s black [%s <- %s",\r
3083                                &gamenum, white_holding, black_holding,\r
3084                                new_piece);\r
3085                         white_holding[strlen(white_holding)-1] = NULLCHAR;\r
3086                         black_holding[strlen(black_holding)-1] = NULLCHAR;\r
3087                         /* [HGM] copy holdings to board holdings area */\r
3088                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);\r
3089                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);\r
3090 #if ZIPPY\r
3091                         if (appData.zippyPlay && first.initDone) {\r
3092                             ZippyHoldings(white_holding, black_holding,\r
3093                                           new_piece);\r
3094                         }\r
3095 #endif /*ZIPPY*/\r
3096                         if (tinyLayout || smallLayout) {\r
3097                             char wh[16], bh[16];\r
3098                             PackHolding(wh, white_holding);\r
3099                             PackHolding(bh, black_holding);\r
3100                             sprintf(str, "[%s-%s] %s-%s", wh, bh,\r
3101                                     gameInfo.white, gameInfo.black);\r
3102                         } else {\r
3103                             sprintf(str, "%s [%s] vs. %s [%s]",\r
3104                                     gameInfo.white, white_holding,\r
3105                                     gameInfo.black, black_holding);\r
3106                         }\r
3107 \r
3108                         DrawPosition(FALSE, boards[currentMove]);\r
3109                         DisplayTitle(str);\r
3110                     }\r
3111                     /* Suppress following prompt */\r
3112                     if (looking_at(buf, &i, "*% ")) {\r
3113                         savingComment = FALSE;\r
3114                     }\r
3115                     next_out = i;\r
3116                 }\r
3117                 continue;\r
3118             }\r
3119 \r
3120             i++;                /* skip unparsed character and loop back */\r
3121         }\r
3122         \r
3123         if (started != STARTED_MOVES && started != STARTED_BOARD &&\r
3124             started != STARTED_HOLDINGS && i > next_out) {\r
3125             SendToPlayer(&buf[next_out], i - next_out);\r
3126             next_out = i;\r
3127         }\r
3128         \r
3129         leftover_len = buf_len - leftover_start;\r
3130         /* if buffer ends with something we couldn't parse,\r
3131            reparse it after appending the next read */\r
3132         \r
3133     } else if (count == 0) {\r
3134         RemoveInputSource(isr);\r
3135         DisplayFatalError("Connection closed by ICS", 0, 0);\r
3136     } else {\r
3137         DisplayFatalError("Error reading from ICS", error, 1);\r
3138     }\r
3139 }\r
3140 \r
3141 \r
3142 /* Board style 12 looks like this:\r
3143    \r
3144    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0\r
3145    \r
3146  * The "<12> " is stripped before it gets to this routine.  The two\r
3147  * trailing 0's (flip state and clock ticking) are later addition, and\r
3148  * some chess servers may not have them, or may have only the first.\r
3149  * Additional trailing fields may be added in the future.  \r
3150  */\r
3151 \r
3152 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"\r
3153 \r
3154 #define RELATION_OBSERVING_PLAYED    0\r
3155 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */\r
3156 #define RELATION_PLAYING_MYMOVE      1\r
3157 #define RELATION_PLAYING_NOTMYMOVE  -1\r
3158 #define RELATION_EXAMINING           2\r
3159 #define RELATION_ISOLATED_BOARD     -3\r
3160 #define RELATION_STARTING_POSITION  -4   /* FICS only */\r
3161 \r
3162 void\r
3163 ParseBoard12(string)\r
3164      char *string;\r
3165\r
3166     GameMode newGameMode;\r
3167     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;\r
3168     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;\r
3169     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;\r
3170     char to_play, board_chars[200];\r
3171     char move_str[500], str[500], elapsed_time[500];\r
3172     char black[32], white[32];\r
3173     Board board;\r
3174     int prevMove = currentMove;\r
3175     int ticking = 2;\r
3176     ChessMove moveType;\r
3177     int fromX, fromY, toX, toY;\r
3178     char promoChar;\r
3179     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */\r
3180     char *bookHit = NULL; // [HGM] book\r
3181 \r
3182     fromX = fromY = toX = toY = -1;\r
3183     \r
3184     newGame = FALSE;\r
3185 \r
3186     if (appData.debugMode)\r
3187       fprintf(debugFP, "Parsing board: %s\n", string);\r
3188 \r
3189     move_str[0] = NULLCHAR;\r
3190     elapsed_time[0] = NULLCHAR;\r
3191     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */\r
3192         int  i = 0, j;\r
3193         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {\r
3194             if(string[i] == ' ') { ranks++; files = 0; }\r
3195             else files++;\r
3196             i++;\r
3197         }\r
3198         for(j = 0; j <i; j++) board_chars[j] = string[j];\r
3199         board_chars[i] = '\0';\r
3200         string += i + 1;\r
3201     }\r
3202     n = sscanf(string, PATTERN, &to_play, &double_push,\r
3203                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,\r
3204                &gamenum, white, black, &relation, &basetime, &increment,\r
3205                &white_stren, &black_stren, &white_time, &black_time,\r
3206                &moveNum, str, elapsed_time, move_str, &ics_flip,\r
3207                &ticking);\r
3208 \r
3209     if (n < 21) {\r
3210         sprintf(str, "Failed to parse board string:\n\"%s\"", string);\r
3211         DisplayError(str, 0);\r
3212         return;\r
3213     }\r
3214 \r
3215     /* Convert the move number to internal form */\r
3216     moveNum = (moveNum - 1) * 2;\r
3217     if (to_play == 'B') moveNum++;\r
3218     if (moveNum >= MAX_MOVES) {\r
3219       DisplayFatalError("Game too long; increase MAX_MOVES and recompile",\r
3220                         0, 1);\r
3221       return;\r
3222     }\r
3223     \r
3224     switch (relation) {\r
3225       case RELATION_OBSERVING_PLAYED:\r
3226       case RELATION_OBSERVING_STATIC:\r
3227         if (gamenum == -1) {\r
3228             /* Old ICC buglet */\r
3229             relation = RELATION_OBSERVING_STATIC;\r
3230         }\r
3231         newGameMode = IcsObserving;\r
3232         break;\r
3233       case RELATION_PLAYING_MYMOVE:\r
3234       case RELATION_PLAYING_NOTMYMOVE:\r
3235         newGameMode =\r
3236           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?\r
3237             IcsPlayingWhite : IcsPlayingBlack;\r
3238         break;\r
3239       case RELATION_EXAMINING:\r
3240         newGameMode = IcsExamining;\r
3241         break;\r
3242       case RELATION_ISOLATED_BOARD:\r
3243       default:\r
3244         /* Just display this board.  If user was doing something else,\r
3245            we will forget about it until the next board comes. */ \r
3246         newGameMode = IcsIdle;\r
3247         break;\r
3248       case RELATION_STARTING_POSITION:\r
3249         newGameMode = gameMode;\r
3250         break;\r
3251     }\r
3252     \r
3253     /* Modify behavior for initial board display on move listing\r
3254        of wild games.\r
3255        */\r
3256     switch (ics_getting_history) {\r
3257       case H_FALSE:\r
3258       case H_REQUESTED:\r
3259         break;\r
3260       case H_GOT_REQ_HEADER:\r
3261       case H_GOT_UNREQ_HEADER:\r
3262         /* This is the initial position of the current game */\r
3263         gamenum = ics_gamenum;\r
3264         moveNum = 0;            /* old ICS bug workaround */\r
3265         if (to_play == 'B') {\r
3266           startedFromSetupPosition = TRUE;\r
3267           blackPlaysFirst = TRUE;\r
3268           moveNum = 1;\r
3269           if (forwardMostMove == 0) forwardMostMove = 1;\r
3270           if (backwardMostMove == 0) backwardMostMove = 1;\r
3271           if (currentMove == 0) currentMove = 1;\r
3272         }\r
3273         newGameMode = gameMode;\r
3274         relation = RELATION_STARTING_POSITION; /* ICC needs this */\r
3275         break;\r
3276       case H_GOT_UNWANTED_HEADER:\r
3277         /* This is an initial board that we don't want */\r
3278         return;\r
3279       case H_GETTING_MOVES:\r
3280         /* Should not happen */\r
3281         DisplayError("Error gathering move list: extra board", 0);\r
3282         ics_getting_history = H_FALSE;\r
3283         return;\r
3284     }\r
3285     \r
3286     /* Take action if this is the first board of a new game, or of a\r
3287        different game than is currently being displayed.  */\r
3288     if (gamenum != ics_gamenum || newGameMode != gameMode ||\r
3289         relation == RELATION_ISOLATED_BOARD) {\r
3290         \r
3291         /* Forget the old game and get the history (if any) of the new one */\r
3292         if (gameMode != BeginningOfGame) {\r
3293           Reset(FALSE, TRUE);\r
3294         }\r
3295         newGame = TRUE;\r
3296         if (appData.autoRaiseBoard) BoardToTop();\r
3297         prevMove = -3;\r
3298         if (gamenum == -1) {\r
3299             newGameMode = IcsIdle;\r
3300         } else if (moveNum > 0 && newGameMode != IcsIdle &&\r
3301                    appData.getMoveList) {\r
3302             /* Need to get game history */\r
3303             ics_getting_history = H_REQUESTED;\r
3304             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3305             SendToICS(str);\r
3306         }\r
3307         \r
3308         /* Initially flip the board to have black on the bottom if playing\r
3309            black or if the ICS flip flag is set, but let the user change\r
3310            it with the Flip View button. */\r
3311         flipView = appData.autoFlipView ? \r
3312           (newGameMode == IcsPlayingBlack) || ics_flip :\r
3313           appData.flipView;\r
3314         \r
3315         /* Done with values from previous mode; copy in new ones */\r
3316         gameMode = newGameMode;\r
3317         ModeHighlight();\r
3318         ics_gamenum = gamenum;\r
3319         if (gamenum == gs_gamenum) {\r
3320             int klen = strlen(gs_kind);\r
3321             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;\r
3322             sprintf(str, "ICS %s", gs_kind);\r
3323             gameInfo.event = StrSave(str);\r
3324         } else {\r
3325             gameInfo.event = StrSave("ICS game");\r
3326         }\r
3327         gameInfo.site = StrSave(appData.icsHost);\r
3328         gameInfo.date = PGNDate();\r
3329         gameInfo.round = StrSave("-");\r
3330         gameInfo.white = StrSave(white);\r
3331         gameInfo.black = StrSave(black);\r
3332         timeControl = basetime * 60 * 1000;\r
3333         timeControl_2 = 0;\r
3334         timeIncrement = increment * 1000;\r
3335         movesPerSession = 0;\r
3336         gameInfo.timeControl = TimeControlTagValue();\r
3337         VariantSwitch(board, StringToVariant(gameInfo.event) );\r
3338   if (appData.debugMode) {\r
3339     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);\r
3340     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));\r
3341     setbuf(debugFP, NULL);\r
3342   }\r
3343 \r
3344         gameInfo.outOfBook = NULL;\r
3345         \r
3346         /* Do we have the ratings? */\r
3347         if (strcmp(player1Name, white) == 0 &&\r
3348             strcmp(player2Name, black) == 0) {\r
3349             if (appData.debugMode)\r
3350               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3351                       player1Rating, player2Rating);\r
3352             gameInfo.whiteRating = player1Rating;\r
3353             gameInfo.blackRating = player2Rating;\r
3354         } else if (strcmp(player2Name, white) == 0 &&\r
3355                    strcmp(player1Name, black) == 0) {\r
3356             if (appData.debugMode)\r
3357               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3358                       player2Rating, player1Rating);\r
3359             gameInfo.whiteRating = player2Rating;\r
3360             gameInfo.blackRating = player1Rating;\r
3361         }\r
3362         player1Name[0] = player2Name[0] = NULLCHAR;\r
3363 \r
3364         /* Silence shouts if requested */\r
3365         if (appData.quietPlay &&\r
3366             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {\r
3367             SendToICS(ics_prefix);\r
3368             SendToICS("set shout 0\n");\r
3369         }\r
3370     }\r
3371     \r
3372     /* Deal with midgame name changes */\r
3373     if (!newGame) {\r
3374         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {\r
3375             if (gameInfo.white) free(gameInfo.white);\r
3376             gameInfo.white = StrSave(white);\r
3377         }\r
3378         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {\r
3379             if (gameInfo.black) free(gameInfo.black);\r
3380             gameInfo.black = StrSave(black);\r
3381         }\r
3382     }\r
3383     \r
3384     /* Throw away game result if anything actually changes in examine mode */\r
3385     if (gameMode == IcsExamining && !newGame) {\r
3386         gameInfo.result = GameUnfinished;\r
3387         if (gameInfo.resultDetails != NULL) {\r
3388             free(gameInfo.resultDetails);\r
3389             gameInfo.resultDetails = NULL;\r
3390         }\r
3391     }\r
3392     \r
3393     /* In pausing && IcsExamining mode, we ignore boards coming\r
3394        in if they are in a different variation than we are. */\r
3395     if (pauseExamInvalid) return;\r
3396     if (pausing && gameMode == IcsExamining) {\r
3397         if (moveNum <= pauseExamForwardMostMove) {\r
3398             pauseExamInvalid = TRUE;\r
3399             forwardMostMove = pauseExamForwardMostMove;\r
3400             return;\r
3401         }\r
3402     }\r
3403     \r
3404   if (appData.debugMode) {\r
3405     fprintf(debugFP, "load %dx%d board\n", files, ranks);\r
3406   }\r
3407     /* Parse the board */\r
3408     for (k = 0; k < ranks; k++) {\r
3409       for (j = 0; j < files; j++)\r
3410         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);\r
3411       if(gameInfo.holdingsWidth > 1) {\r
3412            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;\r
3413            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;\r
3414       }\r
3415     }\r
3416     CopyBoard(boards[moveNum], board);\r
3417     if (moveNum == 0) {\r
3418         startedFromSetupPosition =\r
3419           !CompareBoards(board, initialPosition);\r
3420         if(startedFromSetupPosition)\r
3421             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */\r
3422     }\r
3423 \r
3424     /* [HGM] Set castling rights. Take the outermost Rooks,\r
3425        to make it also work for FRC opening positions. Note that board12\r
3426        is really defective for later FRC positions, as it has no way to\r
3427        indicate which Rook can castle if they are on the same side of King.\r
3428        For the initial position we grant rights to the outermost Rooks,\r
3429        and remember thos rights, and we then copy them on positions\r
3430        later in an FRC game. This means WB might not recognize castlings with\r
3431        Rooks that have moved back to their original position as illegal,\r
3432        but in ICS mode that is not its job anyway.\r
3433     */\r
3434     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)\r
3435     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;\r
3436 \r
3437         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)\r
3438             if(board[0][i] == WhiteRook) j = i;\r
3439         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3440         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)\r
3441             if(board[0][i] == WhiteRook) j = i;\r
3442         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3443         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)\r
3444             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;\r
3445         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3446         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)\r
3447             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;\r
3448         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3449 \r
3450         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }\r
3451         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)\r
3452             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;\r
3453         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)\r
3454             if(board[BOARD_HEIGHT-1][k] == bKing)\r
3455                 initialRights[5] = castlingRights[moveNum][5] = k;\r
3456     } else { int r;\r
3457         r = castlingRights[moveNum][0] = initialRights[0];\r
3458         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;\r
3459         r = castlingRights[moveNum][1] = initialRights[1];\r
3460         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;\r
3461         r = castlingRights[moveNum][3] = initialRights[3];\r
3462         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;\r
3463         r = castlingRights[moveNum][4] = initialRights[4];\r
3464         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;\r
3465         /* wildcastle kludge: always assume King has rights */\r
3466         r = castlingRights[moveNum][2] = initialRights[2];\r
3467         r = castlingRights[moveNum][5] = initialRights[5];\r
3468     }\r
3469     /* [HGM] e.p. rights. Assume that ICS sends file number here? */\r
3470     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;\r
3471 \r
3472     \r
3473     if (ics_getting_history == H_GOT_REQ_HEADER ||\r
3474         ics_getting_history == H_GOT_UNREQ_HEADER) {\r
3475         /* This was an initial position from a move list, not\r
3476            the current position */\r
3477         return;\r
3478     }\r
3479     \r
3480     /* Update currentMove and known move number limits */\r
3481     newMove = newGame || moveNum > forwardMostMove;\r
3482     if (newGame) {\r
3483         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3484         if (gameMode == IcsExamining && moveNum == 0) {\r
3485           /* Workaround for ICS limitation: we are not told the wild\r
3486              type when starting to examine a game.  But if we ask for\r
3487              the move list, the move list header will tell us */\r
3488             ics_getting_history = H_REQUESTED;\r
3489             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3490             SendToICS(str);\r
3491         }\r
3492     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove\r
3493                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {\r
3494         forwardMostMove = moveNum;\r
3495         if (!pausing || currentMove > forwardMostMove)\r
3496           currentMove = forwardMostMove;\r
3497     } else {\r
3498         /* New part of history that is not contiguous with old part */ \r
3499         if (pausing && gameMode == IcsExamining) {\r
3500             pauseExamInvalid = TRUE;\r
3501             forwardMostMove = pauseExamForwardMostMove;\r
3502             return;\r
3503         }\r
3504         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3505         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {\r
3506             ics_getting_history = H_REQUESTED;\r
3507             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3508             SendToICS(str);\r
3509         }\r
3510     }\r
3511     \r
3512     /* Update the clocks */\r
3513     if (strchr(elapsed_time, '.')) {\r
3514       /* Time is in ms */\r
3515       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;\r
3516       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;\r
3517     } else {\r
3518       /* Time is in seconds */\r
3519       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;\r
3520       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;\r
3521     }\r
3522       \r
3523 \r
3524 #if ZIPPY\r
3525     if (appData.zippyPlay && newGame &&\r
3526         gameMode != IcsObserving && gameMode != IcsIdle &&\r
3527         gameMode != IcsExamining)\r
3528       ZippyFirstBoard(moveNum, basetime, increment);\r
3529 #endif\r
3530     \r
3531     /* Put the move on the move list, first converting\r
3532        to canonical algebraic form. */\r
3533     if (moveNum > 0) {\r
3534   if (appData.debugMode) {\r
3535     if (appData.debugMode) { int f = forwardMostMove;\r
3536         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,\r
3537                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
3538     }\r
3539     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);\r
3540     fprintf(debugFP, "moveNum = %d\n", moveNum);\r
3541     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);\r
3542     setbuf(debugFP, NULL);\r
3543   }\r
3544         if (moveNum <= backwardMostMove) {\r
3545             /* We don't know what the board looked like before\r
3546                this move.  Punt. */\r
3547             strcpy(parseList[moveNum - 1], move_str);\r
3548             strcat(parseList[moveNum - 1], " ");\r
3549             strcat(parseList[moveNum - 1], elapsed_time);\r
3550             moveList[moveNum - 1][0] = NULLCHAR;\r
3551         } else if (ParseOneMove(move_str, moveNum - 1, &moveType,\r
3552                                 &fromX, &fromY, &toX, &toY, &promoChar)) {\r
3553             (void) CoordsToAlgebraic(boards[moveNum - 1],\r
3554                                      PosFlags(moveNum - 1), EP_UNKNOWN,\r
3555                                      fromY, fromX, toY, toX, promoChar,\r
3556                                      parseList[moveNum-1]);\r
3557             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,\r
3558                              castlingRights[moveNum]) ) {\r
3559               case MT_NONE:\r
3560               case MT_STALEMATE:\r
3561               default:\r
3562                 break;\r
3563               case MT_CHECK:\r
3564                 if(gameInfo.variant != VariantShogi)\r
3565                     strcat(parseList[moveNum - 1], "+");\r
3566                 break;\r
3567               case MT_CHECKMATE:\r
3568                 strcat(parseList[moveNum - 1], "#");\r
3569                 break;\r
3570             }\r
3571             strcat(parseList[moveNum - 1], " ");\r
3572             strcat(parseList[moveNum - 1], elapsed_time);\r
3573             /* currentMoveString is set as a side-effect of ParseOneMove */\r
3574             strcpy(moveList[moveNum - 1], currentMoveString);\r
3575             strcat(moveList[moveNum - 1], "\n");\r
3576         } else if (strcmp(move_str, "none") == 0) {\r
3577             /* Again, we don't know what the board looked like;\r
3578                this is really the start of the game. */\r
3579             parseList[moveNum - 1][0] = NULLCHAR;\r
3580             moveList[moveNum - 1][0] = NULLCHAR;\r
3581             backwardMostMove = moveNum;\r
3582             startedFromSetupPosition = TRUE;\r
3583             fromX = fromY = toX = toY = -1;\r
3584         } else {\r
3585             /* Move from ICS was illegal!?  Punt. */\r
3586   if (appData.debugMode) {\r
3587     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);\r
3588     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
3589   }\r
3590 #if 0\r
3591             if (appData.testLegality && appData.debugMode) {\r
3592                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);\r
3593                 DisplayError(str, 0);\r
3594             }\r
3595 #endif\r
3596             strcpy(parseList[moveNum - 1], move_str);\r
3597             strcat(parseList[moveNum - 1], " ");\r
3598             strcat(parseList[moveNum - 1], elapsed_time);\r
3599             moveList[moveNum - 1][0] = NULLCHAR;\r
3600             fromX = fromY = toX = toY = -1;\r
3601         }\r
3602   if (appData.debugMode) {\r
3603     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);\r
3604     setbuf(debugFP, NULL);\r
3605   }\r
3606 \r
3607 #if ZIPPY\r
3608         /* Send move to chess program (BEFORE animating it). */\r
3609         if (appData.zippyPlay && !newGame && newMove && \r
3610            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {\r
3611 \r
3612             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||\r
3613                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {\r
3614                 if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3615                     sprintf(str, "Couldn't parse move \"%s\" from ICS",\r
3616                             move_str);\r
3617                     DisplayError(str, 0);\r
3618                 } else {\r
3619                     if (first.sendTime) {\r
3620                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);\r
3621                     }\r
3622                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book\r
3623                     if (firstMove && !bookHit) {\r
3624                         firstMove = FALSE;\r
3625                         if (first.useColors) {\r
3626                           SendToProgram(gameMode == IcsPlayingWhite ?\r
3627                                         "white\ngo\n" :\r
3628                                         "black\ngo\n", &first);\r
3629                         } else {\r
3630                           SendToProgram("go\n", &first);\r
3631                         }\r
3632                         first.maybeThinking = TRUE;\r
3633                     }\r
3634                 }\r
3635             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {\r
3636               if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3637                 sprintf(str, "Couldn't parse move \"%s\" from ICS", move_str);\r
3638                 DisplayError(str, 0);\r
3639               } else {\r
3640                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!\r
3641                 SendMoveToProgram(moveNum - 1, &first);\r
3642               }\r
3643             }\r
3644         }\r
3645 #endif\r
3646     }\r
3647 \r
3648     if (moveNum > 0 && !gotPremove) {\r
3649         /* If move comes from a remote source, animate it.  If it\r
3650            isn't remote, it will have already been animated. */\r
3651         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {\r
3652             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);\r
3653         }\r
3654         if (!pausing && appData.highlightLastMove) {\r
3655             SetHighlights(fromX, fromY, toX, toY);\r
3656         }\r
3657     }\r
3658     \r
3659     /* Start the clocks */\r
3660     whiteFlag = blackFlag = FALSE;\r
3661     appData.clockMode = !(basetime == 0 && increment == 0);\r
3662     if (ticking == 0) {\r
3663       ics_clock_paused = TRUE;\r
3664       StopClocks();\r
3665     } else if (ticking == 1) {\r
3666       ics_clock_paused = FALSE;\r
3667     }\r
3668     if (gameMode == IcsIdle ||\r
3669         relation == RELATION_OBSERVING_STATIC ||\r
3670         relation == RELATION_EXAMINING ||\r
3671         ics_clock_paused)\r
3672       DisplayBothClocks();\r
3673     else\r
3674       StartClocks();\r
3675     \r
3676     /* Display opponents and material strengths */\r
3677     if (gameInfo.variant != VariantBughouse &&\r
3678         gameInfo.variant != VariantCrazyhouse) {\r
3679         if (tinyLayout || smallLayout) {\r
3680             if(gameInfo.variant == VariantNormal)\r
3681                 sprintf(str, "%s(%d) %s(%d) {%d %d}", \r
3682                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3683                     basetime, increment);\r
3684             else\r
3685                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", \r
3686                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3687                     basetime, increment, (int) gameInfo.variant);\r
3688         } else {\r
3689             if(gameInfo.variant == VariantNormal)\r
3690                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", \r
3691                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3692                     basetime, increment);\r
3693             else\r
3694                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", \r
3695                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3696                     basetime, increment, VariantName(gameInfo.variant));\r
3697         }\r
3698         DisplayTitle(str);\r
3699   if (appData.debugMode) {\r
3700     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);\r
3701   }\r
3702     }\r
3703 \r
3704    \r
3705     /* Display the board */\r
3706     if (!pausing) {\r
3707       \r
3708       if (appData.premove)\r
3709           if (!gotPremove || \r
3710              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||\r
3711              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))\r
3712               ClearPremoveHighlights();\r
3713 \r
3714       DrawPosition(FALSE, boards[currentMove]);\r
3715       DisplayMove(moveNum - 1);\r
3716       if (appData.ringBellAfterMoves && !ics_user_moved)\r
3717         RingBell();\r
3718     }\r
3719 \r
3720     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
3721 #if ZIPPY\r
3722     if(bookHit) { // [HGM] book: simulate book reply\r
3723         static char bookMove[MSG_SIZ]; // a bit generous?\r
3724 \r
3725         programStats.depth = programStats.nodes = programStats.time = \r
3726         programStats.score = programStats.got_only_move = 0;\r
3727         sprintf(programStats.movelist, "%s (xbook)", bookMove);\r
3728 \r
3729         strcpy(bookMove, "move ");\r
3730         strcat(bookMove, bookHit);\r
3731         HandleMachineMove(bookMove, &first);\r
3732     }\r
3733 #endif\r
3734 }\r
3735 \r
3736 void\r
3737 GetMoveListEvent()\r
3738 {\r
3739     char buf[MSG_SIZ];\r
3740     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {\r
3741         ics_getting_history = H_REQUESTED;\r
3742         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);\r
3743         SendToICS(buf);\r
3744     }\r
3745 }\r
3746 \r
3747 void\r
3748 AnalysisPeriodicEvent(force)\r
3749      int force;\r
3750 {\r
3751     if (((programStats.ok_to_send == 0 || programStats.line_is_book)\r
3752          && !force) || !appData.periodicUpdates)\r
3753       return;\r
3754 \r
3755     /* Send . command to Crafty to collect stats */\r
3756     SendToProgram(".\n", &first);\r
3757 \r
3758     /* Don't send another until we get a response (this makes\r
3759        us stop sending to old Crafty's which don't understand\r
3760        the "." command (sending illegal cmds resets node count & time,\r
3761        which looks bad)) */\r
3762     programStats.ok_to_send = 0;\r
3763 }\r
3764 \r
3765 void\r
3766 SendMoveToProgram(moveNum, cps)\r
3767      int moveNum;\r
3768      ChessProgramState *cps;\r
3769 {\r
3770     char buf[MSG_SIZ];\r
3771 \r
3772     if (cps->useUsermove) {\r
3773       SendToProgram("usermove ", cps);\r
3774     }\r
3775     if (cps->useSAN) {\r
3776       char *space;\r
3777       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {\r
3778         int len = space - parseList[moveNum];\r
3779         memcpy(buf, parseList[moveNum], len);\r
3780         buf[len++] = '\n';\r
3781         buf[len] = NULLCHAR;\r
3782       } else {\r
3783         sprintf(buf, "%s\n", parseList[moveNum]);\r
3784       }\r
3785       SendToProgram(buf, cps);\r
3786     } else {\r
3787       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */\r
3788         AlphaRank(moveList[moveNum], 4);\r
3789         SendToProgram(moveList[moveNum], cps);\r
3790         AlphaRank(moveList[moveNum], 4); // and back\r
3791       } else\r
3792       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by\r
3793        * the engine. It would be nice to have a better way to identify castle \r
3794        * moves here. */\r
3795       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)\r
3796                                                                          && cps->useOOCastle) {\r
3797         int fromX = moveList[moveNum][0] - AAA; \r
3798         int fromY = moveList[moveNum][1] - ONE;\r
3799         int toX = moveList[moveNum][2] - AAA; \r
3800         int toY = moveList[moveNum][3] - ONE;\r
3801         if((boards[moveNum][fromY][fromX] == WhiteKing \r
3802             && boards[moveNum][toY][toX] == WhiteRook)\r
3803            || (boards[moveNum][fromY][fromX] == BlackKing \r
3804                && boards[moveNum][toY][toX] == BlackRook)) {\r
3805           if(toX > fromX) SendToProgram("O-O\n", cps);\r
3806           else SendToProgram("O-O-O\n", cps);\r
3807         }\r
3808         else SendToProgram(moveList[moveNum], cps);\r
3809       }\r
3810       else SendToProgram(moveList[moveNum], cps);\r
3811       /* End of additions by Tord */\r
3812     }\r
3813 \r
3814     /* [HGM] setting up the opening has brought engine in force mode! */\r
3815     /*       Send 'go' if we are in a mode where machine should play. */\r
3816     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&\r
3817         (gameMode == TwoMachinesPlay   ||\r
3818 #ifdef ZIPPY\r
3819          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||\r
3820 #endif\r
3821          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {\r
3822         SendToProgram("go\n", cps);\r
3823   if (appData.debugMode) {\r
3824     fprintf(debugFP, "(extra)\n");\r
3825   }\r
3826     }\r
3827     setboardSpoiledMachineBlack = 0;\r
3828 }\r
3829 \r
3830 void\r
3831 SendMoveToICS(moveType, fromX, fromY, toX, toY)\r
3832      ChessMove moveType;\r
3833      int fromX, fromY, toX, toY;\r
3834 {\r
3835     char user_move[MSG_SIZ];\r
3836 \r
3837     switch (moveType) {\r
3838       default:\r
3839         sprintf(user_move, "say Internal error; bad moveType %d (%d,%d-%d,%d)",\r
3840                 (int)moveType, fromX, fromY, toX, toY);\r
3841         DisplayError(user_move + strlen("say "), 0);\r
3842         break;\r
3843       case WhiteKingSideCastle:\r
3844       case BlackKingSideCastle:\r
3845       case WhiteQueenSideCastleWild:\r
3846       case BlackQueenSideCastleWild:\r
3847       /* PUSH Fabien */\r
3848       case WhiteHSideCastleFR:\r
3849       case BlackHSideCastleFR:\r
3850       /* POP Fabien */\r
3851         sprintf(user_move, "o-o\n");\r
3852         break;\r
3853       case WhiteQueenSideCastle:\r
3854       case BlackQueenSideCastle:\r
3855       case WhiteKingSideCastleWild:\r
3856       case BlackKingSideCastleWild:\r
3857       /* PUSH Fabien */\r
3858       case WhiteASideCastleFR:\r
3859       case BlackASideCastleFR:\r
3860       /* POP Fabien */\r
3861         sprintf(user_move, "o-o-o\n");\r
3862         break;\r
3863       case WhitePromotionQueen:\r
3864       case BlackPromotionQueen:\r
3865       case WhitePromotionRook:\r
3866       case BlackPromotionRook:\r
3867       case WhitePromotionBishop:\r
3868       case BlackPromotionBishop:\r
3869       case WhitePromotionKnight:\r
3870       case BlackPromotionKnight:\r
3871       case WhitePromotionKing:\r
3872       case BlackPromotionKing:\r
3873       case WhitePromotionChancellor:\r
3874       case BlackPromotionChancellor:\r
3875       case WhitePromotionArchbishop:\r
3876       case BlackPromotionArchbishop:\r
3877         if(gameInfo.variant == VariantShatranj)\r
3878             sprintf(user_move, "%c%c%c%c=%c\n",\r
3879                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
3880                 PieceToChar(WhiteFerz));\r
3881         else\r
3882             sprintf(user_move, "%c%c%c%c=%c\n",\r
3883                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
3884                 PieceToChar(PromoPiece(moveType)));\r
3885         break;\r
3886       case WhiteDrop:\r
3887       case BlackDrop:\r
3888         sprintf(user_move, "%c@%c%c\n",\r
3889                 ToUpper(PieceToChar((ChessSquare) fromX)),\r
3890                 AAA + toX, ONE + toY);\r
3891         break;\r
3892       case NormalMove:\r
3893       case WhiteCapturesEnPassant:\r
3894       case BlackCapturesEnPassant:\r
3895       case IllegalMove:  /* could be a variant we don't quite understand */\r
3896         sprintf(user_move, "%c%c%c%c\n",\r
3897                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);\r
3898         break;\r
3899     }\r
3900     SendToICS(user_move);\r
3901 }\r
3902 \r
3903 void\r
3904 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)\r
3905      int rf, ff, rt, ft;\r
3906      char promoChar;\r
3907      char move[7];\r
3908 {\r
3909     if (rf == DROP_RANK) {\r
3910         sprintf(move, "%c@%c%c\n",\r
3911                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);\r
3912     } else {\r
3913         if (promoChar == 'x' || promoChar == NULLCHAR) {\r
3914             sprintf(move, "%c%c%c%c\n",\r
3915                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);\r
3916         } else {\r
3917             sprintf(move, "%c%c%c%c%c\n",\r
3918                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);\r
3919         }\r
3920     }\r
3921 }\r
3922 \r
3923 void\r
3924 ProcessICSInitScript(f)\r
3925      FILE *f;\r
3926 {\r
3927     char buf[MSG_SIZ];\r
3928 \r
3929     while (fgets(buf, MSG_SIZ, f)) {\r
3930         SendToICSDelayed(buf,(long)appData.msLoginDelay);\r
3931     }\r
3932 \r
3933     fclose(f);\r
3934 }\r
3935 \r
3936 \r
3937 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */\r
3938 void\r
3939 AlphaRank(char *move, int n)\r
3940 {\r
3941     char *p = move, c; int x, y;\r
3942 \r
3943     if (appData.debugMode) {\r
3944         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);\r
3945     }\r
3946 \r
3947     if(move[1]=='*' && \r
3948        move[2]>='0' && move[2]<='9' &&\r
3949        move[3]>='a' && move[3]<='x'    ) {\r
3950         move[1] = '@';\r
3951         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;\r
3952         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;\r
3953     } else\r
3954     if(move[0]>='0' && move[0]<='9' &&\r
3955        move[1]>='a' && move[1]<='x' &&\r
3956        move[2]>='0' && move[2]<='9' &&\r
3957        move[3]>='a' && move[3]<='x'    ) {\r
3958         /* input move, Shogi -> normal */\r
3959         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;\r
3960         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;\r
3961         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;\r
3962         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;\r
3963     } else\r
3964     if(move[1]=='@' &&\r
3965        move[3]>='0' && move[3]<='9' &&\r
3966        move[2]>='a' && move[2]<='x'    ) {\r
3967         move[1] = '*';\r
3968         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';\r
3969         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';\r
3970     } else\r
3971     if(\r
3972        move[0]>='a' && move[0]<='x' &&\r
3973        move[3]>='0' && move[3]<='9' &&\r
3974        move[2]>='a' && move[2]<='x'    ) {\r
3975          /* output move, normal -> Shogi */\r
3976         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';\r
3977         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';\r
3978         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';\r
3979         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';\r
3980         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';\r
3981     }\r
3982     if (appData.debugMode) {\r
3983         fprintf(debugFP, "   out = '%s'\n", move);\r
3984     }\r
3985 }\r
3986 \r
3987 /* Parser for moves from gnuchess, ICS, or user typein box */\r
3988 Boolean\r
3989 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)\r
3990      char *move;\r
3991      int moveNum;\r
3992      ChessMove *moveType;\r
3993      int *fromX, *fromY, *toX, *toY;\r
3994      char *promoChar;\r
3995 {       \r
3996     if (appData.debugMode) {\r
3997         fprintf(debugFP, "move to parse: %s\n", move);\r
3998     }\r
3999     *moveType = yylexstr(moveNum, move);\r
4000 \r
4001     switch (*moveType) {\r
4002       case WhitePromotionChancellor:\r
4003       case BlackPromotionChancellor:\r
4004       case WhitePromotionArchbishop:\r
4005       case BlackPromotionArchbishop:\r
4006       case WhitePromotionQueen:\r
4007       case BlackPromotionQueen:\r
4008       case WhitePromotionRook:\r
4009       case BlackPromotionRook:\r
4010       case WhitePromotionBishop:\r
4011       case BlackPromotionBishop:\r
4012       case WhitePromotionKnight:\r
4013       case BlackPromotionKnight:\r
4014       case WhitePromotionKing:\r
4015       case BlackPromotionKing:\r
4016       case NormalMove:\r
4017       case WhiteCapturesEnPassant:\r
4018       case BlackCapturesEnPassant:\r
4019       case WhiteKingSideCastle:\r
4020       case WhiteQueenSideCastle:\r
4021       case BlackKingSideCastle:\r
4022       case BlackQueenSideCastle:\r
4023       case WhiteKingSideCastleWild:\r
4024       case WhiteQueenSideCastleWild:\r
4025       case BlackKingSideCastleWild:\r
4026       case BlackQueenSideCastleWild:\r
4027       /* Code added by Tord: */\r
4028       case WhiteHSideCastleFR:\r
4029       case WhiteASideCastleFR:\r
4030       case BlackHSideCastleFR:\r
4031       case BlackASideCastleFR:\r
4032       /* End of code added by Tord */\r
4033       case IllegalMove:         /* bug or odd chess variant */\r
4034         *fromX = currentMoveString[0] - AAA;\r
4035         *fromY = currentMoveString[1] - ONE;\r
4036         *toX = currentMoveString[2] - AAA;\r
4037         *toY = currentMoveString[3] - ONE;\r
4038         *promoChar = currentMoveString[4];\r
4039         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||\r
4040             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {\r
4041     if (appData.debugMode) {\r
4042         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);\r
4043     }\r
4044             *fromX = *fromY = *toX = *toY = 0;\r
4045             return FALSE;\r
4046         }\r
4047         if (appData.testLegality) {\r
4048           return (*moveType != IllegalMove);\r
4049         } else {\r
4050           return !(fromX == fromY && toX == toY);\r
4051         }\r
4052 \r
4053       case WhiteDrop:\r
4054       case BlackDrop:\r
4055         *fromX = *moveType == WhiteDrop ?\r
4056           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
4057           (int) CharToPiece(ToLower(currentMoveString[0]));\r
4058         *fromY = DROP_RANK;\r
4059         *toX = currentMoveString[2] - AAA;\r
4060         *toY = currentMoveString[3] - ONE;\r
4061         *promoChar = NULLCHAR;\r
4062         return TRUE;\r
4063 \r
4064       case AmbiguousMove:\r
4065       case ImpossibleMove:\r
4066       case (ChessMove) 0:       /* end of file */\r
4067       case ElapsedTime:\r
4068       case Comment:\r
4069       case PGNTag:\r
4070       case NAG:\r
4071       case WhiteWins:\r
4072       case BlackWins:\r
4073       case GameIsDrawn:\r
4074       default:\r
4075     if (appData.debugMode) {\r
4076         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);\r
4077     }\r
4078         /* bug? */\r
4079         *fromX = *fromY = *toX = *toY = 0;\r
4080         *promoChar = NULLCHAR;\r
4081         return FALSE;\r
4082     }\r
4083 }\r
4084 \r
4085 /* [AS] FRC game initialization */\r
4086 static int FindEmptySquare( Board board, int n )\r
4087 {\r
4088     int i = 0;\r
4089 \r
4090     while( 1 ) {\r
4091         while( board[0][i] != EmptySquare ) i++;\r
4092         if( n == 0 )\r
4093             break;\r
4094         n--;\r
4095         i++;\r
4096     }\r
4097 \r
4098     return i;\r
4099 }\r
4100 \r
4101 #if 0\r
4102 static void ShuffleFRC( Board board )\r
4103 {\r
4104     int i;\r
4105 \r
4106     srand( time(0) );\r
4107     \r
4108     for( i=0; i<8; i++ ) {\r
4109         board[0][i] = EmptySquare;\r
4110     }\r
4111 \r
4112     board[0][(rand() % 4)*2  ] = WhiteBishop; /* On dark square */\r
4113     board[0][(rand() % 4)*2+1] = WhiteBishop; /* On lite square */\r
4114     board[0][FindEmptySquare(board, rand() % 6)] = WhiteQueen;\r
4115     board[0][FindEmptySquare(board, rand() % 5)] = WhiteKnight;\r
4116     board[0][FindEmptySquare(board, rand() % 4)] = WhiteKnight;\r
4117     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4118     initialRights[1]  = initialRights[4]  =\r
4119     castlingRights[0][1] = castlingRights[0][4] = i;\r
4120     board[0][ i=FindEmptySquare(board, 0) ] = WhiteKing;\r
4121     initialRights[2]  = initialRights[5]  =\r
4122     castlingRights[0][2] = castlingRights[0][5] = i;\r
4123     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4124     initialRights[0]  = initialRights[3]  =\r
4125     castlingRights[0][0] = castlingRights[0][3] = i;\r
4126 \r
4127     for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {\r
4128         board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;\r
4129     }\r
4130 }\r
4131 \r
4132 static unsigned char FRC_KnightTable[10] = {\r
4133     0x00, 0x01, 0x02, 0x03, 0x11, 0x12, 0x13, 0x22, 0x23, 0x33\r
4134 };\r
4135 \r
4136 static void SetupFRC( Board board, int pos_index )\r
4137 {\r
4138     int i;\r
4139     unsigned char knights;\r
4140 \r
4141     /* Bring the position index into a safe range (just in case...) */\r
4142     if( pos_index < 0 ) pos_index = 0;\r
4143 \r
4144     pos_index %= 960;\r
4145 \r
4146     /* Clear the board */\r
4147     for( i=0; i<8; i++ ) {\r
4148         board[0][i] = EmptySquare;\r
4149     }\r
4150 \r
4151     /* Place bishops and queen */\r
4152     board[0][ (pos_index % 4)*2 + 1 ] = WhiteBishop; /* On lite square */\r
4153     pos_index /= 4;\r
4154     \r
4155     board[0][ (pos_index % 4)*2     ] = WhiteBishop; /* On dark square */\r
4156     pos_index /= 4;\r
4157 \r
4158     board[0][ FindEmptySquare(board, pos_index % 6) ] = WhiteQueen;\r
4159     pos_index /= 6;\r
4160 \r
4161     /* Place knigths */\r
4162     knights = FRC_KnightTable[ pos_index ];\r
4163 \r
4164     board[0][ FindEmptySquare(board, knights / 16) ] = WhiteKnight;\r
4165     board[0][ FindEmptySquare(board, knights % 16) ] = WhiteKnight;\r
4166 \r
4167     /* Place rooks and king */\r
4168     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4169     initialRights[1]  = initialRights[4]  =\r
4170     castlingRights[0][1] = castlingRights[0][4] = i;\r
4171     board[0][ i=FindEmptySquare(board, 0) ] = WhiteKing;\r
4172     initialRights[2]  = initialRights[5]  =\r
4173     castlingRights[0][2] = castlingRights[0][5] = i;\r
4174     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4175     initialRights[0]  = initialRights[3]  =\r
4176     castlingRights[0][0] = castlingRights[0][3] = i;\r
4177 \r
4178     /* Mirror piece placement for black */\r
4179     for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {\r
4180         board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;\r
4181     }\r
4182 }\r
4183 #else\r
4184 // [HGM] shuffle: a more general way to suffle opening setups, applicable to arbitrry variants.\r
4185 // All positions will have equal probability, but the current method will not provide a unique\r
4186 // numbering scheme for arrays that contain 3 or more pieces of the same kind.\r
4187 #define DARK 1\r
4188 #define LITE 2\r
4189 #define ANY 3\r
4190 \r
4191 int squaresLeft[4];\r
4192 int piecesLeft[(int)BlackPawn];\r
4193 long long int seed, nrOfShuffles;\r
4194 \r
4195 void GetPositionNumber()\r
4196 {       // sets global variable seed\r
4197         int i;\r
4198 \r
4199         seed = appData.defaultFrcPosition;\r
4200         if(seed < 0) { // randomize based on time for negative FRC position numbers\r
4201                 srandom(time(0)); \r
4202                 for(i=0; i<50; i++) seed += random();\r
4203                 seed = random() ^ random() >> 8 ^ random() << 8;\r
4204                 if(seed<0) seed = -seed;\r
4205         }\r
4206 }\r
4207 \r
4208 int put(Board board, int pieceType, int rank, int n, int shade)\r
4209 // put the piece on the (n-1)-th empty squares of the given shade\r
4210 {\r
4211         int i;\r
4212 \r
4213         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {\r
4214                 if( ((i-BOARD_LEFT)&1)+1 & shade && board[rank][i] == EmptySquare && n-- == 0) {\r
4215                         board[rank][i] = (ChessSquare) pieceType;\r
4216                         squaresLeft[(i-BOARD_LEFT&1) + 1]--;\r
4217                         squaresLeft[ANY]--;\r
4218                         piecesLeft[pieceType]--; \r
4219                         return i;\r
4220                 }\r
4221         }\r
4222         return -1;\r
4223 }\r
4224 \r
4225 \r
4226 void AddOnePiece(Board board, int pieceType, int rank, int shade)\r
4227 // calculate where the next piece goes, (any empty square), and put it there\r
4228 {\r
4229         int i;\r
4230 \r
4231         i = seed % squaresLeft[shade];\r
4232         nrOfShuffles *= squaresLeft[shade];\r
4233         seed /= squaresLeft[shade];\r
4234         put(board, pieceType, rank, i, shade);\r
4235 }\r
4236 \r
4237 void AddTwoPieces(Board board, int pieceType, int rank)\r
4238 // calculate where the next 2 identical pieces go, (any empty square), and put it there\r
4239 {\r
4240         int i, n=squaresLeft[ANY], j=n-1, k;\r
4241 \r
4242         k = n*(n-1)/2; // nr of possibilities, not counting permutations\r
4243         i = seed % k;  // pick one\r
4244         nrOfShuffles *= k;\r
4245         seed /= k;\r
4246         while(i >= j) i -= j--;\r
4247         j = n - 1 - j; i += j;\r
4248         put(board, pieceType, rank, j, ANY);\r
4249         put(board, pieceType, rank, i, ANY);\r
4250 }\r
4251 \r
4252 void SetUpShuffle(Board board, int number)\r
4253 {\r
4254         int i, p, first=1;\r
4255 \r
4256         GetPositionNumber(); nrOfShuffles = 1;\r
4257 \r
4258         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;\r
4259         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;\r
4260         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];\r
4261 \r
4262         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;\r
4263 \r
4264         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board\r
4265             p = (int) board[0][i];\r
4266             if(p < (int) BlackPawn) piecesLeft[p] ++;\r
4267             board[0][i] = EmptySquare;\r
4268         }\r
4269 \r
4270         if(PosFlags(0) & F_ALL_CASTLE_OK) {\r
4271             // shuffles restricted to allow normal castling put KRR first\r
4272             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle\r
4273                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);\r
4274             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles\r
4275                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);\r
4276             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling\r
4277                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);\r
4278             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling\r
4279                 put(board, WhiteRook, 0, 0, ANY);\r
4280             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle\r
4281         }\r
4282 \r
4283         if((BOARD_RGHT-BOARD_LEFT & 1) == 0)\r
4284             // only for even boards make effort to put pairs of colorbound pieces on opposite colors\r
4285             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {\r
4286                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;\r
4287                 while(piecesLeft[p] >= 2) {\r
4288                     AddOnePiece(board, p, 0, LITE);\r
4289                     AddOnePiece(board, p, 0, DARK);\r
4290                 }\r
4291                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)\r
4292             }\r
4293 \r
4294         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {\r
4295             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere\r
4296             // but we leave King and Rooks for last, to possibly obey FRC restriction\r
4297             if(p == (int)WhiteRook) continue;\r
4298             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations\r
4299             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece\r
4300         }\r
4301 \r
4302         // now everything is placed, except perhaps King (Unicorn) and Rooks\r
4303 \r
4304         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {\r
4305             // Last King gets castling rights\r
4306             while(piecesLeft[(int)WhiteUnicorn]) {\r
4307                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);\r
4308                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;\r
4309             }\r
4310 \r
4311             while(piecesLeft[(int)WhiteKing]) {\r
4312                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);\r
4313                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;\r
4314             }\r
4315 \r
4316 \r
4317         } else {\r
4318             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);\r
4319             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);\r
4320         }\r
4321 \r
4322         // Only Rooks can be left; simply place them all\r
4323         while(piecesLeft[(int)WhiteRook]) {\r
4324                 i = put(board, WhiteRook, 0, 0, ANY);\r
4325                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights\r
4326                         if(first) {\r
4327                                 first=0;\r
4328                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;\r
4329                         }\r
4330                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;\r
4331                 }\r
4332         }\r
4333         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white\r
4334             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;\r
4335         }\r
4336 \r
4337         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize\r
4338 }\r
4339 \r
4340 #endif\r
4341 \r
4342 int SetCharTable( char *table, const char * map )\r
4343 /* [HGM] moved here from winboard.c because of its general usefulness */\r
4344 /*       Basically a safe strcpy that uses the last character as King */\r
4345 {\r
4346     int result = FALSE; int NrPieces;\r
4347 \r
4348     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare \r
4349                     && NrPieces >= 12 && !(NrPieces&1)) {\r
4350         int i; /* [HGM] Accept even length from 12 to 34 */\r
4351 \r
4352         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';\r
4353         for( i=0; i<NrPieces/2-1; i++ ) {\r
4354             table[i] = map[i];\r
4355             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];\r
4356         }\r
4357         table[(int) WhiteKing]  = map[NrPieces/2-1];\r
4358         table[(int) BlackKing]  = map[NrPieces-1];\r
4359 \r
4360         result = TRUE;\r
4361     }\r
4362 \r
4363     return result;\r
4364 }\r
4365 \r
4366 void Prelude(Board board)\r
4367 {       // [HGM] superchess: random selection of exo-pieces\r
4368         int i, j, k; ChessSquare p; \r
4369         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };\r
4370 \r
4371         GetPositionNumber(); // use FRC position number\r
4372 \r
4373         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table\r
4374             SetCharTable(pieceToChar, appData.pieceToCharTable);\r
4375             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) \r
4376                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;\r
4377         }\r
4378 \r
4379         j = seed%4;                 seed /= 4; \r
4380         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);\r
4381         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4382         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4383         j = seed%3 + (seed%3 >= j); seed /= 3; \r
4384         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);\r
4385         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4386         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4387         j = seed%3;                 seed /= 3; \r
4388         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);\r
4389         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4390         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4391         j = seed%2 + (seed%2 >= j); seed /= 2; \r
4392         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);\r
4393         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4394         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4395         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);\r
4396         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);\r
4397         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);\r
4398         put(board, exoPieces[0],    0, 0, ANY);\r
4399         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];\r
4400 }\r
4401 \r
4402 void\r
4403 InitPosition(redraw)\r
4404      int redraw;\r
4405 {\r
4406     ChessSquare (* pieces)[BOARD_SIZE];\r
4407     int i, j, pawnRow, overrule,\r
4408     oldx = gameInfo.boardWidth,\r
4409     oldy = gameInfo.boardHeight,\r
4410     oldh = gameInfo.holdingsWidth,\r
4411     oldv = gameInfo.variant;\r
4412 \r
4413     currentMove = forwardMostMove = backwardMostMove = 0;\r
4414     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request\r
4415 \r
4416     /* [AS] Initialize pv info list [HGM] and game status */\r
4417     {\r
4418         for( i=0; i<MAX_MOVES; i++ ) {\r
4419             pvInfoList[i].depth = 0;\r
4420             epStatus[i]=EP_NONE;\r
4421             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
4422         }\r
4423 \r
4424         initialRulePlies = 0; /* 50-move counter start */\r
4425 \r
4426         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;\r
4427         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;\r
4428     }\r
4429 \r
4430     \r
4431     /* [HGM] logic here is completely changed. In stead of full positions */\r
4432     /* the initialized data only consist of the two backranks. The switch */\r
4433     /* selects which one we will use, which is than copied to the Board   */\r
4434     /* initialPosition, which for the rest is initialized by Pawns and    */\r
4435     /* empty squares. This initial position is then copied to boards[0],  */\r
4436     /* possibly after shuffling, so that it remains available.            */\r
4437 \r
4438     gameInfo.holdingsWidth = 0; /* default board sizes */\r
4439     gameInfo.boardWidth    = 8;\r
4440     gameInfo.boardHeight   = 8;\r
4441     gameInfo.holdingsSize  = 0;\r
4442     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */\r
4443     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */\r
4444     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); \r
4445 \r
4446     switch (gameInfo.variant) {\r
4447     case VariantFischeRandom:\r
4448       shuffleOpenings = TRUE;\r
4449     default:\r
4450       pieces = FIDEArray;\r
4451       break;\r
4452     case VariantShatranj:\r
4453       pieces = ShatranjArray;\r
4454       nrCastlingRights = 0;\r
4455       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); \r
4456       break;\r
4457     case VariantTwoKings:\r
4458       pieces = twoKingsArray;\r
4459       nrCastlingRights = 8;                 /* add rights for second King */\r
4460       castlingRights[0][6] = initialRights[2] = 5;\r
4461       castlingRights[0][7] = initialRights[5] = 5;\r
4462       castlingRank[6] = 0;\r
4463       castlingRank[7] = BOARD_HEIGHT-1;\r
4464       break;\r
4465     case VariantCapaRandom:\r
4466       shuffleOpenings = TRUE;\r
4467     case VariantCapablanca:\r
4468       pieces = CapablancaArray;\r
4469       gameInfo.boardWidth = 10;\r
4470       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
4471       break;\r
4472     case VariantGothic:\r
4473       pieces = GothicArray;\r
4474       gameInfo.boardWidth = 10;\r
4475       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
4476       break;\r
4477     case VariantJanus:\r
4478       pieces = JanusArray;\r
4479       gameInfo.boardWidth = 10;\r
4480       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); \r
4481       nrCastlingRights = 6;\r
4482         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
4483         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
4484         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH-1>>1;\r
4485         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
4486         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4487         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH-1>>1;\r
4488       break;\r
4489     case VariantFalcon:\r
4490       pieces = FalconArray;\r
4491       gameInfo.boardWidth = 10;\r
4492       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); \r
4493       break;\r
4494     case VariantXiangqi:\r
4495       pieces = XiangqiArray;\r
4496       gameInfo.boardWidth  = 9;\r
4497       gameInfo.boardHeight = 10;\r
4498       nrCastlingRights = 0;\r
4499       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); \r
4500       break;\r
4501     case VariantShogi:\r
4502       pieces = ShogiArray;\r
4503       gameInfo.boardWidth  = 9;\r
4504       gameInfo.boardHeight = 9;\r
4505       gameInfo.holdingsSize = 7;\r
4506       nrCastlingRights = 0;\r
4507       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); \r
4508       break;\r
4509     case VariantCourier:\r
4510       pieces = CourierArray;\r
4511       gameInfo.boardWidth  = 12;\r
4512       nrCastlingRights = 0;\r
4513       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); \r
4514       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
4515       break;\r
4516     case VariantKnightmate:\r
4517       pieces = KnightmateArray;\r
4518       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); \r
4519       break;\r
4520     case VariantFairy:\r
4521       pieces = fairyArray;\r
4522       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); \r
4523       break;\r
4524     case VariantSuper:\r
4525       pieces = FIDEArray;\r
4526       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");\r
4527       gameInfo.holdingsSize = 8;\r
4528       startedFromSetupPosition = TRUE;\r
4529       break;\r
4530     case VariantCrazyhouse:\r
4531     case VariantBughouse:\r
4532       pieces = FIDEArray;\r
4533       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); \r
4534       gameInfo.holdingsSize = 5;\r
4535       break;\r
4536     case VariantWildCastle:\r
4537       pieces = FIDEArray;\r
4538       /* !!?shuffle with kings guaranteed to be on d or e file */\r
4539       shuffleOpenings = 1;\r
4540       break;\r
4541     case VariantNoCastle:\r
4542       pieces = FIDEArray;\r
4543       nrCastlingRights = 0;\r
4544       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
4545       /* !!?unconstrained back-rank shuffle */\r
4546       shuffleOpenings = 1;\r
4547       break;\r
4548     }\r
4549 \r
4550     overrule = 0;\r
4551     if(appData.NrFiles >= 0) {\r
4552         if(gameInfo.boardWidth != appData.NrFiles) overrule++;\r
4553         gameInfo.boardWidth = appData.NrFiles;\r
4554     }\r
4555     if(appData.NrRanks >= 0) {\r
4556         gameInfo.boardHeight = appData.NrRanks;\r
4557     }\r
4558     if(appData.holdingsSize >= 0) {\r
4559         i = appData.holdingsSize;\r
4560         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;\r
4561         gameInfo.holdingsSize = i;\r
4562     }\r
4563     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;\r
4564     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)\r
4565         DisplayFatalError("Recompile to support this BOARD_SIZE!", 0, 2);\r
4566 \r
4567     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */\r
4568     if(pawnRow < 1) pawnRow = 1;\r
4569 \r
4570     /* User pieceToChar list overrules defaults */\r
4571     if(appData.pieceToCharTable != NULL)\r
4572         SetCharTable(pieceToChar, appData.pieceToCharTable);\r
4573 \r
4574     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;\r
4575 \r
4576         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)\r
4577             s = (ChessSquare) 0; /* account holding counts in guard band */\r
4578         for( i=0; i<BOARD_HEIGHT; i++ )\r
4579             initialPosition[i][j] = s;\r
4580 \r
4581         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;\r
4582         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];\r
4583         initialPosition[pawnRow][j] = WhitePawn;\r
4584         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;\r
4585         if(gameInfo.variant == VariantXiangqi) {\r
4586             if(j&1) {\r
4587                 initialPosition[pawnRow][j] = \r
4588                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;\r
4589                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {\r
4590                    initialPosition[2][j] = WhiteCannon;\r
4591                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;\r
4592                 }\r
4593             }\r
4594         }\r
4595         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];\r
4596     }\r
4597     if( (gameInfo.variant == VariantShogi) && !overrule ) {\r
4598 \r
4599             j=BOARD_LEFT+1;\r
4600             initialPosition[1][j] = WhiteBishop;\r
4601             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;\r
4602             j=BOARD_RGHT-2;\r
4603             initialPosition[1][j] = WhiteRook;\r
4604             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;\r
4605     }\r
4606 \r
4607     if( nrCastlingRights == -1) {\r
4608         /* [HGM] Build normal castling rights (must be done after board sizing!) */\r
4609         /*       This sets default castling rights from none to normal corners   */\r
4610         /* Variants with other castling rights must set them themselves above    */\r
4611         nrCastlingRights = 6;\r
4612        \r
4613         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
4614         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
4615         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;\r
4616         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
4617         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4618         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;\r
4619      }\r
4620 \r
4621      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);\r
4622 #if 0\r
4623     if(gameInfo.variant == VariantFischeRandom) {\r
4624       if( appData.defaultFrcPosition < 0 ) {\r
4625         ShuffleFRC( initialPosition );\r
4626       }\r
4627       else {\r
4628         SetupFRC( initialPosition, appData.defaultFrcPosition );\r
4629       }\r
4630       startedFromSetupPosition = TRUE;\r
4631     } else \r
4632 #else\r
4633   if (appData.debugMode) {\r
4634     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);\r
4635   }\r
4636     if(shuffleOpenings) {\r
4637         SetUpShuffle(initialPosition, appData.defaultFrcPosition);\r
4638         startedFromSetupPosition = TRUE;\r
4639     }\r
4640 #endif\r
4641     if(startedFromPositionFile) {\r
4642       /* [HGM] loadPos: use PositionFile for every new game */\r
4643       CopyBoard(initialPosition, filePosition);\r
4644       for(i=0; i<nrCastlingRights; i++)\r
4645           castlingRights[0][i] = initialRights[i] = fileRights[i];\r
4646       startedFromSetupPosition = TRUE;\r
4647     }\r
4648 \r
4649     CopyBoard(boards[0], initialPosition);\r
4650 \r
4651     if(oldx != gameInfo.boardWidth ||\r
4652        oldy != gameInfo.boardHeight ||\r
4653        oldh != gameInfo.holdingsWidth\r
4654 #ifdef GOTHIC\r
4655        || oldv == VariantGothic ||        // For licensing popups\r
4656        gameInfo.variant == VariantGothic\r
4657 #endif\r
4658 #ifdef FALCON\r
4659        || oldv == VariantFalcon ||\r
4660        gameInfo.variant == VariantFalcon\r
4661 #endif\r
4662                                          )\r
4663             InitDrawingSizes(-2 ,0);\r
4664 \r
4665     if (redraw)\r
4666       DrawPosition(TRUE, boards[currentMove]);\r
4667 }\r
4668 \r
4669 void\r
4670 SendBoard(cps, moveNum)\r
4671      ChessProgramState *cps;\r
4672      int moveNum;\r
4673 {\r
4674     char message[MSG_SIZ];\r
4675     \r
4676     if (cps->useSetboard) {\r
4677       char* fen = PositionToFEN(moveNum, cps->useFEN960);\r
4678       sprintf(message, "setboard %s\n", fen);\r
4679       SendToProgram(message, cps);\r
4680       free(fen);\r
4681 \r
4682     } else {\r
4683       ChessSquare *bp;\r
4684       int i, j;\r
4685       /* Kludge to set black to move, avoiding the troublesome and now\r
4686        * deprecated "black" command.\r
4687        */\r
4688       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);\r
4689 \r
4690       SendToProgram("edit\n", cps);\r
4691       SendToProgram("#\n", cps);\r
4692       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4693         bp = &boards[moveNum][i][BOARD_LEFT];\r
4694         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4695           if ((int) *bp < (int) BlackPawn) {\r
4696             sprintf(message, "%c%c%c\n", PieceToChar(*bp), \r
4697                     AAA + j, ONE + i);\r
4698             if(message[0] == '+' || message[0] == '~') {\r
4699                 sprintf(message, "%c%c%c+\n",\r
4700                         PieceToChar((ChessSquare)(DEMOTED *bp)),\r
4701                         AAA + j, ONE + i);\r
4702             }\r
4703             if(cps->alphaRank) { /* [HGM] shogi: translate coords */\r
4704                 message[1] = BOARD_RGHT   - 1 - j + '1';\r
4705                 message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
4706             }\r
4707             SendToProgram(message, cps);\r
4708           }\r
4709         }\r
4710       }\r
4711     \r
4712       SendToProgram("c\n", cps);\r
4713       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4714         bp = &boards[moveNum][i][BOARD_LEFT];\r
4715         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4716           if (((int) *bp != (int) EmptySquare)\r
4717               && ((int) *bp >= (int) BlackPawn)) {\r
4718             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),\r
4719                     AAA + j, ONE + i);\r
4720             if(message[0] == '+' || message[0] == '~') {\r
4721                 sprintf(message, "%c%c%c+\n",\r
4722                         PieceToChar((ChessSquare)(DEMOTED *bp)),\r
4723                         AAA + j, ONE + i);\r
4724             }\r
4725             if(cps->alphaRank) { /* [HGM] shogi: translate coords */\r
4726                 message[1] = BOARD_RGHT   - 1 - j + '1';\r
4727                 message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
4728             }\r
4729             SendToProgram(message, cps);\r
4730           }\r
4731         }\r
4732       }\r
4733     \r
4734       SendToProgram(".\n", cps);\r
4735     }\r
4736     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */\r
4737 }\r
4738 \r
4739 int\r
4740 IsPromotion(fromX, fromY, toX, toY)\r
4741      int fromX, fromY, toX, toY;\r
4742 {\r
4743     /* [HGM] add Shogi promotions */\r
4744     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;\r
4745     ChessSquare piece;\r
4746 \r
4747     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||\r
4748       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;\r
4749    /* [HGM] Note to self: line above also weeds out drops */\r
4750     piece = boards[currentMove][fromY][fromX];\r
4751     if(gameInfo.variant == VariantShogi) {\r
4752         promotionZoneSize = 3;\r
4753         highestPromotingPiece = (int)WhiteKing;\r
4754         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,\r
4755            and if in normal chess we then allow promotion to King, why not\r
4756            allow promotion of other piece in Shogi?                         */\r
4757     }\r
4758     if((int)piece >= BlackPawn) {\r
4759         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)\r
4760              return FALSE;\r
4761         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;\r
4762     } else {\r
4763         if(  toY < BOARD_HEIGHT - promotionZoneSize &&\r
4764            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;\r
4765     }\r
4766     return ( (int)piece <= highestPromotingPiece );\r
4767 }\r
4768 \r
4769 int\r
4770 InPalace(row, column)\r
4771      int row, column;\r
4772 {   /* [HGM] for Xiangqi */\r
4773     if( (row < 3 || row > BOARD_HEIGHT-4) &&\r
4774          column < (BOARD_WIDTH + 4)/2 &&\r
4775          column > (BOARD_WIDTH - 5)/2 ) return TRUE;\r
4776     return FALSE;\r
4777 }\r
4778 \r
4779 int\r
4780 PieceForSquare (x, y)\r
4781      int x;\r
4782      int y;\r
4783 {\r
4784   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)\r
4785      return -1;\r
4786   else\r
4787      return boards[currentMove][y][x];\r
4788 }\r
4789 \r
4790 int\r
4791 OKToStartUserMove(x, y)\r
4792      int x, y;\r
4793 {\r
4794     ChessSquare from_piece;\r
4795     int white_piece;\r
4796 \r
4797     if (matchMode) return FALSE;\r
4798     if (gameMode == EditPosition) return TRUE;\r
4799 \r
4800     if (x >= 0 && y >= 0)\r
4801       from_piece = boards[currentMove][y][x];\r
4802     else\r
4803       from_piece = EmptySquare;\r
4804 \r
4805     if (from_piece == EmptySquare) return FALSE;\r
4806 \r
4807     white_piece = (int)from_piece >= (int)WhitePawn &&\r
4808       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */\r
4809 \r
4810     switch (gameMode) {\r
4811       case PlayFromGameFile:\r
4812       case AnalyzeFile:\r
4813       case TwoMachinesPlay:\r
4814       case EndOfGame:\r
4815         return FALSE;\r
4816 \r
4817       case IcsObserving:\r
4818       case IcsIdle:\r
4819         return FALSE;\r
4820 \r
4821       case MachinePlaysWhite:\r
4822       case IcsPlayingBlack:\r
4823         if (appData.zippyPlay) return FALSE;\r
4824         if (white_piece) {\r
4825             DisplayMoveError("You are playing Black");\r
4826             return FALSE;\r
4827         }\r
4828         break;\r
4829 \r
4830       case MachinePlaysBlack:\r
4831       case IcsPlayingWhite:\r
4832         if (appData.zippyPlay) return FALSE;\r
4833         if (!white_piece) {\r
4834             DisplayMoveError("You are playing White");\r
4835             return FALSE;\r
4836         }\r
4837         break;\r
4838 \r
4839       case EditGame:\r
4840         if (!white_piece && WhiteOnMove(currentMove)) {\r
4841             DisplayMoveError("It is White's turn");\r
4842             return FALSE;\r
4843         }           \r
4844         if (white_piece && !WhiteOnMove(currentMove)) {\r
4845             DisplayMoveError("It is Black's turn");\r
4846             return FALSE;\r
4847         }           \r
4848         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {\r
4849             /* Editing correspondence game history */\r
4850             /* Could disallow this or prompt for confirmation */\r
4851             cmailOldMove = -1;\r
4852         }\r
4853         if (currentMove < forwardMostMove) {\r
4854             /* Discarding moves */\r
4855             /* Could prompt for confirmation here,\r
4856                but I don't think that's such a good idea */\r
4857             forwardMostMove = currentMove;\r
4858         }\r
4859         break;\r
4860 \r
4861       case BeginningOfGame:\r
4862         if (appData.icsActive) return FALSE;\r
4863         if (!appData.noChessProgram) {\r
4864             if (!white_piece) {\r
4865                 DisplayMoveError("You are playing White");\r
4866                 return FALSE;\r
4867             }\r
4868         }\r
4869         break;\r
4870         \r
4871       case Training:\r
4872         if (!white_piece && WhiteOnMove(currentMove)) {\r
4873             DisplayMoveError("It is White's turn");\r
4874             return FALSE;\r
4875         }           \r
4876         if (white_piece && !WhiteOnMove(currentMove)) {\r
4877             DisplayMoveError("It is Black's turn");\r
4878             return FALSE;\r
4879         }           \r
4880         break;\r
4881 \r
4882       default:\r
4883       case IcsExamining:\r
4884         break;\r
4885     }\r
4886     if (currentMove != forwardMostMove && gameMode != AnalyzeMode\r
4887         && gameMode != AnalyzeFile && gameMode != Training) {\r
4888         DisplayMoveError("Displayed position is not current");\r
4889         return FALSE;\r
4890     }\r
4891     return TRUE;\r
4892 }\r
4893 \r
4894 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;\r
4895 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;\r
4896 int lastLoadGameUseList = FALSE;\r
4897 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];\r
4898 ChessMove lastLoadGameStart = (ChessMove) 0;\r
4899 \r
4900 \r
4901 ChessMove\r
4902 UserMoveTest(fromX, fromY, toX, toY, promoChar)\r
4903      int fromX, fromY, toX, toY;\r
4904      int promoChar;\r
4905 {\r
4906     ChessMove moveType;\r
4907     ChessSquare pdown, pup;\r
4908 \r
4909     if (fromX < 0 || fromY < 0) return ImpossibleMove;\r
4910     if ((fromX == toX) && (fromY == toY)) {\r
4911         return ImpossibleMove;\r
4912     }\r
4913 \r
4914     /* [HGM] suppress all moves into holdings area and guard band */\r
4915     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )\r
4916             return ImpossibleMove;\r
4917 \r
4918     /* [HGM] <sameColor> moved to here from winboard.c */\r
4919     /* note: this code seems to exist for filtering out some obviously illegal premoves */\r
4920     pdown = boards[currentMove][fromY][fromX];\r
4921     pup = boards[currentMove][toY][toX];\r
4922     if (    gameMode != EditPosition &&\r
4923             (WhitePawn <= pdown && pdown < BlackPawn &&\r
4924              WhitePawn <= pup && pup < BlackPawn  ||\r
4925              BlackPawn <= pdown && pdown < EmptySquare &&\r
4926              BlackPawn <= pup && pup < EmptySquare \r
4927             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&\r
4928                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||\r
4929                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) \r
4930         )           )\r
4931          return ImpossibleMove;\r
4932 \r
4933     /* Check if the user is playing in turn.  This is complicated because we\r
4934        let the user "pick up" a piece before it is his turn.  So the piece he\r
4935        tried to pick up may have been captured by the time he puts it down!\r
4936        Therefore we use the color the user is supposed to be playing in this\r
4937        test, not the color of the piece that is currently on the starting\r
4938        square---except in EditGame mode, where the user is playing both\r
4939        sides; fortunately there the capture race can't happen.  (It can\r
4940        now happen in IcsExamining mode, but that's just too bad.  The user\r
4941        will get a somewhat confusing message in that case.)\r
4942        */\r
4943 \r
4944     switch (gameMode) {\r
4945       case PlayFromGameFile:\r
4946       case AnalyzeFile:\r
4947       case TwoMachinesPlay:\r
4948       case EndOfGame:\r
4949       case IcsObserving:\r
4950       case IcsIdle:\r
4951         /* We switched into a game mode where moves are not accepted,\r
4952            perhaps while the mouse button was down. */\r
4953         return ImpossibleMove;\r
4954 \r
4955       case MachinePlaysWhite:\r
4956         /* User is moving for Black */\r
4957         if (WhiteOnMove(currentMove)) {\r
4958             DisplayMoveError("It is White's turn");\r
4959             return ImpossibleMove;\r
4960         }\r
4961         break;\r
4962 \r
4963       case MachinePlaysBlack:\r
4964         /* User is moving for White */\r
4965         if (!WhiteOnMove(currentMove)) {\r
4966             DisplayMoveError("It is Black's turn");\r
4967             return ImpossibleMove;\r
4968         }\r
4969         break;\r
4970 \r
4971       case EditGame:\r
4972       case IcsExamining:\r
4973       case BeginningOfGame:\r
4974       case AnalyzeMode:\r
4975       case Training:\r
4976         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&\r
4977             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {\r
4978             /* User is moving for Black */\r
4979             if (WhiteOnMove(currentMove)) {\r
4980                 DisplayMoveError("It is White's turn");\r
4981                 return ImpossibleMove;\r
4982             }\r
4983         } else {\r
4984             /* User is moving for White */\r
4985             if (!WhiteOnMove(currentMove)) {\r
4986                 DisplayMoveError("It is Black's turn");\r
4987                 return ImpossibleMove;\r
4988             }\r
4989         }\r
4990         break;\r
4991 \r
4992       case IcsPlayingBlack:\r
4993         /* User is moving for Black */\r
4994         if (WhiteOnMove(currentMove)) {\r
4995             if (!appData.premove) {\r
4996                 DisplayMoveError("It is White's turn");\r
4997             } else if (toX >= 0 && toY >= 0) {\r
4998                 premoveToX = toX;\r
4999                 premoveToY = toY;\r
5000                 premoveFromX = fromX;\r
5001                 premoveFromY = fromY;\r
5002                 premovePromoChar = promoChar;\r
5003                 gotPremove = 1;\r
5004                 if (appData.debugMode) \r
5005                     fprintf(debugFP, "Got premove: fromX %d,"\r
5006                             "fromY %d, toX %d, toY %d\n",\r
5007                             fromX, fromY, toX, toY);\r
5008             }\r
5009             return ImpossibleMove;\r
5010         }\r
5011         break;\r
5012 \r
5013       case IcsPlayingWhite:\r
5014         /* User is moving for White */\r
5015         if (!WhiteOnMove(currentMove)) {\r
5016             if (!appData.premove) {\r
5017                 DisplayMoveError("It is Black's turn");\r
5018             } else if (toX >= 0 && toY >= 0) {\r
5019                 premoveToX = toX;\r
5020                 premoveToY = toY;\r
5021                 premoveFromX = fromX;\r
5022                 premoveFromY = fromY;\r
5023                 premovePromoChar = promoChar;\r
5024                 gotPremove = 1;\r
5025                 if (appData.debugMode) \r
5026                     fprintf(debugFP, "Got premove: fromX %d,"\r
5027                             "fromY %d, toX %d, toY %d\n",\r
5028                             fromX, fromY, toX, toY);\r
5029             }\r
5030             return ImpossibleMove;\r
5031         }\r
5032         break;\r
5033 \r
5034       default:\r
5035         break;\r
5036 \r
5037       case EditPosition:\r
5038         /* EditPosition, empty square, or different color piece;\r
5039            click-click move is possible */\r
5040         if (toX == -2 || toY == -2) {\r
5041             boards[0][fromY][fromX] = EmptySquare;\r
5042             return AmbiguousMove;\r
5043         } else if (toX >= 0 && toY >= 0) {\r
5044             boards[0][toY][toX] = boards[0][fromY][fromX];\r
5045             boards[0][fromY][fromX] = EmptySquare;\r
5046             return AmbiguousMove;\r
5047         }\r
5048         return ImpossibleMove;\r
5049     }\r
5050 \r
5051     /* [HGM] If move started in holdings, it means a drop */\r
5052     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { \r
5053          if( pup != EmptySquare ) return ImpossibleMove;\r
5054          if(appData.testLegality) {\r
5055              /* it would be more logical if LegalityTest() also figured out\r
5056               * which drops are legal. For now we forbid pawns on back rank.\r
5057               * Shogi is on its own here...\r
5058               */\r
5059              if( (pdown == WhitePawn || pdown == BlackPawn) &&\r
5060                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )\r
5061                  return(ImpossibleMove); /* no pawn drops on 1st/8th */\r
5062          }\r
5063          return WhiteDrop; /* Not needed to specify white or black yet */\r
5064     }\r
5065 \r
5066     userOfferedDraw = FALSE;\r
5067         \r
5068     /* [HGM] always test for legality, to get promotion info */\r
5069     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),\r
5070                           epStatus[currentMove], castlingRights[currentMove],\r
5071                                          fromY, fromX, toY, toX, promoChar);\r
5072 \r
5073     /* [HGM] but possibly ignore an IllegalMove result */\r
5074     if (appData.testLegality) {\r
5075         if (moveType == IllegalMove || moveType == ImpossibleMove) {\r
5076             DisplayMoveError("Illegal move");\r
5077             return ImpossibleMove;\r
5078         }\r
5079     }\r
5080 \r
5081     return moveType;\r
5082     /* [HGM] <popupFix> in stead of calling FinishMove directly, this\r
5083        function is made into one that returns an OK move type if FinishMove\r
5084        should be called. This to give the calling driver routine the\r
5085        opportunity to finish the userMove input with a promotion popup,\r
5086        without bothering the user with this for invalid or illegal moves */\r
5087 \r
5088 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */\r
5089 }\r
5090 \r
5091 /* Common tail of UserMoveEvent and DropMenuEvent */\r
5092 int\r
5093 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)\r
5094      ChessMove moveType;\r
5095      int fromX, fromY, toX, toY;\r
5096      /*char*/int promoChar;\r
5097 {\r
5098     char *bookHit = 0;\r
5099 \r
5100     if(gameInfo.variant == VariantSuper && promoChar != NULLCHAR) { \r
5101         // [HGM] superchess: suppress promotions to non-available piece\r
5102         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));\r
5103         if(WhiteOnMove(currentMove)) {\r
5104             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;\r
5105         } else {\r
5106             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;\r
5107         }\r
5108     }\r
5109 \r
5110     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion\r
5111        move type in caller when we know the move is a legal promotion */\r
5112     if(moveType == NormalMove && promoChar)\r
5113         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);\r
5114 \r
5115     /* [HGM] convert drag-and-drop piece drops to standard form */\r
5116     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {\r
5117          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
5118          fromX = boards[currentMove][fromY][fromX];\r
5119          fromY = DROP_RANK;\r
5120     }\r
5121 \r
5122     /* [HGM] <popupFix> The following if has been moved here from\r
5123        UserMoveEvent(). Because it seemed to belon here (why not allow\r
5124        piece drops in training games?), and because it can only be\r
5125        performed after it is known to what we promote. */\r
5126     if (gameMode == Training) {\r
5127       /* compare the move played on the board to the next move in the\r
5128        * game. If they match, display the move and the opponent's response. \r
5129        * If they don't match, display an error message.\r
5130        */\r
5131       int saveAnimate;\r
5132       Board testBoard;\r
5133       CopyBoard(testBoard, boards[currentMove]);\r
5134       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);\r
5135 \r
5136       if (CompareBoards(testBoard, boards[currentMove+1])) {\r
5137         ForwardInner(currentMove+1);\r
5138 \r
5139         /* Autoplay the opponent's response.\r
5140          * if appData.animate was TRUE when Training mode was entered,\r
5141          * the response will be animated.\r
5142          */\r
5143         saveAnimate = appData.animate;\r
5144         appData.animate = animateTraining;\r
5145         ForwardInner(currentMove+1);\r
5146         appData.animate = saveAnimate;\r
5147 \r
5148         /* check for the end of the game */\r
5149         if (currentMove >= forwardMostMove) {\r
5150           gameMode = PlayFromGameFile;\r
5151           ModeHighlight();\r
5152           SetTrainingModeOff();\r
5153           DisplayInformation("End of game");\r
5154         }\r
5155       } else {\r
5156         DisplayError("Incorrect move", 0);\r
5157       }\r
5158       return 1;\r
5159     }\r
5160 \r
5161   /* Ok, now we know that the move is good, so we can kill\r
5162      the previous line in Analysis Mode */\r
5163   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {\r
5164     forwardMostMove = currentMove;\r
5165   }\r
5166 \r
5167   /* If we need the chess program but it's dead, restart it */\r
5168   ResurrectChessProgram();\r
5169 \r
5170   /* A user move restarts a paused game*/\r
5171   if (pausing)\r
5172     PauseEvent();\r
5173 \r
5174   thinkOutput[0] = NULLCHAR;\r
5175 \r
5176   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/\r
5177 \r
5178     if(gameInfo.variant == VariantSuper && promoChar != NULLCHAR && gameInfo.holdingsSize) { \r
5179         // [HGM] superchess: take promotion piece out of holdings\r
5180         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));\r
5181         if(WhiteOnMove(forwardMostMove-1)) {\r
5182             if(!--boards[forwardMostMove][k][BOARD_WIDTH-2])\r
5183                 boards[forwardMostMove][k][BOARD_WIDTH-1] = EmptySquare;\r
5184         } else {\r
5185             if(!--boards[forwardMostMove][BOARD_HEIGHT-1-k][1])\r
5186                 boards[forwardMostMove][BOARD_HEIGHT-1-k][0] = EmptySquare;\r
5187         }\r
5188     }\r
5189 \r
5190   if (gameMode == BeginningOfGame) {\r
5191     if (appData.noChessProgram) {\r
5192       gameMode = EditGame;\r
5193       SetGameInfo();\r
5194     } else {\r
5195       char buf[MSG_SIZ];\r
5196       gameMode = MachinePlaysBlack;\r
5197       StartClocks();\r
5198       SetGameInfo();\r
5199       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
5200       DisplayTitle(buf);\r
5201       if (first.sendName) {\r
5202         sprintf(buf, "name %s\n", gameInfo.white);\r
5203         SendToProgram(buf, &first);\r
5204       }\r
5205       StartClocks();\r
5206     }\r
5207     ModeHighlight();\r
5208   }\r
5209 \r
5210   /* Relay move to ICS or chess engine */\r
5211   if (appData.icsActive) {\r
5212     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
5213         gameMode == IcsExamining) {\r
5214       SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
5215       ics_user_moved = 1;\r
5216     }\r
5217   } else {\r
5218     if (first.sendTime && (gameMode == BeginningOfGame ||\r
5219                            gameMode == MachinePlaysWhite ||\r
5220                            gameMode == MachinePlaysBlack)) {\r
5221       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);\r
5222     }\r
5223     if (gameMode != EditGame && gameMode != PlayFromGameFile) {\r
5224          // [HGM] book: if program might be playing, let it use book\r
5225         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);\r
5226         first.maybeThinking = TRUE;\r
5227     } else SendMoveToProgram(forwardMostMove-1, &first);\r
5228     if (currentMove == cmailOldMove + 1) {\r
5229       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
5230     }\r
5231   }\r
5232 \r
5233   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5234 \r
5235   switch (gameMode) {\r
5236   case EditGame:\r
5237     switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
5238                      EP_UNKNOWN, castlingRights[currentMove]) ) {\r
5239     case MT_NONE:\r
5240     case MT_CHECK:\r
5241       break;\r
5242     case MT_CHECKMATE:\r
5243       if (WhiteOnMove(currentMove)) {\r
5244         GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
5245       } else {\r
5246         GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
5247       }\r
5248       break;\r
5249     case MT_STALEMATE:\r
5250       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
5251       break;\r
5252     }\r
5253     break;\r
5254     \r
5255   case MachinePlaysBlack:\r
5256   case MachinePlaysWhite:\r
5257     /* disable certain menu options while machine is thinking */\r
5258     SetMachineThinkingEnables();\r
5259     break;\r
5260 \r
5261   default:\r
5262     break;\r
5263   }\r
5264 \r
5265   if(bookHit) { // [HGM] book: simulate book reply\r
5266         static char bookMove[MSG_SIZ]; // a bit generous?\r
5267 \r
5268         programStats.depth = programStats.nodes = programStats.time = \r
5269         programStats.score = programStats.got_only_move = 0;\r
5270         sprintf(programStats.movelist, "%s (xbook)", bookMove);\r
5271 \r
5272         strcpy(bookMove, "move ");\r
5273         strcat(bookMove, bookHit);\r
5274         HandleMachineMove(bookMove, &first);\r
5275   }\r
5276   return 1;\r
5277 }\r
5278 \r
5279 void\r
5280 UserMoveEvent(fromX, fromY, toX, toY, promoChar)\r
5281      int fromX, fromY, toX, toY;\r
5282      int promoChar;\r
5283 {\r
5284     /* [HGM] This routine was added to allow calling of its two logical\r
5285        parts from other modules in the old way. Before, UserMoveEvent()\r
5286        automatically called FinishMove() if the move was OK, and returned\r
5287        otherwise. I separated the two, in order to make it possible to\r
5288        slip a promotion popup in between. But that it always needs two\r
5289        calls, to the first part, (now called UserMoveTest() ), and to\r
5290        FinishMove if the first part succeeded. Calls that do not need\r
5291        to do anything in between, can call this routine the old way. \r
5292     */\r
5293     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);\r
5294 \r
5295     if(moveType != ImpossibleMove)\r
5296         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);\r
5297 }\r
5298 \r
5299 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )\r
5300 {\r
5301     char * hint = lastHint;\r
5302     FrontEndProgramStats stats;\r
5303 \r
5304     stats.which = cps == &first ? 0 : 1;\r
5305     stats.depth = cpstats->depth;\r
5306     stats.nodes = cpstats->nodes;\r
5307     stats.score = cpstats->score;\r
5308     stats.time = cpstats->time;\r
5309     stats.pv = cpstats->movelist;\r
5310     stats.hint = lastHint;\r
5311     stats.an_move_index = 0;\r
5312     stats.an_move_count = 0;\r
5313 \r
5314     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {\r
5315         stats.hint = cpstats->move_name;\r
5316         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;\r
5317         stats.an_move_count = cpstats->nr_moves;\r
5318     }\r
5319 \r
5320     SetProgramStats( &stats );\r
5321 }\r
5322 \r
5323 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)\r
5324 {   // [HGM] book: this routine intercepts moves to simulate book replies\r
5325     char *bookHit = NULL;\r
5326 \r
5327     //first determine if the incoming move brings opponent into his book\r
5328     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))\r
5329         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move\r
5330     if(appData.debugMode && bookHit) fprintf(debugFP, "book hit = %s\n", bookHit);\r
5331     if(bookHit != NULL && !cps->bookSuspend) {\r
5332         // make sure opponent is not going to reply after receiving move to book position\r
5333         SendToProgram("force\n", cps);\r
5334         cps->bookSuspend = TRUE; // flag indicating it has to be restarted\r
5335     }\r
5336     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move\r
5337     // now arrange restart after book miss\r
5338     if(bookHit) {\r
5339         // after a book hit we never send 'go', and the code after the call to this routine\r
5340         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').\r
5341         char buf[MSG_SIZ];\r
5342         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(\r
5343         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it\r
5344         SendToProgram(buf, cps);\r
5345         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'\r
5346     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine\r
5347         SendToProgram("go\n", cps);\r
5348         cps->bookSuspend = FALSE; // after a 'go' we are never suspended\r
5349     } else { // 'go' might be sent based on 'firstMove' after this routine returns\r
5350         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return\r
5351             SendToProgram("go\n", cps); \r
5352         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss\r
5353     }\r
5354     return bookHit; // notify caller of hit, so it can take action to send move to opponent\r
5355 }\r
5356 \r
5357 char *savedMessage;\r
5358 ChessProgramState *savedState;\r
5359 void DeferredBookMove(void)\r
5360 {\r
5361         if(savedState->lastPing != savedState->lastPong)\r
5362                     ScheduleDelayedEvent(DeferredBookMove, 10);\r
5363         else\r
5364         HandleMachineMove(savedMessage, savedState);\r
5365 }\r
5366 \r
5367 void\r
5368 HandleMachineMove(message, cps)\r
5369      char *message;\r
5370      ChessProgramState *cps;\r
5371 {\r
5372     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];\r
5373     char realname[MSG_SIZ];\r
5374     int fromX, fromY, toX, toY;\r
5375     ChessMove moveType;\r
5376     char promoChar;\r
5377     char *p;\r
5378     int machineWhite;\r
5379     char *bookHit;\r
5380 \r
5381 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit\r
5382     /*\r
5383      * Kludge to ignore BEL characters\r
5384      */\r
5385     while (*message == '\007') message++;\r
5386 \r
5387     /*\r
5388      * [HGM] engine debug message: ignore lines starting with '#' character\r
5389      */\r
5390     if(cps->debug && *message == '#') return;\r
5391 \r
5392     /*\r
5393      * Look for book output\r
5394      */\r
5395     if (cps == &first && bookRequested) {\r
5396         if (message[0] == '\t' || message[0] == ' ') {\r
5397             /* Part of the book output is here; append it */\r
5398             strcat(bookOutput, message);\r
5399             strcat(bookOutput, "  \n");\r
5400             return;\r
5401         } else if (bookOutput[0] != NULLCHAR) {\r
5402             /* All of book output has arrived; display it */\r
5403             char *p = bookOutput;\r
5404             while (*p != NULLCHAR) {\r
5405                 if (*p == '\t') *p = ' ';\r
5406                 p++;\r
5407             }\r
5408             DisplayInformation(bookOutput);\r
5409             bookRequested = FALSE;\r
5410             /* Fall through to parse the current output */\r
5411         }\r
5412     }\r
5413 \r
5414     /*\r
5415      * Look for machine move.\r
5416      */\r
5417     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||\r
5418         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) \r
5419     {\r
5420         /* This method is only useful on engines that support ping */\r
5421         if (cps->lastPing != cps->lastPong) {\r
5422           if (gameMode == BeginningOfGame) {\r
5423             /* Extra move from before last new; ignore */\r
5424             if (appData.debugMode) {\r
5425                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
5426             }\r
5427           } else {\r
5428             if (appData.debugMode) {\r
5429                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
5430                         cps->which, gameMode);\r
5431             }\r
5432 \r
5433             SendToProgram("undo\n", cps);\r
5434           }\r
5435           return;\r
5436         }\r
5437 \r
5438         switch (gameMode) {\r
5439           case BeginningOfGame:\r
5440             /* Extra move from before last reset; ignore */\r
5441             if (appData.debugMode) {\r
5442                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
5443             }\r
5444             return;\r
5445 \r
5446           case EndOfGame:\r
5447           case IcsIdle:\r
5448           default:\r
5449             /* Extra move after we tried to stop.  The mode test is\r
5450                not a reliable way of detecting this problem, but it's\r
5451                the best we can do on engines that don't support ping.\r
5452             */\r
5453             if (appData.debugMode) {\r
5454                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
5455                         cps->which, gameMode);\r
5456             }\r
5457             SendToProgram("undo\n", cps);\r
5458             return;\r
5459 \r
5460           case MachinePlaysWhite:\r
5461           case IcsPlayingWhite:\r
5462             machineWhite = TRUE;\r
5463             break;\r
5464 \r
5465           case MachinePlaysBlack:\r
5466           case IcsPlayingBlack:\r
5467             machineWhite = FALSE;\r
5468             break;\r
5469 \r
5470           case TwoMachinesPlay:\r
5471             machineWhite = (cps->twoMachinesColor[0] == 'w');\r
5472             break;\r
5473         }\r
5474         if (WhiteOnMove(forwardMostMove) != machineWhite) {\r
5475             if (appData.debugMode) {\r
5476                 fprintf(debugFP,\r
5477                         "Ignoring move out of turn by %s, gameMode %d"\r
5478                         ", forwardMost %d\n",\r
5479                         cps->which, gameMode, forwardMostMove);\r
5480             }\r
5481             return;\r
5482         }\r
5483 \r
5484     if (appData.debugMode) { int f = forwardMostMove;\r
5485         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,\r
5486                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
5487     }\r
5488         if(cps->alphaRank) AlphaRank(machineMove, 4);\r
5489         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,\r
5490                               &fromX, &fromY, &toX, &toY, &promoChar)) {\r
5491             /* Machine move could not be parsed; ignore it. */\r
5492             sprintf(buf1, "Illegal move \"%s\" from %s machine",\r
5493                     machineMove, cps->which);\r
5494             DisplayError(buf1, 0);\r
5495             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d%c",\r
5496                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);\r
5497             if (gameMode == TwoMachinesPlay) {\r
5498               GameEnds(machineWhite ? BlackWins : WhiteWins,\r
5499                        buf1, GE_XBOARD);\r
5500             }\r
5501             return;\r
5502         }\r
5503 \r
5504         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */\r
5505         /* So we have to redo legality test with true e.p. status here,  */\r
5506         /* to make sure an illegal e.p. capture does not slip through,   */\r
5507         /* to cause a forfeit on a justified illegal-move complaint      */\r
5508         /* of the opponent.                                              */\r
5509         if( gameMode==TwoMachinesPlay && appData.testLegality\r
5510             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */\r
5511                                                               ) {\r
5512            ChessMove moveType;\r
5513            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
5514                         epStatus[forwardMostMove], castlingRights[forwardMostMove],\r
5515                              fromY, fromX, toY, toX, promoChar);\r
5516             if (appData.debugMode) {\r
5517                 int i;\r
5518                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",\r
5519                     castlingRights[forwardMostMove][i], castlingRank[i]);\r
5520                 fprintf(debugFP, "castling rights\n");\r
5521             }\r
5522             if(moveType == IllegalMove) {\r
5523                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",\r
5524                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);\r
5525                 GameEnds(machineWhite ? BlackWins : WhiteWins,\r
5526                            buf1, GE_XBOARD);\r
5527            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)\r
5528            /* [HGM] Kludge to handle engines that send FRC-style castling\r
5529               when they shouldn't (like TSCP-Gothic) */\r
5530            switch(moveType) {\r
5531              case WhiteASideCastleFR:\r
5532              case BlackASideCastleFR:\r
5533                toX+=2;\r
5534                currentMoveString[2]++;\r
5535                break;\r
5536              case WhiteHSideCastleFR:\r
5537              case BlackHSideCastleFR:\r
5538                toX--;\r
5539                currentMoveString[2]--;\r
5540                break;\r
5541            }\r
5542         }\r
5543         hintRequested = FALSE;\r
5544         lastHint[0] = NULLCHAR;\r
5545         bookRequested = FALSE;\r
5546         /* Program may be pondering now */\r
5547         cps->maybeThinking = TRUE;\r
5548         if (cps->sendTime == 2) cps->sendTime = 1;\r
5549         if (cps->offeredDraw) cps->offeredDraw--;\r
5550 \r
5551 #if ZIPPY\r
5552         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&\r
5553             first.initDone) {\r
5554           SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
5555           ics_user_moved = 1;\r
5556           if(appData.autoKibitz) { /* [HGM] kibitz: send most-recent PV info to ICS */\r
5557                 char buf[3*MSG_SIZ];\r
5558 \r
5559                 sprintf(buf, "kibitz %d/%+.2f (%.2f sec, %.0f nodes, %1.0f knps) PV = %s\n",\r
5560                         programStats.depth,\r
5561                         programStats.score / 100.,\r
5562                         programStats.time / 100.,\r
5563                         (double) programStats.nodes,\r
5564                         programStats.nodes / (10*abs(programStats.time) + 1.),\r
5565                         programStats.movelist);\r
5566                 SendToICS(buf);\r
5567           }\r
5568         }\r
5569 #endif\r
5570         /* currentMoveString is set as a side-effect of ParseOneMove */\r
5571         strcpy(machineMove, currentMoveString);\r
5572         strcat(machineMove, "\n");\r
5573         strcpy(moveList[forwardMostMove], machineMove);\r
5574 \r
5575         /* [AS] Save move info and clear stats for next move */\r
5576         pvInfoList[ forwardMostMove ].score = programStats.score;\r
5577         pvInfoList[ forwardMostMove ].depth = programStats.depth;\r
5578         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats\r
5579         ClearProgramStats();\r
5580         thinkOutput[0] = NULLCHAR;\r
5581         hiddenThinkOutputState = 0;\r
5582 \r
5583         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/\r
5584 \r
5585         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */\r
5586         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {\r
5587             int count = 0;\r
5588 \r
5589             while( count < adjudicateLossPlies ) {\r
5590                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;\r
5591 \r
5592                 if( count & 1 ) {\r
5593                     score = -score; /* Flip score for winning side */\r
5594                 }\r
5595 \r
5596                 if( score > adjudicateLossThreshold ) {\r
5597                     break;\r
5598                 }\r
5599 \r
5600                 count++;\r
5601             }\r
5602 \r
5603             if( count >= adjudicateLossPlies ) {\r
5604                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5605 \r
5606                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5607                     "Xboard adjudication", \r
5608                     GE_XBOARD );\r
5609 \r
5610                 return;\r
5611             }\r
5612         }\r
5613 \r
5614         if( gameMode == TwoMachinesPlay ) {\r
5615           // [HGM] some adjudications useful with buggy engines\r
5616             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;\r
5617           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper) {\r
5618 \r
5619             if(appData.testLegality)\r
5620             // don't wait for engine to announce game end if we can judge ourselves\r
5621             switch (MateTest(boards[forwardMostMove],\r
5622                                  PosFlags(forwardMostMove), epFile,\r
5623                                        castlingRights[forwardMostMove]) ) {\r
5624               case MT_NONE:\r
5625               case MT_CHECK:\r
5626               default:\r
5627                 break;\r
5628               case MT_STALEMATE:\r
5629                 epStatus[forwardMostMove] = EP_STALEMATE;\r
5630                 if(appData.checkMates) {\r
5631                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5632                     GameEnds( GameIsDrawn, "Xboard adjudication: Stalemate",\r
5633                         GE_XBOARD );\r
5634                 }\r
5635                 break;\r
5636               case MT_CHECKMATE:\r
5637                 epStatus[forwardMostMove] = EP_CHECKMATE;\r
5638                 if(appData.checkMates) {\r
5639                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5640                     GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, \r
5641                     "Xboard adjudication: Checkmate", \r
5642                     GE_XBOARD );\r
5643                 }\r
5644                 break;\r
5645             }\r
5646 \r
5647             if( appData.testLegality )\r
5648             {   /* [HGM] Some more adjudications for obstinate engines */\r
5649                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,\r
5650                     NrWQ=0, NrBQ=0, NrW=0, bishopsColor = 0,\r
5651                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j, k;\r
5652                 static int moveCount = 6;\r
5653 \r
5654                 /* First absolutely insufficient mating material. Count what is on board. */\r
5655                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
5656                 {   ChessSquare p = boards[forwardMostMove][i][j];\r
5657                     int m=i;\r
5658 \r
5659                     switch((int) p)\r
5660                     {   /* count B,N,R and other of each side */\r
5661                         case WhiteKnight:\r
5662                              NrWN++; break;\r
5663                         case WhiteBishop:\r
5664                              bishopsColor |= 1 << ((i^j)&1);\r
5665                              NrWB++; break;\r
5666                         case BlackKnight:\r
5667                              NrBN++; break;\r
5668                         case BlackBishop:\r
5669                              bishopsColor |= 1 << ((i^j)&1);\r
5670                              NrBB++; break;\r
5671                         case WhiteRook:\r
5672                              NrWR++; break;\r
5673                         case BlackRook:\r
5674                              NrBR++; break;\r
5675                         case WhiteQueen:\r
5676                              NrWQ++; break;\r
5677                         case BlackQueen:\r
5678                              NrBQ++; break;\r
5679                         case EmptySquare: \r
5680                              break;\r
5681                         case BlackPawn:\r
5682                              m = 7-i;\r
5683                         case WhitePawn:\r
5684                              PawnAdvance += m; NrPawns++;\r
5685                     }\r
5686                     NrPieces += (p != EmptySquare);\r
5687                     NrW += ((int)p < (int)BlackPawn);\r
5688                 }\r
5689 \r
5690                 if( NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 || NrPieces == 2\r
5691                  || NrPieces == 4 && NrBB+NrWB == NrPieces-2 && bishopsColor != 3)\r
5692                 {    /* KBK, KNK, KK of KBKB with like Bishops */\r
5693 \r
5694                      /* always flag draws, for judging claims */\r
5695                      epStatus[forwardMostMove] = EP_INSUF_DRAW;\r
5696 \r
5697                      if(appData.materialDraws) {\r
5698                          /* but only adjudicate them if adjudication enabled */\r
5699                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5700                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );\r
5701                          return;\r
5702                      }\r
5703                 }\r
5704 \r
5705                 /* Shatranj baring rule */\r
5706                 if( gameInfo.variant == VariantShatranj && (NrW == 1 || NrPieces - NrW == 1) )\r
5707                 {    /* bare King */\r
5708 \r
5709                      if(--bare < 0 && appData.checkMates) {\r
5710                          /* but only adjudicate them if adjudication enabled */\r
5711                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5712                          GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, \r
5713                                                         "Xboard adjudication: Bare king", GE_XBOARD );\r
5714                          return;\r
5715                      }\r
5716                 } else bare = 1;\r
5717 \r
5718                 /* Then some trivial draws (only adjudicate, cannot be claimed) */\r
5719                 if(NrPieces == 4 && \r
5720                    (   NrWR == 1 && NrBR == 1 /* KRKR */\r
5721                    || NrWQ==1 && NrBQ==1     /* KQKQ */\r
5722                    || NrWN==2 || NrBN==2     /* KNNK */\r
5723                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */\r
5724                   ) ) {\r
5725                      if(--moveCount < 0 && appData.trivialDraws)\r
5726                      {    /* if the first 3 moves do not show a tactical win, declare draw */\r
5727                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5728                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );\r
5729                           return;\r
5730                      }\r
5731                 } else moveCount = 6;\r
5732             }\r
5733           }\r
5734 #if 1\r
5735     if (appData.debugMode) { int i;\r
5736       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",\r
5737               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],\r
5738               appData.drawRepeats);\r
5739       for( i=forwardMostMove; i>=backwardMostMove; i-- )\r
5740            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);\r
5741 \r
5742     }\r
5743 #endif\r
5744                 /* Check for rep-draws */\r
5745                 count = 0;\r
5746                 for(k = forwardMostMove-2;\r
5747                     k>=backwardMostMove && k>=forwardMostMove-100 &&\r
5748                         epStatus[k] < EP_UNKNOWN &&\r
5749                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;\r
5750                     k-=2)\r
5751                 {   int rights=0;\r
5752 #if 0\r
5753     if (appData.debugMode) {\r
5754       fprintf(debugFP, " loop\n");\r
5755     }\r
5756 #endif\r
5757                     if(CompareBoards(boards[k], boards[forwardMostMove])) {\r
5758 #if 0\r
5759     if (appData.debugMode) {\r
5760       fprintf(debugFP, "match\n");\r
5761     }\r
5762 #endif\r
5763                         /* compare castling rights */\r
5764                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&\r
5765                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )\r
5766                                 rights++; /* King lost rights, while rook still had them */\r
5767                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */\r
5768                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||\r
5769                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )\r
5770                                    rights++; /* but at least one rook lost them */\r
5771                         }\r
5772                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&\r
5773                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )\r
5774                                 rights++; \r
5775                         if( castlingRights[forwardMostMove][5] >= 0 ) {\r
5776                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||\r
5777                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )\r
5778                                    rights++;\r
5779                         }\r
5780 #if 0\r
5781     if (appData.debugMode) {\r
5782       for(i=0; i<nrCastlingRights; i++)\r
5783       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);\r
5784     }\r
5785 \r
5786     if (appData.debugMode) {\r
5787       fprintf(debugFP, " %d %d\n", rights, k);\r
5788     }\r
5789 #endif\r
5790                         if( rights == 0 && ++count > appData.drawRepeats-2\r
5791                             && appData.drawRepeats > 1) {\r
5792                              /* adjudicate after user-specified nr of repeats */\r
5793                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5794                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );\r
5795                              return;\r
5796                         }\r
5797                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */\r
5798                              epStatus[forwardMostMove] = EP_REP_DRAW;\r
5799                     }\r
5800                 }\r
5801 \r
5802                 /* Now we test for 50-move draws. Determine ply count */\r
5803                 count = forwardMostMove;\r
5804                 /* look for last irreversble move */\r
5805                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )\r
5806                     count--;\r
5807                 /* if we hit starting position, add initial plies */\r
5808                 if( count == backwardMostMove )\r
5809                     count -= initialRulePlies;\r
5810                 count = forwardMostMove - count; \r
5811                 if( count >= 100)\r
5812                          epStatus[forwardMostMove] = EP_RULE_DRAW;\r
5813                          /* this is used to judge if draw claims are legal */\r
5814                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {\r
5815                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5816                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );\r
5817                          return;\r
5818                 }\r
5819 \r
5820                 /* if draw offer is pending, treat it as a draw claim\r
5821                  * when draw condition present, to allow engines a way to\r
5822                  * claim draws before making their move to avoid a race\r
5823                  * condition occurring after their move\r
5824                  */\r
5825                 if( cps->other->offeredDraw || cps->offeredDraw ) {\r
5826                          char *p = NULL;\r
5827                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)\r
5828                              p = "Draw claim: 50-move rule";\r
5829                          if(epStatus[forwardMostMove] == EP_REP_DRAW)\r
5830                              p = "Draw claim: 3-fold repetition";\r
5831                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)\r
5832                              p = "Draw claim: insufficient mating material";\r
5833                          if( p != NULL ) {\r
5834                              GameEnds( GameIsDrawn, p, GE_XBOARD );\r
5835                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5836                              return;\r
5837                          }\r
5838                 }\r
5839 \r
5840 \r
5841         }\r
5842 \r
5843         if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {\r
5844             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5845 \r
5846             GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );\r
5847 \r
5848             return;\r
5849         }\r
5850 \r
5851         bookHit = NULL;\r
5852         if (gameMode == TwoMachinesPlay) {\r
5853             /* [HGM] relaying draw offers moved to after reception of move */\r
5854             /* and interpreting offer as claim if it brings draw condition */\r
5855             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {\r
5856                 SendToProgram("draw\n", cps->other);\r
5857             }\r
5858             if (cps->other->sendTime) {\r
5859                 SendTimeRemaining(cps->other,\r
5860                                   cps->other->twoMachinesColor[0] == 'w');\r
5861             }\r
5862             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);\r
5863             if (firstMove && !bookHit) {\r
5864                 firstMove = FALSE;\r
5865                 if (cps->other->useColors) {\r
5866                   SendToProgram(cps->other->twoMachinesColor, cps->other);\r
5867                 }\r
5868                 SendToProgram("go\n", cps->other);\r
5869             }\r
5870             cps->other->maybeThinking = TRUE;\r
5871         }\r
5872 \r
5873         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5874         \r
5875         if (!pausing && appData.ringBellAfterMoves) {\r
5876             RingBell();\r
5877         }\r
5878 \r
5879         /* \r
5880          * Reenable menu items that were disabled while\r
5881          * machine was thinking\r
5882          */\r
5883         if (gameMode != TwoMachinesPlay)\r
5884             SetUserThinkingEnables();\r
5885 \r
5886         // [HGM] book: after book hit opponent has received move and is now in force mode\r
5887         // force the book reply into it, and then fake that it outputted this move by jumping\r
5888         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move\r
5889         if(bookHit) {\r
5890                 static char bookMove[MSG_SIZ]; // a bit generous?\r
5891 \r
5892                 strcpy(bookMove, "move ");\r
5893                 strcat(bookMove, bookHit);\r
5894                 message = bookMove;\r
5895                 cps = cps->other;\r
5896                 programStats.depth = programStats.nodes = programStats.time = \r
5897                 programStats.score = programStats.got_only_move = 0;\r
5898                 sprintf(programStats.movelist, "%s (xbook)", bookMove);\r
5899 \r
5900                 if(cps->lastPing != cps->lastPong) {\r
5901                     savedMessage = message; // args for deferred call\r
5902                     savedState = cps;\r
5903                     ScheduleDelayedEvent(DeferredBookMove, 10);\r
5904                     return;\r
5905                 }\r
5906                 goto FakeBookMove;\r
5907         }\r
5908 \r
5909         return;\r
5910     }\r
5911 \r
5912     /* Set special modes for chess engines.  Later something general\r
5913      *  could be added here; for now there is just one kludge feature,\r
5914      *  needed because Crafty 15.10 and earlier don't ignore SIGINT\r
5915      *  when "xboard" is given as an interactive command.\r
5916      */\r
5917     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {\r
5918         cps->useSigint = FALSE;\r
5919         cps->useSigterm = FALSE;\r
5920     }\r
5921 \r
5922     /* [HGM] Allow engine to set up a position. Don't ask me why one would\r
5923      * want this, I was asked to put it in, and obliged.\r
5924      */\r
5925     if (!strncmp(message, "setboard ", 9)) {\r
5926         Board initial_position; int i;\r
5927 \r
5928         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);\r
5929 \r
5930         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {\r
5931             DisplayError("Bad FEN received from engine", 0);\r
5932             return ;\r
5933         } else {\r
5934            Reset(FALSE, FALSE);\r
5935            CopyBoard(boards[0], initial_position);\r
5936            initialRulePlies = FENrulePlies;\r
5937            epStatus[0] = FENepStatus;\r
5938            for( i=0; i<nrCastlingRights; i++ )\r
5939                 castlingRights[0][i] = FENcastlingRights[i];\r
5940            if(blackPlaysFirst) gameMode = MachinePlaysWhite;\r
5941            else gameMode = MachinePlaysBlack;                 \r
5942            DrawPosition(FALSE, boards[currentMove]);\r
5943         }\r
5944         return;\r
5945     }\r
5946 \r
5947     /*\r
5948      * Look for communication commands\r
5949      */\r
5950     if (!strncmp(message, "telluser ", 9)) {\r
5951         DisplayNote(message + 9);\r
5952         return;\r
5953     }\r
5954     if (!strncmp(message, "tellusererror ", 14)) {\r
5955         DisplayError(message + 14, 0);\r
5956         return;\r
5957     }\r
5958     if (!strncmp(message, "tellopponent ", 13)) {\r
5959       if (appData.icsActive) {\r
5960         if (loggedOn) {\r
5961           sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);\r
5962           SendToICS(buf1);\r
5963         }\r
5964       } else {\r
5965         DisplayNote(message + 13);\r
5966       }\r
5967       return;\r
5968     }\r
5969     if (!strncmp(message, "tellothers ", 11)) {\r
5970       if (appData.icsActive) {\r
5971         if (loggedOn) {\r
5972           sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);\r
5973           SendToICS(buf1);\r
5974         }\r
5975       }\r
5976       return;\r
5977     }\r
5978     if (!strncmp(message, "tellall ", 8)) {\r
5979       if (appData.icsActive) {\r
5980         if (loggedOn) {\r
5981           sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);\r
5982           SendToICS(buf1);\r
5983         }\r
5984       } else {\r
5985         DisplayNote(message + 8);\r
5986       }\r
5987       return;\r
5988     }\r
5989     if (strncmp(message, "warning", 7) == 0) {\r
5990         /* Undocumented feature, use tellusererror in new code */\r
5991         DisplayError(message, 0);\r
5992         return;\r
5993     }\r
5994     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {\r
5995         strcpy(realname, cps->tidy);\r
5996         strcat(realname, " query");\r
5997         AskQuestion(realname, buf2, buf1, cps->pr);\r
5998         return;\r
5999     }\r
6000     /* Commands from the engine directly to ICS.  We don't allow these to be \r
6001      *  sent until we are logged on. Crafty kibitzes have been known to \r
6002      *  interfere with the login process.\r
6003      */\r
6004     if (loggedOn) {\r
6005         if (!strncmp(message, "tellics ", 8)) {\r
6006             SendToICS(message + 8);\r
6007             SendToICS("\n");\r
6008             return;\r
6009         }\r
6010         if (!strncmp(message, "tellicsnoalias ", 15)) {\r
6011             SendToICS(ics_prefix);\r
6012             SendToICS(message + 15);\r
6013             SendToICS("\n");\r
6014             return;\r
6015         }\r
6016         /* The following are for backward compatibility only */\r
6017         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||\r
6018             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {\r
6019             SendToICS(ics_prefix);\r
6020             SendToICS(message);\r
6021             SendToICS("\n");\r
6022             return;\r
6023         }\r
6024     }\r
6025     if (strncmp(message, "feature ", 8) == 0) {\r
6026       ParseFeatures(message+8, cps);\r
6027     }\r
6028     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {\r
6029         return;\r
6030     }\r
6031     /*\r
6032      * If the move is illegal, cancel it and redraw the board.\r
6033      * Also deal with other error cases.  Matching is rather loose\r
6034      * here to accommodate engines written before the spec.\r
6035      */\r
6036     if (strncmp(message + 1, "llegal move", 11) == 0 ||\r
6037         strncmp(message, "Error", 5) == 0) {\r
6038         if (StrStr(message, "name") || \r
6039             StrStr(message, "rating") || StrStr(message, "?") ||\r
6040             StrStr(message, "result") || StrStr(message, "board") ||\r
6041             StrStr(message, "bk") || StrStr(message, "computer") ||\r
6042             StrStr(message, "variant") || StrStr(message, "hint") ||\r
6043             StrStr(message, "random") || StrStr(message, "depth") ||\r
6044             StrStr(message, "accepted")) {\r
6045             return;\r
6046         }\r
6047         if (StrStr(message, "protover")) {\r
6048           /* Program is responding to input, so it's apparently done\r
6049              initializing, and this error message indicates it is\r
6050              protocol version 1.  So we don't need to wait any longer\r
6051              for it to initialize and send feature commands. */\r
6052           FeatureDone(cps, 1);\r
6053           cps->protocolVersion = 1;\r
6054           return;\r
6055         }\r
6056         cps->maybeThinking = FALSE;\r
6057 \r
6058         if (StrStr(message, "draw")) {\r
6059             /* Program doesn't have "draw" command */\r
6060             cps->sendDrawOffers = 0;\r
6061             return;\r
6062         }\r
6063         if (cps->sendTime != 1 &&\r
6064             (StrStr(message, "time") || StrStr(message, "otim"))) {\r
6065           /* Program apparently doesn't have "time" or "otim" command */\r
6066           cps->sendTime = 0;\r
6067           return;\r
6068         }\r
6069         if (StrStr(message, "analyze")) {\r
6070             cps->analysisSupport = FALSE;\r
6071             cps->analyzing = FALSE;\r
6072             Reset(FALSE, TRUE);\r
6073             sprintf(buf2, "%s does not support analysis", cps->tidy);\r
6074             DisplayError(buf2, 0);\r
6075             return;\r
6076         }\r
6077         if (StrStr(message, "(no matching move)st")) {\r
6078           /* Special kludge for GNU Chess 4 only */\r
6079           cps->stKludge = TRUE;\r
6080           SendTimeControl(cps, movesPerSession, timeControl,\r
6081                           timeIncrement, appData.searchDepth,\r
6082                           searchTime);\r
6083           return;\r
6084         }\r
6085         if (StrStr(message, "(no matching move)sd")) {\r
6086           /* Special kludge for GNU Chess 4 only */\r
6087           cps->sdKludge = TRUE;\r
6088           SendTimeControl(cps, movesPerSession, timeControl,\r
6089                           timeIncrement, appData.searchDepth,\r
6090                           searchTime);\r
6091           return;\r
6092         }\r
6093         if (!StrStr(message, "llegal")) {\r
6094             return;\r
6095         }\r
6096         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
6097             gameMode == IcsIdle) return;\r
6098         if (forwardMostMove <= backwardMostMove) return;\r
6099 #if 0\r
6100         /* Following removed: it caused a bug where a real illegal move\r
6101            message in analyze mored would be ignored. */\r
6102         if (cps == &first && programStats.ok_to_send == 0) {\r
6103             /* Bogus message from Crafty responding to "."  This filtering\r
6104                can miss some of the bad messages, but fortunately the bug \r
6105                is fixed in current Crafty versions, so it doesn't matter. */\r
6106             return;\r
6107         }\r
6108 #endif\r
6109         if (pausing) PauseEvent();\r
6110         if (gameMode == PlayFromGameFile) {\r
6111             /* Stop reading this game file */\r
6112             gameMode = EditGame;\r
6113             ModeHighlight();\r
6114         }\r
6115         currentMove = --forwardMostMove;\r
6116         DisplayMove(currentMove-1); /* before DisplayMoveError */\r
6117         SwitchClocks();\r
6118         DisplayBothClocks();\r
6119         sprintf(buf1, "Illegal move \"%s\" (rejected by %s chess program)",\r
6120                 parseList[currentMove], cps->which);\r
6121         DisplayMoveError(buf1);\r
6122         DrawPosition(FALSE, boards[currentMove]);\r
6123 \r
6124         /* [HGM] illegal-move claim should forfeit game when Xboard */\r
6125         /* only passes fully legal moves                            */\r
6126         if( appData.testLegality && gameMode == TwoMachinesPlay ) {\r
6127             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,\r
6128                                 "False illegal-move claim", GE_XBOARD );\r
6129         }\r
6130         return;\r
6131     }\r
6132     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {\r
6133         /* Program has a broken "time" command that\r
6134            outputs a string not ending in newline.\r
6135            Don't use it. */\r
6136         cps->sendTime = 0;\r
6137     }\r
6138     \r
6139     /*\r
6140      * If chess program startup fails, exit with an error message.\r
6141      * Attempts to recover here are futile.\r
6142      */\r
6143     if ((StrStr(message, "unknown host") != NULL)\r
6144         || (StrStr(message, "No remote directory") != NULL)\r
6145         || (StrStr(message, "not found") != NULL)\r
6146         || (StrStr(message, "No such file") != NULL)\r
6147         || (StrStr(message, "can't alloc") != NULL)\r
6148         || (StrStr(message, "Permission denied") != NULL)) {\r
6149 \r
6150         cps->maybeThinking = FALSE;\r
6151         sprintf(buf1, "Failed to start %s chess program %s on %s: %s\n",\r
6152                 cps->which, cps->program, cps->host, message);\r
6153         RemoveInputSource(cps->isr);\r
6154         DisplayFatalError(buf1, 0, 1);\r
6155         return;\r
6156     }\r
6157     \r
6158     /* \r
6159      * Look for hint output\r
6160      */\r
6161     if (sscanf(message, "Hint: %s", buf1) == 1) {\r
6162         if (cps == &first && hintRequested) {\r
6163             hintRequested = FALSE;\r
6164             if (ParseOneMove(buf1, forwardMostMove, &moveType,\r
6165                                  &fromX, &fromY, &toX, &toY, &promoChar)) {\r
6166                 (void) CoordsToAlgebraic(boards[forwardMostMove],\r
6167                                     PosFlags(forwardMostMove), EP_UNKNOWN,\r
6168                                     fromY, fromX, toY, toX, promoChar, buf1);\r
6169                 sprintf(buf2, "Hint: %s", buf1);\r
6170                 DisplayInformation(buf2);\r
6171             } else {\r
6172                 /* Hint move could not be parsed!? */\r
6173                 sprintf(buf2,\r
6174                         "Illegal hint move \"%s\"\nfrom %s chess program",\r
6175                         buf1, cps->which);\r
6176                 DisplayError(buf2, 0);\r
6177             }\r
6178         } else {\r
6179             strcpy(lastHint, buf1);\r
6180         }\r
6181         return;\r
6182     }\r
6183 \r
6184     /*\r
6185      * Ignore other messages if game is not in progress\r
6186      */\r
6187     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
6188         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;\r
6189 \r
6190     /*\r
6191      * look for win, lose, draw, or draw offer\r
6192      */\r
6193     if (strncmp(message, "1-0", 3) == 0) {\r
6194         char *p, *q, *r = "";\r
6195         p = strchr(message, '{');\r
6196         if (p) {\r
6197             q = strchr(p, '}');\r
6198             if (q) {\r
6199                 *q = NULLCHAR;\r
6200                 r = p + 1;\r
6201             }\r
6202         }\r
6203         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */\r
6204         return;\r
6205     } else if (strncmp(message, "0-1", 3) == 0) {\r
6206         char *p, *q, *r = "";\r
6207         p = strchr(message, '{');\r
6208         if (p) {\r
6209             q = strchr(p, '}');\r
6210             if (q) {\r
6211                 *q = NULLCHAR;\r
6212                 r = p + 1;\r
6213             }\r
6214         }\r
6215         /* Kludge for Arasan 4.1 bug */\r
6216         if (strcmp(r, "Black resigns") == 0) {\r
6217             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));\r
6218             return;\r
6219         }\r
6220         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));\r
6221         return;\r
6222     } else if (strncmp(message, "1/2", 3) == 0) {\r
6223         char *p, *q, *r = "";\r
6224         p = strchr(message, '{');\r
6225         if (p) {\r
6226             q = strchr(p, '}');\r
6227             if (q) {\r
6228                 *q = NULLCHAR;\r
6229                 r = p + 1;\r
6230             }\r
6231         }\r
6232             \r
6233         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));\r
6234         return;\r
6235 \r
6236     } else if (strncmp(message, "White resign", 12) == 0) {\r
6237         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
6238         return;\r
6239     } else if (strncmp(message, "Black resign", 12) == 0) {\r
6240         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
6241         return;\r
6242     } else if (strncmp(message, "White matches", 13) == 0 ||\r
6243                strncmp(message, "Black matches", 13) == 0   ) {\r
6244         /* [HGM] ignore GNUShogi noises */\r
6245         return;\r
6246     } else if (strncmp(message, "White", 5) == 0 &&\r
6247                message[5] != '(' &&\r
6248                StrStr(message, "Black") == NULL) {\r
6249         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6250         return;\r
6251     } else if (strncmp(message, "Black", 5) == 0 &&\r
6252                message[5] != '(') {\r
6253         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6254         return;\r
6255     } else if (strcmp(message, "resign") == 0 ||\r
6256                strcmp(message, "computer resigns") == 0) {\r
6257         switch (gameMode) {\r
6258           case MachinePlaysBlack:\r
6259           case IcsPlayingBlack:\r
6260             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);\r
6261             break;\r
6262           case MachinePlaysWhite:\r
6263           case IcsPlayingWhite:\r
6264             GameEnds(BlackWins, "White resigns", GE_ENGINE);\r
6265             break;\r
6266           case TwoMachinesPlay:\r
6267             if (cps->twoMachinesColor[0] == 'w')\r
6268               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
6269             else\r
6270               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
6271             break;\r
6272           default:\r
6273             /* can't happen */\r
6274             break;\r
6275         }\r
6276         return;\r
6277     } else if (strncmp(message, "opponent mates", 14) == 0) {\r
6278         switch (gameMode) {\r
6279           case MachinePlaysBlack:\r
6280           case IcsPlayingBlack:\r
6281             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
6282             break;\r
6283           case MachinePlaysWhite:\r
6284           case IcsPlayingWhite:\r
6285             GameEnds(BlackWins, "Black mates", GE_ENGINE);\r
6286             break;\r
6287           case TwoMachinesPlay:\r
6288             if (cps->twoMachinesColor[0] == 'w')\r
6289               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6290             else\r
6291               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6292             break;\r
6293           default:\r
6294             /* can't happen */\r
6295             break;\r
6296         }\r
6297         return;\r
6298     } else if (strncmp(message, "computer mates", 14) == 0) {\r
6299         switch (gameMode) {\r
6300           case MachinePlaysBlack:\r
6301           case IcsPlayingBlack:\r
6302             GameEnds(BlackWins, "Black mates", GE_ENGINE1);\r
6303             break;\r
6304           case MachinePlaysWhite:\r
6305           case IcsPlayingWhite:\r
6306             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
6307             break;\r
6308           case TwoMachinesPlay:\r
6309             if (cps->twoMachinesColor[0] == 'w')\r
6310               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6311             else\r
6312               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6313             break;\r
6314           default:\r
6315             /* can't happen */\r
6316             break;\r
6317         }\r
6318         return;\r
6319     } else if (strncmp(message, "checkmate", 9) == 0) {\r
6320         if (WhiteOnMove(forwardMostMove)) {\r
6321             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6322         } else {\r
6323             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6324         }\r
6325         return;\r
6326     } else if (strstr(message, "Draw") != NULL ||\r
6327                strstr(message, "game is a draw") != NULL) {\r
6328         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));\r
6329         return;\r
6330     } else if (strstr(message, "offer") != NULL &&\r
6331                strstr(message, "draw") != NULL) {\r
6332 #if ZIPPY\r
6333         if (appData.zippyPlay && first.initDone) {\r
6334             /* Relay offer to ICS */\r
6335             SendToICS(ics_prefix);\r
6336             SendToICS("draw\n");\r
6337         }\r
6338 #endif\r
6339         cps->offeredDraw = 2; /* valid until this engine moves twice */\r
6340         if (gameMode == TwoMachinesPlay) {\r
6341             if (cps->other->offeredDraw) {\r
6342                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
6343             /* [HGM] in two-machine mode we delay relaying draw offer      */\r
6344             /* until after we also have move, to see if it is really claim */\r
6345             }\r
6346 #if 0\r
6347               else {\r
6348                 if (cps->other->sendDrawOffers) {\r
6349                     SendToProgram("draw\n", cps->other);\r
6350                 }\r
6351             }\r
6352 #endif\r
6353         } else if (gameMode == MachinePlaysWhite ||\r
6354                    gameMode == MachinePlaysBlack) {\r
6355           if (userOfferedDraw) {\r
6356             DisplayInformation("Machine accepts your draw offer");\r
6357             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
6358           } else {\r
6359             DisplayInformation("Machine offers a draw\nSelect Action / Draw to agree");\r
6360           }\r
6361         }\r
6362     }\r
6363 \r
6364     \r
6365     /*\r
6366      * Look for thinking output\r
6367      */\r
6368     if ( appData.showThinking // [HGM] thinking: test all options that cause this output\r
6369           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()\r
6370                                 ) {\r
6371         int plylev, mvleft, mvtot, curscore, time;\r
6372         char mvname[MOVE_LEN];\r
6373         unsigned long nodes;\r
6374         char plyext;\r
6375         int ignore = FALSE;\r
6376         int prefixHint = FALSE;\r
6377         mvname[0] = NULLCHAR;\r
6378 \r
6379         switch (gameMode) {\r
6380           case MachinePlaysBlack:\r
6381           case IcsPlayingBlack:\r
6382             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
6383             break;\r
6384           case MachinePlaysWhite:\r
6385           case IcsPlayingWhite:\r
6386             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
6387             break;\r
6388           case AnalyzeMode:\r
6389           case AnalyzeFile:\r
6390             break;\r
6391           case TwoMachinesPlay:\r
6392             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {\r
6393                 ignore = TRUE;\r
6394             }\r
6395             break;\r
6396           default:\r
6397             ignore = TRUE;\r
6398             break;\r
6399         }\r
6400 \r
6401         if (!ignore) {\r
6402             buf1[0] = NULLCHAR;\r
6403             if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",\r
6404                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {\r
6405 \r
6406                 if (plyext != ' ' && plyext != '\t') {\r
6407                     time *= 100;\r
6408                 }\r
6409 \r
6410                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
6411                 if( cps->scoreIsAbsolute && \r
6412                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )\r
6413                 {\r
6414                     curscore = -curscore;\r
6415                 }\r
6416 \r
6417 \r
6418                 programStats.depth = plylev;\r
6419                 programStats.nodes = nodes;\r
6420                 programStats.time = time;\r
6421                 programStats.score = curscore;\r
6422                 programStats.got_only_move = 0;\r
6423 \r
6424                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */\r
6425                         int ticklen;\r
6426 \r
6427                         if(cps->nps == 0) ticklen = 10*time;       // use engine reported time\r
6428                         else ticklen = (1000. * nodes) / cps->nps; // convert node count to time\r
6429                         if(WhiteOnMove(forwardMostMove)) \r
6430                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;\r
6431                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;\r
6432                 }\r
6433 \r
6434                 /* Buffer overflow protection */\r
6435                 if (buf1[0] != NULLCHAR) {\r
6436                     if (strlen(buf1) >= sizeof(programStats.movelist)\r
6437                         && appData.debugMode) {\r
6438                         fprintf(debugFP,\r
6439                                 "PV is too long; using the first %d bytes.\n",\r
6440                                 sizeof(programStats.movelist) - 1);\r
6441                     }\r
6442 \r
6443                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );\r
6444                 } else {\r
6445                     sprintf(programStats.movelist, " no PV\n");\r
6446                 }\r
6447 \r
6448                 if (programStats.seen_stat) {\r
6449                     programStats.ok_to_send = 1;\r
6450                 }\r
6451 \r
6452                 if (strchr(programStats.movelist, '(') != NULL) {\r
6453                     programStats.line_is_book = 1;\r
6454                     programStats.nr_moves = 0;\r
6455                     programStats.moves_left = 0;\r
6456                 } else {\r
6457                     programStats.line_is_book = 0;\r
6458                 }\r
6459 \r
6460                 SendProgramStatsToFrontend( cps, &programStats );\r
6461 \r
6462                 /* \r
6463                     [AS] Protect the thinkOutput buffer from overflow... this\r
6464                     is only useful if buf1 hasn't overflowed first!\r
6465                 */\r
6466                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",\r
6467                         plylev, \r
6468                         (gameMode == TwoMachinesPlay ?\r
6469                          ToUpper(cps->twoMachinesColor[0]) : ' '),\r
6470                         ((double) curscore) / 100.0,\r
6471                         prefixHint ? lastHint : "",\r
6472                         prefixHint ? " " : "" );\r
6473 \r
6474                 if( buf1[0] != NULLCHAR ) {\r
6475                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;\r
6476 \r
6477                     if( strlen(buf1) > max_len ) {\r
6478                         if( appData.debugMode) {\r
6479                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");\r
6480                         }\r
6481                         buf1[max_len+1] = '\0';\r
6482                     }\r
6483 \r
6484                     strcat( thinkOutput, buf1 );\r
6485                 }\r
6486 \r
6487                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
6488                     DisplayMove(currentMove - 1);\r
6489                     DisplayAnalysis();\r
6490                 }\r
6491                 return;\r
6492 \r
6493             } else if ((p=StrStr(message, "(only move)")) != NULL) {\r
6494                 /* crafty (9.25+) says "(only move) <move>"\r
6495                  * if there is only 1 legal move\r
6496                  */\r
6497                 sscanf(p, "(only move) %s", buf1);\r
6498                 sprintf(thinkOutput, "%s (only move)", buf1);\r
6499                 sprintf(programStats.movelist, "%s (only move)", buf1);\r
6500                 programStats.depth = 1;\r
6501                 programStats.nr_moves = 1;\r
6502                 programStats.moves_left = 1;\r
6503                 programStats.nodes = 1;\r
6504                 programStats.time = 1;\r
6505                 programStats.got_only_move = 1;\r
6506 \r
6507                 /* Not really, but we also use this member to\r
6508                    mean "line isn't going to change" (Crafty\r
6509                    isn't searching, so stats won't change) */\r
6510                 programStats.line_is_book = 1;\r
6511 \r
6512                 SendProgramStatsToFrontend( cps, &programStats );\r
6513                 \r
6514                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {\r
6515                     DisplayMove(currentMove - 1);\r
6516                     DisplayAnalysis();\r
6517                 }\r
6518                 return;\r
6519             } else if (sscanf(message,"stat01: %d %lu %d %d %d %s",\r
6520                               &time, &nodes, &plylev, &mvleft,\r
6521                               &mvtot, mvname) >= 5) {\r
6522                 /* The stat01: line is from Crafty (9.29+) in response\r
6523                    to the "." command */\r
6524                 programStats.seen_stat = 1;\r
6525                 cps->maybeThinking = TRUE;\r
6526 \r
6527                 if (programStats.got_only_move || !appData.periodicUpdates)\r
6528                   return;\r
6529 \r
6530                 programStats.depth = plylev;\r
6531                 programStats.time = time;\r
6532                 programStats.nodes = nodes;\r
6533                 programStats.moves_left = mvleft;\r
6534                 programStats.nr_moves = mvtot;\r
6535                 strcpy(programStats.move_name, mvname);\r
6536                 programStats.ok_to_send = 1;\r
6537                 programStats.movelist[0] = '\0';\r
6538 \r
6539                 SendProgramStatsToFrontend( cps, &programStats );\r
6540 \r
6541                 DisplayAnalysis();\r
6542                 return;\r
6543 \r
6544             } else if (strncmp(message,"++",2) == 0) {\r
6545                 /* Crafty 9.29+ outputs this */\r
6546                 programStats.got_fail = 2;\r
6547                 return;\r
6548 \r
6549             } else if (strncmp(message,"--",2) == 0) {\r
6550                 /* Crafty 9.29+ outputs this */\r
6551                 programStats.got_fail = 1;\r
6552                 return;\r
6553 \r
6554             } else if (thinkOutput[0] != NULLCHAR &&\r
6555                        strncmp(message, "    ", 4) == 0) {\r
6556                 unsigned message_len;\r
6557 \r
6558                 p = message;\r
6559                 while (*p && *p == ' ') p++;\r
6560 \r
6561                 message_len = strlen( p );\r
6562 \r
6563                 /* [AS] Avoid buffer overflow */\r
6564                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {\r
6565                     strcat(thinkOutput, " ");\r
6566                     strcat(thinkOutput, p);\r
6567                 }\r
6568 \r
6569                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {\r
6570                     strcat(programStats.movelist, " ");\r
6571                     strcat(programStats.movelist, p);\r
6572                 }\r
6573 \r
6574                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {\r
6575                     DisplayMove(currentMove - 1);\r
6576                     DisplayAnalysis();\r
6577                 }\r
6578                 return;\r
6579             }\r
6580         }\r
6581         else {\r
6582             buf1[0] = NULLCHAR;\r
6583 \r
6584             if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",\r
6585                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) \r
6586             {\r
6587                 ChessProgramStats cpstats;\r
6588 \r
6589                 if (plyext != ' ' && plyext != '\t') {\r
6590                     time *= 100;\r
6591                 }\r
6592 \r
6593                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
6594                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {\r
6595                     curscore = -curscore;\r
6596                 }\r
6597 \r
6598                 cpstats.depth = plylev;\r
6599                 cpstats.nodes = nodes;\r
6600                 cpstats.time = time;\r
6601                 cpstats.score = curscore;\r
6602                 cpstats.got_only_move = 0;\r
6603                 cpstats.movelist[0] = '\0';\r
6604 \r
6605                 if (buf1[0] != NULLCHAR) {\r
6606                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );\r
6607                 }\r
6608 \r
6609                 cpstats.ok_to_send = 0;\r
6610                 cpstats.line_is_book = 0;\r
6611                 cpstats.nr_moves = 0;\r
6612                 cpstats.moves_left = 0;\r
6613 \r
6614                 SendProgramStatsToFrontend( cps, &cpstats );\r
6615             }\r
6616         }\r
6617     }\r
6618 }\r
6619 \r
6620 \r
6621 /* Parse a game score from the character string "game", and\r
6622    record it as the history of the current game.  The game\r
6623    score is NOT assumed to start from the standard position. \r
6624    The display is not updated in any way.\r
6625    */\r
6626 void\r
6627 ParseGameHistory(game)\r
6628      char *game;\r
6629 {\r
6630     ChessMove moveType;\r
6631     int fromX, fromY, toX, toY, boardIndex;\r
6632     char promoChar;\r
6633     char *p, *q;\r
6634     char buf[MSG_SIZ];\r
6635 \r
6636     if (appData.debugMode)\r
6637       fprintf(debugFP, "Parsing game history: %s\n", game);\r
6638 \r
6639     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");\r
6640     gameInfo.site = StrSave(appData.icsHost);\r
6641     gameInfo.date = PGNDate();\r
6642     gameInfo.round = StrSave("-");\r
6643 \r
6644     /* Parse out names of players */\r
6645     while (*game == ' ') game++;\r
6646     p = buf;\r
6647     while (*game != ' ') *p++ = *game++;\r
6648     *p = NULLCHAR;\r
6649     gameInfo.white = StrSave(buf);\r
6650     while (*game == ' ') game++;\r
6651     p = buf;\r
6652     while (*game != ' ' && *game != '\n') *p++ = *game++;\r
6653     *p = NULLCHAR;\r
6654     gameInfo.black = StrSave(buf);\r
6655 \r
6656     /* Parse moves */\r
6657     boardIndex = blackPlaysFirst ? 1 : 0;\r
6658     yynewstr(game);\r
6659     for (;;) {\r
6660         yyboardindex = boardIndex;\r
6661         moveType = (ChessMove) yylex();\r
6662         switch (moveType) {\r
6663           case IllegalMove:             /* maybe suicide chess, etc. */\r
6664   if (appData.debugMode) {\r
6665     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);\r
6666     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6667     setbuf(debugFP, NULL);\r
6668   }\r
6669           case WhitePromotionChancellor:\r
6670           case BlackPromotionChancellor:\r
6671           case WhitePromotionArchbishop:\r
6672           case BlackPromotionArchbishop:\r
6673           case WhitePromotionQueen:\r
6674           case BlackPromotionQueen:\r
6675           case WhitePromotionRook:\r
6676           case BlackPromotionRook:\r
6677           case WhitePromotionBishop:\r
6678           case BlackPromotionBishop:\r
6679           case WhitePromotionKnight:\r
6680           case BlackPromotionKnight:\r
6681           case WhitePromotionKing:\r
6682           case BlackPromotionKing:\r
6683           case NormalMove:\r
6684           case WhiteCapturesEnPassant:\r
6685           case BlackCapturesEnPassant:\r
6686           case WhiteKingSideCastle:\r
6687           case WhiteQueenSideCastle:\r
6688           case BlackKingSideCastle:\r
6689           case BlackQueenSideCastle:\r
6690           case WhiteKingSideCastleWild:\r
6691           case WhiteQueenSideCastleWild:\r
6692           case BlackKingSideCastleWild:\r
6693           case BlackQueenSideCastleWild:\r
6694           /* PUSH Fabien */\r
6695           case WhiteHSideCastleFR:\r
6696           case WhiteASideCastleFR:\r
6697           case BlackHSideCastleFR:\r
6698           case BlackASideCastleFR:\r
6699           /* POP Fabien */\r
6700             fromX = currentMoveString[0] - AAA;\r
6701             fromY = currentMoveString[1] - ONE;\r
6702             toX = currentMoveString[2] - AAA;\r
6703             toY = currentMoveString[3] - ONE;\r
6704             promoChar = currentMoveString[4];\r
6705             break;\r
6706           case WhiteDrop:\r
6707           case BlackDrop:\r
6708             fromX = moveType == WhiteDrop ?\r
6709               (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
6710             (int) CharToPiece(ToLower(currentMoveString[0]));\r
6711             fromY = DROP_RANK;\r
6712             toX = currentMoveString[2] - AAA;\r
6713             toY = currentMoveString[3] - ONE;\r
6714             promoChar = NULLCHAR;\r
6715             break;\r
6716           case AmbiguousMove:\r
6717             /* bug? */\r
6718             sprintf(buf, "Ambiguous move in ICS output: \"%s\"", yy_text);\r
6719   if (appData.debugMode) {\r
6720     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);\r
6721     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6722     setbuf(debugFP, NULL);\r
6723   }\r
6724             DisplayError(buf, 0);\r
6725             return;\r
6726           case ImpossibleMove:\r
6727             /* bug? */\r
6728             sprintf(buf, "Illegal move in ICS output: \"%s\"", yy_text);\r
6729   if (appData.debugMode) {\r
6730     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);\r
6731     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6732     setbuf(debugFP, NULL);\r
6733   }\r
6734             DisplayError(buf, 0);\r
6735             return;\r
6736           case (ChessMove) 0:   /* end of file */\r
6737             if (boardIndex < backwardMostMove) {\r
6738                 /* Oops, gap.  How did that happen? */\r
6739                 DisplayError("Gap in move list", 0);\r
6740                 return;\r
6741             }\r
6742             backwardMostMove =  blackPlaysFirst ? 1 : 0;\r
6743             if (boardIndex > forwardMostMove) {\r
6744                 forwardMostMove = boardIndex;\r
6745             }\r
6746             return;\r
6747           case ElapsedTime:\r
6748             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {\r
6749                 strcat(parseList[boardIndex-1], " ");\r
6750                 strcat(parseList[boardIndex-1], yy_text);\r
6751             }\r
6752             continue;\r
6753           case Comment:\r
6754           case PGNTag:\r
6755           case NAG:\r
6756           default:\r
6757             /* ignore */\r
6758             continue;\r
6759           case WhiteWins:\r
6760           case BlackWins:\r
6761           case GameIsDrawn:\r
6762           case GameUnfinished:\r
6763             if (gameMode == IcsExamining) {\r
6764                 if (boardIndex < backwardMostMove) {\r
6765                     /* Oops, gap.  How did that happen? */\r
6766                     return;\r
6767                 }\r
6768                 backwardMostMove = blackPlaysFirst ? 1 : 0;\r
6769                 return;\r
6770             }\r
6771             gameInfo.result = moveType;\r
6772             p = strchr(yy_text, '{');\r
6773             if (p == NULL) p = strchr(yy_text, '(');\r
6774             if (p == NULL) {\r
6775                 p = yy_text;\r
6776                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
6777             } else {\r
6778                 q = strchr(p, *p == '{' ? '}' : ')');\r
6779                 if (q != NULL) *q = NULLCHAR;\r
6780                 p++;\r
6781             }\r
6782             gameInfo.resultDetails = StrSave(p);\r
6783             continue;\r
6784         }\r
6785         if (boardIndex >= forwardMostMove &&\r
6786             !(gameMode == IcsObserving && ics_gamenum == -1)) {\r
6787             backwardMostMove = blackPlaysFirst ? 1 : 0;\r
6788             return;\r
6789         }\r
6790         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),\r
6791                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,\r
6792                                  parseList[boardIndex]);\r
6793         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);\r
6794         /* currentMoveString is set as a side-effect of yylex */\r
6795         strcpy(moveList[boardIndex], currentMoveString);\r
6796         strcat(moveList[boardIndex], "\n");\r
6797         boardIndex++;\r
6798         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);\r
6799         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),\r
6800                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {\r
6801           case MT_NONE:\r
6802           case MT_STALEMATE:\r
6803           default:\r
6804             break;\r
6805           case MT_CHECK:\r
6806             if(gameInfo.variant != VariantShogi)\r
6807                 strcat(parseList[boardIndex - 1], "+");\r
6808             break;\r
6809           case MT_CHECKMATE:\r
6810             strcat(parseList[boardIndex - 1], "#");\r
6811             break;\r
6812         }\r
6813     }\r
6814 }\r
6815 \r
6816 \r
6817 /* Apply a move to the given board  */\r
6818 void\r
6819 ApplyMove(fromX, fromY, toX, toY, promoChar, board)\r
6820      int fromX, fromY, toX, toY;\r
6821      int promoChar;\r
6822      Board board;\r
6823 {\r
6824   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;\r
6825 \r
6826     /* [HGM] compute & store e.p. status and castling rights for new position */\r
6827     /* if we are updating a board for which those exist (i.e. in boards[])    */\r
6828     if((p = ((int)board - (int)boards[0])/((int)boards[1]-(int)boards[0])) < MAX_MOVES && p > 0)\r
6829     { int i, j;\r
6830 \r
6831       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;\r
6832       oldEP = epStatus[p-1];\r
6833       epStatus[p] = EP_NONE;\r
6834 \r
6835       if( board[toY][toX] != EmptySquare ) \r
6836            epStatus[p] = EP_CAPTURE;  \r
6837 \r
6838       if( board[fromY][fromX] == WhitePawn ) {\r
6839            epStatus[p] = EP_PAWN_MOVE; \r
6840            if( toY-fromY==2)\r
6841                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&\r
6842                         gameInfo.variant != VariantBerolina || toX < fromX)\r
6843                       epStatus[p] = toX | berolina;\r
6844                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&\r
6845                         gameInfo.variant != VariantBerolina || toX > fromX) \r
6846                       epStatus[p] = toX;\r
6847       } else \r
6848       if( board[fromY][fromX] == BlackPawn ) {\r
6849            epStatus[p] = EP_PAWN_MOVE; \r
6850            if( toY-fromY== -2)\r
6851                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&\r
6852                         gameInfo.variant != VariantBerolina || toX < fromX)\r
6853                       epStatus[p] = toX | berolina;\r
6854                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&\r
6855                         gameInfo.variant != VariantBerolina || toX > fromX) \r
6856                       epStatus[p] = toX;\r
6857        }\r
6858 \r
6859        for(i=0; i<nrCastlingRights; i++) {\r
6860            castlingRights[p][i] = castlingRights[p-1][i];\r
6861            if(castlingRights[p][i] == fromX && castlingRank[i] == fromY ||\r
6862               castlingRights[p][i] == toX   && castlingRank[i] == toY   \r
6863              ) castlingRights[p][i] = -1; // revoke for moved or captured piece\r
6864        }\r
6865 \r
6866     }\r
6867 \r
6868   /* [HGM] In Shatranj and Courier all promotions are to Ferz */\r
6869   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)\r
6870        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);\r
6871          \r
6872   if (fromX == toX && fromY == toY) return;\r
6873 \r
6874   if (fromY == DROP_RANK) {\r
6875         /* must be first */\r
6876         piece = board[toY][toX] = (ChessSquare) fromX;\r
6877   } else {\r
6878      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */\r
6879      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */\r
6880      if(gameInfo.variant == VariantKnightmate)\r
6881          king += (int) WhiteUnicorn - (int) WhiteKing;\r
6882 \r
6883     /* Code added by Tord: */\r
6884     /* FRC castling assumed when king captures friendly rook. */\r
6885     if (board[fromY][fromX] == WhiteKing &&\r
6886              board[toY][toX] == WhiteRook) {\r
6887       board[fromY][fromX] = EmptySquare;\r
6888       board[toY][toX] = EmptySquare;\r
6889       if(toX > fromX) {\r
6890         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;\r
6891       } else {\r
6892         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;\r
6893       }\r
6894     } else if (board[fromY][fromX] == BlackKing &&\r
6895                board[toY][toX] == BlackRook) {\r
6896       board[fromY][fromX] = EmptySquare;\r
6897       board[toY][toX] = EmptySquare;\r
6898       if(toX > fromX) {\r
6899         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;\r
6900       } else {\r
6901         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;\r
6902       }\r
6903     /* End of code added by Tord */\r
6904 \r
6905     } else if (board[fromY][fromX] == king\r
6906         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
6907         && toY == fromY && toX > fromX+1) {\r
6908         board[fromY][fromX] = EmptySquare;\r
6909         board[toY][toX] = king;\r
6910         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
6911         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
6912     } else if (board[fromY][fromX] == king\r
6913         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
6914                && toY == fromY && toX < fromX-1) {\r
6915         board[fromY][fromX] = EmptySquare;\r
6916         board[toY][toX] = king;\r
6917         board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
6918         board[fromY][BOARD_LEFT] = EmptySquare;\r
6919     } else if (board[fromY][fromX] == WhitePawn\r
6920                && toY == BOARD_HEIGHT-1\r
6921                && gameInfo.variant != VariantXiangqi\r
6922                ) {\r
6923         /* white pawn promotion */\r
6924         board[toY][toX] = CharToPiece(ToUpper(promoChar));\r
6925         if (board[toY][toX] == EmptySquare) {\r
6926             board[toY][toX] = WhiteQueen;\r
6927         }\r
6928         if(gameInfo.variant==VariantBughouse ||\r
6929            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
6930             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
6931         board[fromY][fromX] = EmptySquare;\r
6932     } else if ((fromY == BOARD_HEIGHT-4)\r
6933                && (toX != fromX)\r
6934                && gameInfo.variant != VariantXiangqi\r
6935                && gameInfo.variant != VariantBerolina\r
6936                && (board[fromY][fromX] == WhitePawn)\r
6937                && (board[toY][toX] == EmptySquare)) {\r
6938         board[fromY][fromX] = EmptySquare;\r
6939         board[toY][toX] = WhitePawn;\r
6940         captured = board[toY - 1][toX];\r
6941         board[toY - 1][toX] = EmptySquare;\r
6942     } else if ((fromY == BOARD_HEIGHT-4)\r
6943                && (toX == fromX)\r
6944                && gameInfo.variant == VariantBerolina\r
6945                && (board[fromY][fromX] == WhitePawn)\r
6946                && (board[toY][toX] == EmptySquare)) {\r
6947         board[fromY][fromX] = EmptySquare;\r
6948         board[toY][toX] = WhitePawn;\r
6949         if(oldEP & EP_BEROLIN_A) {\r
6950                 captured = board[fromY][fromX-1];\r
6951                 board[fromY][fromX-1] = EmptySquare;\r
6952         }else{  captured = board[fromY][fromX+1];\r
6953                 board[fromY][fromX+1] = EmptySquare;\r
6954         }\r
6955     } else if (board[fromY][fromX] == king\r
6956         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
6957                && toY == fromY && toX > fromX+1) {\r
6958         board[fromY][fromX] = EmptySquare;\r
6959         board[toY][toX] = king;\r
6960         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
6961         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
6962     } else if (board[fromY][fromX] == king\r
6963         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
6964                && toY == fromY && toX < fromX-1) {\r
6965         board[fromY][fromX] = EmptySquare;\r
6966         board[toY][toX] = king;\r
6967         board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
6968         board[fromY][BOARD_LEFT] = EmptySquare;\r
6969     } else if (fromY == 7 && fromX == 3\r
6970                && board[fromY][fromX] == BlackKing\r
6971                && toY == 7 && toX == 5) {\r
6972         board[fromY][fromX] = EmptySquare;\r
6973         board[toY][toX] = BlackKing;\r
6974         board[fromY][7] = EmptySquare;\r
6975         board[toY][4] = BlackRook;\r
6976     } else if (fromY == 7 && fromX == 3\r
6977                && board[fromY][fromX] == BlackKing\r
6978                && toY == 7 && toX == 1) {\r
6979         board[fromY][fromX] = EmptySquare;\r
6980         board[toY][toX] = BlackKing;\r
6981         board[fromY][0] = EmptySquare;\r
6982         board[toY][2] = BlackRook;\r
6983     } else if (board[fromY][fromX] == BlackPawn\r
6984                && toY == 0\r
6985                && gameInfo.variant != VariantXiangqi\r
6986                ) {\r
6987         /* black pawn promotion */\r
6988         board[0][toX] = CharToPiece(ToLower(promoChar));\r
6989         if (board[0][toX] == EmptySquare) {\r
6990             board[0][toX] = BlackQueen;\r
6991         }\r
6992         if(gameInfo.variant==VariantBughouse ||\r
6993            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
6994             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
6995         board[fromY][fromX] = EmptySquare;\r
6996     } else if ((fromY == 3)\r
6997                && (toX != fromX)\r
6998                && gameInfo.variant != VariantXiangqi\r
6999                && gameInfo.variant != VariantBerolina\r
7000                && (board[fromY][fromX] == BlackPawn)\r
7001                && (board[toY][toX] == EmptySquare)) {\r
7002         board[fromY][fromX] = EmptySquare;\r
7003         board[toY][toX] = BlackPawn;\r
7004         captured = board[toY + 1][toX];\r
7005         board[toY + 1][toX] = EmptySquare;\r
7006     } else if ((fromY == 3)\r
7007                && (toX == fromX)\r
7008                && gameInfo.variant == VariantBerolina\r
7009                && (board[fromY][fromX] == BlackPawn)\r
7010                && (board[toY][toX] == EmptySquare)) {\r
7011         board[fromY][fromX] = EmptySquare;\r
7012         board[toY][toX] = BlackPawn;\r
7013         if(oldEP & EP_BEROLIN_A) {\r
7014                 captured = board[fromY][fromX-1];\r
7015                 board[fromY][fromX-1] = EmptySquare;\r
7016         }else{  captured = board[fromY][fromX+1];\r
7017                 board[fromY][fromX+1] = EmptySquare;\r
7018         }\r
7019     } else {\r
7020         board[toY][toX] = board[fromY][fromX];\r
7021         board[fromY][fromX] = EmptySquare;\r
7022     }\r
7023 \r
7024     /* [HGM] now we promote for Shogi, if needed */\r
7025     if(gameInfo.variant == VariantShogi && promoChar == 'q')\r
7026         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
7027   }\r
7028 \r
7029     if (gameInfo.holdingsWidth != 0) {\r
7030 \r
7031       /* !!A lot more code needs to be written to support holdings  */\r
7032       /* [HGM] OK, so I have written it. Holdings are stored in the */\r
7033       /* penultimate board files, so they are automaticlly stored   */\r
7034       /* in the game history.                                       */\r
7035       if (fromY == DROP_RANK) {\r
7036         /* Delete from holdings, by decreasing count */\r
7037         /* and erasing image if necessary            */\r
7038         p = (int) fromX;\r
7039         if(p < (int) BlackPawn) { /* white drop */\r
7040              p -= (int)WhitePawn;\r
7041              if(p >= gameInfo.holdingsSize) p = 0;\r
7042              if(--board[p][BOARD_WIDTH-2] == 0)\r
7043                   board[p][BOARD_WIDTH-1] = EmptySquare;\r
7044         } else {                  /* black drop */\r
7045              p -= (int)BlackPawn;\r
7046              if(p >= gameInfo.holdingsSize) p = 0;\r
7047              if(--board[BOARD_HEIGHT-1-p][1] == 0)\r
7048                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;\r
7049         }\r
7050       }\r
7051       if (captured != EmptySquare && gameInfo.holdingsSize > 0\r
7052           && gameInfo.variant != VariantBughouse        ) {\r
7053         /* [HGM] holdings: Add to holdings, if holdings exist */\r
7054         if(gameInfo.variant == VariantSuper) { \r
7055                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip\r
7056                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;\r
7057         }\r
7058         p = (int) captured;\r
7059         if (p >= (int) BlackPawn) {\r
7060           p -= (int)BlackPawn;\r
7061           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
7062                   /* in Shogi restore piece to its original  first */\r
7063                   captured = (ChessSquare) (DEMOTED captured);\r
7064                   p = DEMOTED p;\r
7065           }\r
7066           p = PieceToNumber((ChessSquare)p);\r
7067           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }\r
7068           board[p][BOARD_WIDTH-2]++;\r
7069           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;\r
7070         } else {\r
7071           p -= (int)WhitePawn;\r
7072           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
7073                   captured = (ChessSquare) (DEMOTED captured);\r
7074                   p = DEMOTED p;\r
7075           }\r
7076           p = PieceToNumber((ChessSquare)p);\r
7077           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }\r
7078           board[BOARD_HEIGHT-1-p][1]++;\r
7079           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;\r
7080         }\r
7081       }\r
7082 \r
7083     } else if (gameInfo.variant == VariantAtomic) {\r
7084       if (captured != EmptySquare) {\r
7085         int y, x;\r
7086         for (y = toY-1; y <= toY+1; y++) {\r
7087           for (x = toX-1; x <= toX+1; x++) {\r
7088             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&\r
7089                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {\r
7090               board[y][x] = EmptySquare;\r
7091             }\r
7092           }\r
7093         }\r
7094         board[toY][toX] = EmptySquare;\r
7095       }\r
7096     }\r
7097     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {\r
7098         /* [HGM] Shogi promotions */\r
7099         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
7100     }\r
7101 \r
7102 }\r
7103 \r
7104 /* Updates forwardMostMove */\r
7105 void\r
7106 MakeMove(fromX, fromY, toX, toY, promoChar)\r
7107      int fromX, fromY, toX, toY;\r
7108      int promoChar;\r
7109 {\r
7110     forwardMostMove++;\r
7111 \r
7112     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting */\r
7113         int timeLeft; static int lastLoadFlag=0; int king, piece;\r
7114         piece = boards[forwardMostMove-1][fromY][fromX];\r
7115         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;\r
7116         if(gameInfo.variant == VariantKnightmate)\r
7117             king += (int) WhiteUnicorn - (int) WhiteKing;\r
7118         if(forwardMostMove == 1) {\r
7119             if(blackPlaysFirst) \r
7120                 fprintf(serverMoves, "%s;", second.tidy);\r
7121             fprintf(serverMoves, "%s;", first.tidy);\r
7122             if(!blackPlaysFirst) \r
7123                 fprintf(serverMoves, "%s;", second.tidy);\r
7124         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");\r
7125         lastLoadFlag = loadFlag;\r
7126         // print base move\r
7127         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);\r
7128         // print castling suffix\r
7129         if( toY == fromY && piece == king ) {\r
7130             if(toX-fromX > 1)\r
7131                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);\r
7132             if(fromX-toX >1)\r
7133                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);\r
7134         }\r
7135         // e.p. suffix\r
7136         if( (boards[forwardMostMove-1][fromY][fromX] == WhitePawn ||\r
7137              boards[forwardMostMove-1][fromY][fromX] == BlackPawn   ) &&\r
7138              boards[forwardMostMove-1][toY][toX] == EmptySquare\r
7139              && fromX != toX )\r
7140                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);\r
7141         // promotion suffix\r
7142         if(promoChar != NULLCHAR)\r
7143                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);\r
7144         if(!loadFlag) {\r
7145             fprintf(serverMoves, "/%d/%d",\r
7146                pvInfoList[forwardMostMove-1].depth, pvInfoList[forwardMostMove-1].score);\r
7147             if(forwardMostMove & 1) timeLeft = whiteTimeRemaining/1000;\r
7148             else                    timeLeft = blackTimeRemaining/1000;\r
7149             fprintf(serverMoves, "/%d", timeLeft);\r
7150         }\r
7151         fflush(serverMoves);\r
7152     }\r
7153 \r
7154     if (forwardMostMove >= MAX_MOVES) {\r
7155       DisplayFatalError("Game too long; increase MAX_MOVES and recompile",\r
7156                         0, 1);\r
7157       return;\r
7158     }\r
7159     SwitchClocks();\r
7160     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
7161     timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
7162     if (commentList[forwardMostMove] != NULL) {\r
7163         free(commentList[forwardMostMove]);\r
7164         commentList[forwardMostMove] = NULL;\r
7165     }\r
7166     CopyBoard(boards[forwardMostMove], boards[forwardMostMove - 1]);\r
7167     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove]);\r
7168     gameInfo.result = GameUnfinished;\r
7169     if (gameInfo.resultDetails != NULL) {\r
7170         free(gameInfo.resultDetails);\r
7171         gameInfo.resultDetails = NULL;\r
7172     }\r
7173     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,\r
7174                               moveList[forwardMostMove - 1]);\r
7175     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],\r
7176                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,\r
7177                              fromY, fromX, toY, toX, promoChar,\r
7178                              parseList[forwardMostMove - 1]);\r
7179     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
7180                        epStatus[forwardMostMove], /* [HGM] use true e.p. */\r
7181                             castlingRights[forwardMostMove]) ) {\r
7182       case MT_NONE:\r
7183       case MT_STALEMATE:\r
7184       default:\r
7185         break;\r
7186       case MT_CHECK:\r
7187         if(gameInfo.variant != VariantShogi)\r
7188             strcat(parseList[forwardMostMove - 1], "+");\r
7189         break;\r
7190       case MT_CHECKMATE:\r
7191         strcat(parseList[forwardMostMove - 1], "#");\r
7192         break;\r
7193     }\r
7194     if (appData.debugMode) {\r
7195         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);\r
7196     }\r
7197 \r
7198 }\r
7199 \r
7200 /* Updates currentMove if not pausing */\r
7201 void\r
7202 ShowMove(fromX, fromY, toX, toY)\r
7203 {\r
7204     int instant = (gameMode == PlayFromGameFile) ?\r
7205         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;\r
7206     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
7207         if (!instant) {\r
7208             if (forwardMostMove == currentMove + 1) {\r
7209                 AnimateMove(boards[forwardMostMove - 1],\r
7210                             fromX, fromY, toX, toY);\r
7211             }\r
7212             if (appData.highlightLastMove) {\r
7213                 SetHighlights(fromX, fromY, toX, toY);\r
7214             }\r
7215         }\r
7216         currentMove = forwardMostMove;\r
7217     }\r
7218 \r
7219     if (instant) return;\r
7220 \r
7221     DisplayMove(currentMove - 1);\r
7222     DrawPosition(FALSE, boards[currentMove]);\r
7223     DisplayBothClocks();\r
7224     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
7225 }\r
7226 \r
7227 void SendEgtPath(ChessProgramState *cps)\r
7228 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */\r
7229         char buf[MSG_SIZ], name[MSG_SIZ], *p;\r
7230 \r
7231         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;\r
7232 \r
7233         while(*p) {\r
7234             char c, *q = name+1, *r, *s;\r
7235 \r
7236             name[0] = ','; // extract next format name from feature and copy with prefixed ','\r
7237             while(*p && *p != ',') *q++ = *p++;\r
7238             *q++ = ':'; *q = 0;\r
7239             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && \r
7240                 strcmp(name, ",nalimov:") == 0 ) {\r
7241                 // take nalimov path from the menu-changeable option first, if it is defined\r
7242                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);\r
7243                 SendToProgram(buf,cps);     // send egtbpath command for nalimov\r
7244             } else\r
7245             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||\r
7246                 (s = StrStr(appData.egtFormats, name)) != NULL) {\r
7247                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma\r
7248                 s = r = StrStr(s, ":") + 1; // beginning of path info\r
7249                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string\r
7250                 c = *r; *r = 0;             // temporarily null-terminate path info\r
7251                     *--q = 0;               // strip of trailig ':' from name\r
7252                     sprintf(buf, "egtbpath %s %s\n", name+1, s);\r
7253                 *r = c;\r
7254                 SendToProgram(buf,cps);     // send egtbpath command for this format\r
7255             }\r
7256             if(*p == ',') p++; // read away comma to position for next format name\r
7257         }\r
7258 }\r
7259 \r
7260 void\r
7261 InitChessProgram(cps, setup)\r
7262      ChessProgramState *cps;\r
7263      int setup; /* [HGM] needed to setup FRC opening position */\r
7264 {\r
7265     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;\r
7266     if (appData.noChessProgram) return;\r
7267     hintRequested = FALSE;\r
7268     bookRequested = FALSE;\r
7269 \r
7270     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */\r
7271     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */\r
7272     if(cps->memSize) { /* [HGM] memory */\r
7273         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);\r
7274         SendToProgram(buf, cps);\r
7275     }\r
7276     SendEgtPath(cps); /* [HGM] EGT */\r
7277     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */\r
7278         sprintf(buf, "cores %d\n", appData.smpCores);\r
7279         SendToProgram(buf, cps);\r
7280     }\r
7281 \r
7282     SendToProgram(cps->initString, cps);\r
7283     if (gameInfo.variant != VariantNormal &&\r
7284         gameInfo.variant != VariantLoadable\r
7285         /* [HGM] also send variant if board size non-standard */\r
7286         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0\r
7287                                             ) {\r
7288       char *v = VariantName(gameInfo.variant);\r
7289       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {\r
7290         /* [HGM] in protocol 1 we have to assume all variants valid */\r
7291         sprintf(buf, "Variant %s not supported by %s", v, cps->tidy);\r
7292         DisplayFatalError(buf, 0, 1);\r
7293         return;\r
7294       }\r
7295 \r
7296       /* [HGM] make prefix for non-standard board size. Awkward testing... */\r
7297       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7298       if( gameInfo.variant == VariantXiangqi )\r
7299            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;\r
7300       if( gameInfo.variant == VariantShogi )\r
7301            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;\r
7302       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )\r
7303            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;\r
7304       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || \r
7305                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )\r
7306            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7307       if( gameInfo.variant == VariantCourier )\r
7308            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7309       if( gameInfo.variant == VariantSuper )\r
7310            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;\r
7311 \r
7312       if(overruled) {\r
7313            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, \r
7314                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name\r
7315            /* [HGM] varsize: try first if this defiant size variant is specifically known */\r
7316            if(StrStr(cps->variants, b) == NULL) { \r
7317                // specific sized variant not known, check if general sizing allowed\r
7318                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best\r
7319                    if(StrStr(cps->variants, "boardsize") == NULL) {\r
7320                        sprintf(buf, "Board size %dx%d+%d not supported by %s",\r
7321                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);\r
7322                        DisplayFatalError(buf, 0, 1);\r
7323                        return;\r
7324                    }\r
7325                    /* [HGM] here we really should compare with the maximum supported board size */\r
7326                }\r
7327            }\r
7328       } else sprintf(b, "%s", VariantName(gameInfo.variant));\r
7329       sprintf(buf, "variant %s\n", b);\r
7330       SendToProgram(buf, cps);\r
7331     }\r
7332     currentlyInitializedVariant = gameInfo.variant;\r
7333 \r
7334     /* [HGM] send opening position in FRC to first engine */\r
7335     if(setup) {\r
7336           SendToProgram("force\n", cps);\r
7337           SendBoard(cps, 0);\r
7338           /* engine is now in force mode! Set flag to wake it up after first move. */\r
7339           setboardSpoiledMachineBlack = 1;\r
7340     }\r
7341 \r
7342     if (cps->sendICS) {\r
7343       sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");\r
7344       SendToProgram(buf, cps);\r
7345     }\r
7346     cps->maybeThinking = FALSE;\r
7347     cps->offeredDraw = 0;\r
7348     if (!appData.icsActive) {\r
7349         SendTimeControl(cps, movesPerSession, timeControl,\r
7350                         timeIncrement, appData.searchDepth,\r
7351                         searchTime);\r
7352     }\r
7353     if (appData.showThinking \r
7354         // [HGM] thinking: four options require thinking output to be sent\r
7355         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()\r
7356                                 ) {\r
7357         SendToProgram("post\n", cps);\r
7358     }\r
7359     SendToProgram("hard\n", cps);\r
7360     if (!appData.ponderNextMove) {\r
7361         /* Warning: "easy" is a toggle in GNU Chess, so don't send\r
7362            it without being sure what state we are in first.  "hard"\r
7363            is not a toggle, so that one is OK.\r
7364          */\r
7365         SendToProgram("easy\n", cps);\r
7366     }\r
7367     if (cps->usePing) {\r
7368       sprintf(buf, "ping %d\n", ++cps->lastPing);\r
7369       SendToProgram(buf, cps);\r
7370     }\r
7371     cps->initDone = TRUE;\r
7372 }   \r
7373 \r
7374 \r
7375 void\r
7376 StartChessProgram(cps)\r
7377      ChessProgramState *cps;\r
7378 {\r
7379     char buf[MSG_SIZ];\r
7380     int err;\r
7381 \r
7382     if (appData.noChessProgram) return;\r
7383     cps->initDone = FALSE;\r
7384 \r
7385     if (strcmp(cps->host, "localhost") == 0) {\r
7386         err = StartChildProcess(cps->program, cps->dir, &cps->pr);\r
7387     } else if (*appData.remoteShell == NULLCHAR) {\r
7388         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);\r
7389     } else {\r
7390         if (*appData.remoteUser == NULLCHAR) {\r
7391             sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,\r
7392                     cps->program);\r
7393         } else {\r
7394             sprintf(buf, "%s %s -l %s %s", appData.remoteShell,\r
7395                     cps->host, appData.remoteUser, cps->program);\r
7396         }\r
7397         err = StartChildProcess(buf, "", &cps->pr);\r
7398     }\r
7399     \r
7400     if (err != 0) {\r
7401         sprintf(buf, "Startup failure on '%s'", cps->program);\r
7402         DisplayFatalError(buf, err, 1);\r
7403         cps->pr = NoProc;\r
7404         cps->isr = NULL;\r
7405         return;\r
7406     }\r
7407     \r
7408     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);\r
7409     if (cps->protocolVersion > 1) {\r
7410       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);\r
7411       SendToProgram(buf, cps);\r
7412     } else {\r
7413       SendToProgram("xboard\n", cps);\r
7414     }\r
7415 }\r
7416 \r
7417 \r
7418 void\r
7419 TwoMachinesEventIfReady P((void))\r
7420 {\r
7421   if (first.lastPing != first.lastPong) {\r
7422     DisplayMessage("", "Waiting for first chess program");\r
7423     ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);\r
7424     return;\r
7425   }\r
7426   if (second.lastPing != second.lastPong) {\r
7427     DisplayMessage("", "Waiting for second chess program");\r
7428     ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);\r
7429     return;\r
7430   }\r
7431   ThawUI();\r
7432   TwoMachinesEvent();\r
7433 }\r
7434 \r
7435 void\r
7436 NextMatchGame P((void))\r
7437 {\r
7438     int index; /* [HGM] autoinc: step lod index during match */\r
7439     Reset(FALSE, TRUE);\r
7440     if (*appData.loadGameFile != NULLCHAR) {\r
7441         index = appData.loadGameIndex;\r
7442         if(index < 0) { // [HGM] autoinc\r
7443             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;\r
7444             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;\r
7445         } \r
7446         LoadGameFromFile(appData.loadGameFile,\r
7447                          index,\r
7448                          appData.loadGameFile, FALSE);\r
7449     } else if (*appData.loadPositionFile != NULLCHAR) {\r
7450         index = appData.loadPositionIndex;\r
7451         if(index < 0) { // [HGM] autoinc\r
7452             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;\r
7453             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;\r
7454         } \r
7455         LoadPositionFromFile(appData.loadPositionFile,\r
7456                              index,\r
7457                              appData.loadPositionFile);\r
7458     }\r
7459     TwoMachinesEventIfReady();\r
7460 }\r
7461 \r
7462 void UserAdjudicationEvent( int result )\r
7463 {\r
7464     ChessMove gameResult = GameIsDrawn;\r
7465 \r
7466     if( result > 0 ) {\r
7467         gameResult = WhiteWins;\r
7468     }\r
7469     else if( result < 0 ) {\r
7470         gameResult = BlackWins;\r
7471     }\r
7472 \r
7473     if( gameMode == TwoMachinesPlay ) {\r
7474         GameEnds( gameResult, "User adjudication", GE_XBOARD );\r
7475     }\r
7476 }\r
7477 \r
7478 \r
7479 void\r
7480 GameEnds(result, resultDetails, whosays)\r
7481      ChessMove result;\r
7482      char *resultDetails;\r
7483      int whosays;\r
7484 {\r
7485     GameMode nextGameMode;\r
7486     int isIcsGame;\r
7487     char buf[MSG_SIZ];\r
7488 \r
7489     if(endingGame) return; /* [HGM] crash: forbid recursion */\r
7490     endingGame = 1;\r
7491 \r
7492     if (appData.debugMode) {\r
7493       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",\r
7494               result, resultDetails ? resultDetails : "(null)", whosays);\r
7495     }\r
7496 \r
7497     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {\r
7498         /* If we are playing on ICS, the server decides when the\r
7499            game is over, but the engine can offer to draw, claim \r
7500            a draw, or resign. \r
7501          */\r
7502 #if ZIPPY\r
7503         if (appData.zippyPlay && first.initDone) {\r
7504             if (result == GameIsDrawn) {\r
7505                 /* In case draw still needs to be claimed */\r
7506                 SendToICS(ics_prefix);\r
7507                 SendToICS("draw\n");\r
7508             } else if (StrCaseStr(resultDetails, "resign")) {\r
7509                 SendToICS(ics_prefix);\r
7510                 SendToICS("resign\n");\r
7511             }\r
7512         }\r
7513 #endif\r
7514         endingGame = 0; /* [HGM] crash */\r
7515         return;\r
7516     }\r
7517 \r
7518     /* If we're loading the game from a file, stop */\r
7519     if (whosays == GE_FILE) {\r
7520       (void) StopLoadGameTimer();\r
7521       gameFileFP = NULL;\r
7522     }\r
7523 \r
7524     /* Cancel draw offers */\r
7525     first.offeredDraw = second.offeredDraw = 0;\r
7526 \r
7527     /* If this is an ICS game, only ICS can really say it's done;\r
7528        if not, anyone can. */\r
7529     isIcsGame = (gameMode == IcsPlayingWhite || \r
7530                  gameMode == IcsPlayingBlack || \r
7531                  gameMode == IcsObserving    || \r
7532                  gameMode == IcsExamining);\r
7533 \r
7534     if (!isIcsGame || whosays == GE_ICS) {\r
7535         /* OK -- not an ICS game, or ICS said it was done */\r
7536         StopClocks();\r
7537         if (!isIcsGame && !appData.noChessProgram) \r
7538           SetUserThinkingEnables();\r
7539     \r
7540         /* [HGM] if a machine claims the game end we verify this claim */\r
7541         if(gameMode == TwoMachinesPlay && appData.testClaims) {\r
7542             if(appData.testLegality && whosays >= GE_ENGINE1 ) {\r
7543                 char claimer;\r
7544 \r
7545                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */\r
7546                                             first.twoMachinesColor[0] :\r
7547                                             second.twoMachinesColor[0] ;\r
7548                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper) &&\r
7549                     (result == WhiteWins && claimer == 'w' ||\r
7550                      result == BlackWins && claimer == 'b'   ) ) {\r
7551                 if (appData.debugMode) {\r
7552                      fprintf(debugFP, "result=%d sp=%d move=%d\n",\r
7553                         result, epStatus[forwardMostMove], forwardMostMove);\r
7554                 }\r
7555                       /* [HGM] verify: engine mate claims accepted if they were flagged */\r
7556                       if(epStatus[forwardMostMove] != EP_CHECKMATE &&\r
7557                          result != (WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins)) {\r
7558                               sprintf(buf, "False win claim: '%s'", resultDetails);\r
7559                               result = claimer == 'w' ? BlackWins : WhiteWins;\r
7560                               resultDetails = buf;\r
7561                       }\r
7562                 } else\r
7563                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS\r
7564                     && (forwardMostMove <= backwardMostMove ||\r
7565                         epStatus[forwardMostMove-1] > EP_DRAWS ||\r
7566                         (claimer=='b')==(forwardMostMove&1))\r
7567                                                                                   ) {\r
7568                       /* [HGM] verify: draws that were not flagged are false claims */\r
7569                       sprintf(buf, "False draw claim: '%s'", resultDetails);\r
7570                       result = claimer == 'w' ? BlackWins : WhiteWins;\r
7571                       resultDetails = buf;\r
7572                 }\r
7573                 /* (Claiming a loss is accepted no questions asked!) */\r
7574             }\r
7575             /* [HGM] bare: don't allow bare King to win */\r
7576             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper) && result != GameIsDrawn)\r
7577             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);\r
7578                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {\r
7579                         int p = (int)boards[forwardMostMove][i][j] - color;\r
7580                         if(p >= 0 && p <= (int)WhiteKing) k++;\r
7581                 }\r
7582                 if (appData.debugMode) {\r
7583                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",\r
7584                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);\r
7585                 }\r
7586                 if(k <= 1) {\r
7587                         result = GameIsDrawn;\r
7588                         sprintf(buf, "%s but bare king", resultDetails);\r
7589                         resultDetails = buf;\r
7590                 }\r
7591             }\r
7592         }\r
7593 \r
7594 \r
7595         if(serverMoves != NULL && !loadFlag) { char c = '=';\r
7596             if(result==WhiteWins) c = '+';\r
7597             if(result==BlackWins) c = '-';\r
7598             if(resultDetails != NULL)\r
7599                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);\r
7600         }\r
7601         if (resultDetails != NULL) {\r
7602             gameInfo.result = result;\r
7603             gameInfo.resultDetails = StrSave(resultDetails);\r
7604 \r
7605             /* display last move only if game was not loaded from file */\r
7606             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))\r
7607                 DisplayMove(currentMove - 1);\r
7608     \r
7609             if (forwardMostMove != 0) {\r
7610                 if (gameMode != PlayFromGameFile && gameMode != EditGame) {\r
7611                     if (*appData.saveGameFile != NULLCHAR) {\r
7612                         SaveGameToFile(appData.saveGameFile, TRUE);\r
7613                     } else if (appData.autoSaveGames) {\r
7614                         AutoSaveGame();\r
7615                     }\r
7616                     if (*appData.savePositionFile != NULLCHAR) {\r
7617                         SavePositionToFile(appData.savePositionFile);\r
7618                     }\r
7619                 }\r
7620             }\r
7621 \r
7622             /* Tell program how game ended in case it is learning */\r
7623             /* [HGM] Moved this to after saving the PGN, just in case */\r
7624             /* engine died and we got here through time loss. In that */\r
7625             /* case we will get a fatal error writing the pipe, which */\r
7626             /* would otherwise lose us the PGN.                       */\r
7627             /* [HGM] crash: not needed anymore, but doesn't hurt;     */\r
7628             /* output during GameEnds should never be fatal anymore   */\r
7629             if (gameMode == MachinePlaysWhite ||\r
7630                 gameMode == MachinePlaysBlack ||\r
7631                 gameMode == TwoMachinesPlay ||\r
7632                 gameMode == IcsPlayingWhite ||\r
7633                 gameMode == IcsPlayingBlack ||\r
7634                 gameMode == BeginningOfGame) {\r
7635                 char buf[MSG_SIZ];\r
7636                 sprintf(buf, "result %s {%s}\n", PGNResult(result),\r
7637                         resultDetails);\r
7638                 if (first.pr != NoProc) {\r
7639                     SendToProgram(buf, &first);\r
7640                 }\r
7641                 if (second.pr != NoProc &&\r
7642                     gameMode == TwoMachinesPlay) {\r
7643                     SendToProgram(buf, &second);\r
7644                 }\r
7645             }\r
7646         }\r
7647 \r
7648         if (appData.icsActive) {\r
7649             if (appData.quietPlay &&\r
7650                 (gameMode == IcsPlayingWhite ||\r
7651                  gameMode == IcsPlayingBlack)) {\r
7652                 SendToICS(ics_prefix);\r
7653                 SendToICS("set shout 1\n");\r
7654             }\r
7655             nextGameMode = IcsIdle;\r
7656             ics_user_moved = FALSE;\r
7657             /* clean up premove.  It's ugly when the game has ended and the\r
7658              * premove highlights are still on the board.\r
7659              */\r
7660             if (gotPremove) {\r
7661               gotPremove = FALSE;\r
7662               ClearPremoveHighlights();\r
7663               DrawPosition(FALSE, boards[currentMove]);\r
7664             }\r
7665             if (whosays == GE_ICS) {\r
7666                 switch (result) {\r
7667                 case WhiteWins:\r
7668                     if (gameMode == IcsPlayingWhite)\r
7669                         PlayIcsWinSound();\r
7670                     else if(gameMode == IcsPlayingBlack)\r
7671                         PlayIcsLossSound();\r
7672                     break;\r
7673                 case BlackWins:\r
7674                     if (gameMode == IcsPlayingBlack)\r
7675                         PlayIcsWinSound();\r
7676                     else if(gameMode == IcsPlayingWhite)\r
7677                         PlayIcsLossSound();\r
7678                     break;\r
7679                 case GameIsDrawn:\r
7680                     PlayIcsDrawSound();\r
7681                     break;\r
7682                 default:\r
7683                     PlayIcsUnfinishedSound();\r
7684                 }\r
7685             }\r
7686         } else if (gameMode == EditGame ||\r
7687                    gameMode == PlayFromGameFile || \r
7688                    gameMode == AnalyzeMode || \r
7689                    gameMode == AnalyzeFile) {\r
7690             nextGameMode = gameMode;\r
7691         } else {\r
7692             nextGameMode = EndOfGame;\r
7693         }\r
7694         pausing = FALSE;\r
7695         ModeHighlight();\r
7696     } else {\r
7697         nextGameMode = gameMode;\r
7698     }\r
7699 \r
7700     if (appData.noChessProgram) {\r
7701         gameMode = nextGameMode;\r
7702         ModeHighlight();\r
7703         endingGame = 0; /* [HGM] crash */\r
7704         return;\r
7705     }\r
7706 \r
7707     if (first.reuse) {\r
7708         /* Put first chess program into idle state */\r
7709         if (first.pr != NoProc &&\r
7710             (gameMode == MachinePlaysWhite ||\r
7711              gameMode == MachinePlaysBlack ||\r
7712              gameMode == TwoMachinesPlay ||\r
7713              gameMode == IcsPlayingWhite ||\r
7714              gameMode == IcsPlayingBlack ||\r
7715              gameMode == BeginningOfGame)) {\r
7716             SendToProgram("force\n", &first);\r
7717             if (first.usePing) {\r
7718               char buf[MSG_SIZ];\r
7719               sprintf(buf, "ping %d\n", ++first.lastPing);\r
7720               SendToProgram(buf, &first);\r
7721             }\r
7722         }\r
7723     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
7724         /* Kill off first chess program */\r
7725         if (first.isr != NULL)\r
7726           RemoveInputSource(first.isr);\r
7727         first.isr = NULL;\r
7728     \r
7729         if (first.pr != NoProc) {\r
7730             ExitAnalyzeMode();\r
7731             DoSleep( appData.delayBeforeQuit );\r
7732             SendToProgram("quit\n", &first);\r
7733             DoSleep( appData.delayAfterQuit );\r
7734             DestroyChildProcess(first.pr, first.useSigterm);\r
7735         }\r
7736         first.pr = NoProc;\r
7737     }\r
7738     if (second.reuse) {\r
7739         /* Put second chess program into idle state */\r
7740         if (second.pr != NoProc &&\r
7741             gameMode == TwoMachinesPlay) {\r
7742             SendToProgram("force\n", &second);\r
7743             if (second.usePing) {\r
7744               char buf[MSG_SIZ];\r
7745               sprintf(buf, "ping %d\n", ++second.lastPing);\r
7746               SendToProgram(buf, &second);\r
7747             }\r
7748         }\r
7749     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
7750         /* Kill off second chess program */\r
7751         if (second.isr != NULL)\r
7752           RemoveInputSource(second.isr);\r
7753         second.isr = NULL;\r
7754     \r
7755         if (second.pr != NoProc) {\r
7756             DoSleep( appData.delayBeforeQuit );\r
7757             SendToProgram("quit\n", &second);\r
7758             DoSleep( appData.delayAfterQuit );\r
7759             DestroyChildProcess(second.pr, second.useSigterm);\r
7760         }\r
7761         second.pr = NoProc;\r
7762     }\r
7763 \r
7764     if (matchMode && gameMode == TwoMachinesPlay) {\r
7765         switch (result) {\r
7766         case WhiteWins:\r
7767           if (first.twoMachinesColor[0] == 'w') {\r
7768             first.matchWins++;\r
7769           } else {\r
7770             second.matchWins++;\r
7771           }\r
7772           break;\r
7773         case BlackWins:\r
7774           if (first.twoMachinesColor[0] == 'b') {\r
7775             first.matchWins++;\r
7776           } else {\r
7777             second.matchWins++;\r
7778           }\r
7779           break;\r
7780         default:\r
7781           break;\r
7782         }\r
7783         if (matchGame < appData.matchGames) {\r
7784             char *tmp;\r
7785             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */\r
7786                 tmp = first.twoMachinesColor;\r
7787                 first.twoMachinesColor = second.twoMachinesColor;\r
7788                 second.twoMachinesColor = tmp;\r
7789             }\r
7790             gameMode = nextGameMode;\r
7791             matchGame++;\r
7792             if(appData.matchPause>10000 || appData.matchPause<10)\r
7793                 appData.matchPause = 10000; /* [HGM] make pause adjustable */\r
7794             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);\r
7795             endingGame = 0; /* [HGM] crash */\r
7796             return;\r
7797         } else {\r
7798             char buf[MSG_SIZ];\r
7799             gameMode = nextGameMode;\r
7800             sprintf(buf, "Match %s vs. %s: final score %d-%d-%d",\r
7801                     first.tidy, second.tidy,\r
7802                     first.matchWins, second.matchWins,\r
7803                     appData.matchGames - (first.matchWins + second.matchWins));\r
7804             DisplayFatalError(buf, 0, 0);\r
7805         }\r
7806     }\r
7807     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&\r
7808         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))\r
7809       ExitAnalyzeMode();\r
7810     gameMode = nextGameMode;\r
7811     ModeHighlight();\r
7812     endingGame = 0;  /* [HGM] crash */\r
7813 }\r
7814 \r
7815 /* Assumes program was just initialized (initString sent).\r
7816    Leaves program in force mode. */\r
7817 void\r
7818 FeedMovesToProgram(cps, upto) \r
7819      ChessProgramState *cps;\r
7820      int upto;\r
7821 {\r
7822     int i;\r
7823     \r
7824     if (appData.debugMode)\r
7825       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",\r
7826               startedFromSetupPosition ? "position and " : "",\r
7827               backwardMostMove, upto, cps->which);\r
7828     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];\r
7829         // [HGM] variantswitch: make engine aware of new variant\r
7830         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)\r
7831                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!\r
7832         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));\r
7833         SendToProgram(buf, cps);\r
7834         currentlyInitializedVariant = gameInfo.variant;\r
7835     }\r
7836     SendToProgram("force\n", cps);\r
7837     if (startedFromSetupPosition) {\r
7838         SendBoard(cps, backwardMostMove);\r
7839     if (appData.debugMode) {\r
7840         fprintf(debugFP, "feedMoves\n");\r
7841     }\r
7842     }\r
7843     for (i = backwardMostMove; i < upto; i++) {\r
7844         SendMoveToProgram(i, cps);\r
7845     }\r
7846 }\r
7847 \r
7848 \r
7849 void\r
7850 ResurrectChessProgram()\r
7851 {\r
7852      /* The chess program may have exited.\r
7853         If so, restart it and feed it all the moves made so far. */\r
7854 \r
7855     if (appData.noChessProgram || first.pr != NoProc) return;\r
7856     \r
7857     StartChessProgram(&first);\r
7858     InitChessProgram(&first, FALSE);\r
7859     FeedMovesToProgram(&first, currentMove);\r
7860 \r
7861     if (!first.sendTime) {\r
7862         /* can't tell gnuchess what its clock should read,\r
7863            so we bow to its notion. */\r
7864         ResetClocks();\r
7865         timeRemaining[0][currentMove] = whiteTimeRemaining;\r
7866         timeRemaining[1][currentMove] = blackTimeRemaining;\r
7867     }\r
7868 \r
7869     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&\r
7870         first.analysisSupport) {\r
7871       SendToProgram("analyze\n", &first);\r
7872       first.analyzing = TRUE;\r
7873     }\r
7874 }\r
7875 \r
7876 /*\r
7877  * Button procedures\r
7878  */\r
7879 void\r
7880 Reset(redraw, init)\r
7881      int redraw, init;\r
7882 {\r
7883     int i;\r
7884 \r
7885     if (appData.debugMode) {\r
7886         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",\r
7887                 redraw, init, gameMode);\r
7888     }\r
7889     pausing = pauseExamInvalid = FALSE;\r
7890     startedFromSetupPosition = blackPlaysFirst = FALSE;\r
7891     firstMove = TRUE;\r
7892     whiteFlag = blackFlag = FALSE;\r
7893     userOfferedDraw = FALSE;\r
7894     hintRequested = bookRequested = FALSE;\r
7895     first.maybeThinking = FALSE;\r
7896     second.maybeThinking = FALSE;\r
7897     first.bookSuspend = FALSE; // [HGM] book\r
7898     second.bookSuspend = FALSE;\r
7899     thinkOutput[0] = NULLCHAR;\r
7900     lastHint[0] = NULLCHAR;\r
7901     ClearGameInfo(&gameInfo);\r
7902     gameInfo.variant = StringToVariant(appData.variant);\r
7903     ics_user_moved = ics_clock_paused = FALSE;\r
7904     ics_getting_history = H_FALSE;\r
7905     ics_gamenum = -1;\r
7906     white_holding[0] = black_holding[0] = NULLCHAR;\r
7907     ClearProgramStats();\r
7908     \r
7909     ResetFrontEnd();\r
7910     ClearHighlights();\r
7911     flipView = appData.flipView;\r
7912     ClearPremoveHighlights();\r
7913     gotPremove = FALSE;\r
7914     alarmSounded = FALSE;\r
7915 \r
7916     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
7917     if(appData.serverMovesName != NULL) {\r
7918         /* [HGM] prepare to make moves file for broadcasting */\r
7919         clock_t t = clock();\r
7920         if(serverMoves != NULL) fclose(serverMoves);\r
7921         serverMoves = fopen(appData.serverMovesName, "r");\r
7922         if(serverMoves != NULL) {\r
7923             fclose(serverMoves);\r
7924             /* delay 15 sec before overwriting, so all clients can see end */\r
7925             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);\r
7926         }\r
7927         serverMoves = fopen(appData.serverMovesName, "w");\r
7928     }\r
7929 \r
7930     ExitAnalyzeMode();\r
7931     gameMode = BeginningOfGame;\r
7932     ModeHighlight();\r
7933     if(appData.icsActive) gameInfo.variant = VariantNormal;\r
7934     InitPosition(redraw);\r
7935     for (i = 0; i < MAX_MOVES; i++) {\r
7936         if (commentList[i] != NULL) {\r
7937             free(commentList[i]);\r
7938             commentList[i] = NULL;\r
7939         }\r
7940     }\r
7941     ResetClocks();\r
7942     timeRemaining[0][0] = whiteTimeRemaining;\r
7943     timeRemaining[1][0] = blackTimeRemaining;\r
7944     if (first.pr == NULL) {\r
7945         StartChessProgram(&first);\r
7946     }\r
7947     if (init) {\r
7948             InitChessProgram(&first, startedFromSetupPosition);\r
7949     }\r
7950     DisplayTitle("");\r
7951     DisplayMessage("", "");\r
7952     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
7953 }\r
7954 \r
7955 void\r
7956 AutoPlayGameLoop()\r
7957 {\r
7958     for (;;) {\r
7959         if (!AutoPlayOneMove())\r
7960           return;\r
7961         if (matchMode || appData.timeDelay == 0)\r
7962           continue;\r
7963         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)\r
7964           return;\r
7965         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));\r
7966         break;\r
7967     }\r
7968 }\r
7969 \r
7970 \r
7971 int\r
7972 AutoPlayOneMove()\r
7973 {\r
7974     int fromX, fromY, toX, toY;\r
7975 \r
7976     if (appData.debugMode) {\r
7977       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);\r
7978     }\r
7979 \r
7980     if (gameMode != PlayFromGameFile)\r
7981       return FALSE;\r
7982 \r
7983     if (currentMove >= forwardMostMove) {\r
7984       gameMode = EditGame;\r
7985       ModeHighlight();\r
7986 \r
7987       /* [AS] Clear current move marker at the end of a game */\r
7988       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */\r
7989 \r
7990       return FALSE;\r
7991     }\r
7992     \r
7993     toX = moveList[currentMove][2] - AAA;\r
7994     toY = moveList[currentMove][3] - ONE;\r
7995 \r
7996     if (moveList[currentMove][1] == '@') {\r
7997         if (appData.highlightLastMove) {\r
7998             SetHighlights(-1, -1, toX, toY);\r
7999         }\r
8000     } else {\r
8001         fromX = moveList[currentMove][0] - AAA;\r
8002         fromY = moveList[currentMove][1] - ONE;\r
8003 \r
8004         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */\r
8005 \r
8006         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
8007 \r
8008         if (appData.highlightLastMove) {\r
8009             SetHighlights(fromX, fromY, toX, toY);\r
8010         }\r
8011     }\r
8012     DisplayMove(currentMove);\r
8013     SendMoveToProgram(currentMove++, &first);\r
8014     DisplayBothClocks();\r
8015     DrawPosition(FALSE, boards[currentMove]);\r
8016     // [HGM] PV info: always display, routine tests if empty\r
8017     DisplayComment(currentMove - 1, commentList[currentMove]);\r
8018     return TRUE;\r
8019 }\r
8020 \r
8021 \r
8022 int\r
8023 LoadGameOneMove(readAhead)\r
8024      ChessMove readAhead;\r
8025 {\r
8026     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;\r
8027     char promoChar = NULLCHAR;\r
8028     ChessMove moveType;\r
8029     char move[MSG_SIZ];\r
8030     char *p, *q;\r
8031     \r
8032     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && \r
8033         gameMode != AnalyzeMode && gameMode != Training) {\r
8034         gameFileFP = NULL;\r
8035         return FALSE;\r
8036     }\r
8037     \r
8038     yyboardindex = forwardMostMove;\r
8039     if (readAhead != (ChessMove)0) {\r
8040       moveType = readAhead;\r
8041     } else {\r
8042       if (gameFileFP == NULL)\r
8043           return FALSE;\r
8044       moveType = (ChessMove) yylex();\r
8045     }\r
8046     \r
8047     done = FALSE;\r
8048     switch (moveType) {\r
8049       case Comment:\r
8050         if (appData.debugMode) \r
8051           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
8052         p = yy_text;\r
8053         if (*p == '{' || *p == '[' || *p == '(') {\r
8054             p[strlen(p) - 1] = NULLCHAR;\r
8055             p++;\r
8056         }\r
8057 \r
8058         /* append the comment but don't display it */\r
8059         while (*p == '\n') p++;\r
8060         AppendComment(currentMove, p);\r
8061         return TRUE;\r
8062 \r
8063       case WhiteCapturesEnPassant:\r
8064       case BlackCapturesEnPassant:\r
8065       case WhitePromotionChancellor:\r
8066       case BlackPromotionChancellor:\r
8067       case WhitePromotionArchbishop:\r
8068       case BlackPromotionArchbishop:\r
8069       case WhitePromotionCentaur:\r
8070       case BlackPromotionCentaur:\r
8071       case WhitePromotionQueen:\r
8072       case BlackPromotionQueen:\r
8073       case WhitePromotionRook:\r
8074       case BlackPromotionRook:\r
8075       case WhitePromotionBishop:\r
8076       case BlackPromotionBishop:\r
8077       case WhitePromotionKnight:\r
8078       case BlackPromotionKnight:\r
8079       case WhitePromotionKing:\r
8080       case BlackPromotionKing:\r
8081       case NormalMove:\r
8082       case WhiteKingSideCastle:\r
8083       case WhiteQueenSideCastle:\r
8084       case BlackKingSideCastle:\r
8085       case BlackQueenSideCastle:\r
8086       case WhiteKingSideCastleWild:\r
8087       case WhiteQueenSideCastleWild:\r
8088       case BlackKingSideCastleWild:\r
8089       case BlackQueenSideCastleWild:\r
8090       /* PUSH Fabien */\r
8091       case WhiteHSideCastleFR:\r
8092       case WhiteASideCastleFR:\r
8093       case BlackHSideCastleFR:\r
8094       case BlackASideCastleFR:\r
8095       /* POP Fabien */\r
8096         if (appData.debugMode)\r
8097           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
8098         fromX = currentMoveString[0] - AAA;\r
8099         fromY = currentMoveString[1] - ONE;\r
8100         toX = currentMoveString[2] - AAA;\r
8101         toY = currentMoveString[3] - ONE;\r
8102         promoChar = currentMoveString[4];\r
8103         break;\r
8104 \r
8105       case WhiteDrop:\r
8106       case BlackDrop:\r
8107         if (appData.debugMode)\r
8108           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
8109         fromX = moveType == WhiteDrop ?\r
8110           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
8111         (int) CharToPiece(ToLower(currentMoveString[0]));\r
8112         fromY = DROP_RANK;\r
8113         toX = currentMoveString[2] - AAA;\r
8114         toY = currentMoveString[3] - ONE;\r
8115         break;\r
8116 \r
8117       case WhiteWins:\r
8118       case BlackWins:\r
8119       case GameIsDrawn:\r
8120       case GameUnfinished:\r
8121         if (appData.debugMode)\r
8122           fprintf(debugFP, "Parsed game end: %s\n", yy_text);\r
8123         p = strchr(yy_text, '{');\r
8124         if (p == NULL) p = strchr(yy_text, '(');\r
8125         if (p == NULL) {\r
8126             p = yy_text;\r
8127             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
8128         } else {\r
8129             q = strchr(p, *p == '{' ? '}' : ')');\r
8130             if (q != NULL) *q = NULLCHAR;\r
8131             p++;\r
8132         }\r
8133         GameEnds(moveType, p, GE_FILE);\r
8134         done = TRUE;\r
8135         if (cmailMsgLoaded) {\r
8136             ClearHighlights();\r
8137             flipView = WhiteOnMove(currentMove);\r
8138             if (moveType == GameUnfinished) flipView = !flipView;\r
8139             if (appData.debugMode)\r
8140               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;\r
8141         }\r
8142         break;\r
8143 \r
8144       case (ChessMove) 0:       /* end of file */\r
8145         if (appData.debugMode)\r
8146           fprintf(debugFP, "Parser hit end of file\n");\r
8147         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8148                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8149           case MT_NONE:\r
8150           case MT_CHECK:\r
8151             break;\r
8152           case MT_CHECKMATE:\r
8153             if (WhiteOnMove(currentMove)) {\r
8154                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
8155             } else {\r
8156                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
8157             }\r
8158             break;\r
8159           case MT_STALEMATE:\r
8160             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
8161             break;\r
8162         }\r
8163         done = TRUE;\r
8164         break;\r
8165 \r
8166       case MoveNumberOne:\r
8167         if (lastLoadGameStart == GNUChessGame) {\r
8168             /* GNUChessGames have numbers, but they aren't move numbers */\r
8169             if (appData.debugMode)\r
8170               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
8171                       yy_text, (int) moveType);\r
8172             return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
8173         }\r
8174         /* else fall thru */\r
8175 \r
8176       case XBoardGame:\r
8177       case GNUChessGame:\r
8178       case PGNTag:\r
8179         /* Reached start of next game in file */\r
8180         if (appData.debugMode)\r
8181           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);\r
8182         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8183                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8184           case MT_NONE:\r
8185           case MT_CHECK:\r
8186             break;\r
8187           case MT_CHECKMATE:\r
8188             if (WhiteOnMove(currentMove)) {\r
8189                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
8190             } else {\r
8191                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
8192             }\r
8193             break;\r
8194           case MT_STALEMATE:\r
8195             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
8196             break;\r
8197         }\r
8198         done = TRUE;\r
8199         break;\r
8200 \r
8201       case PositionDiagram:     /* should not happen; ignore */\r
8202       case ElapsedTime:         /* ignore */\r
8203       case NAG:                 /* ignore */\r
8204         if (appData.debugMode)\r
8205           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
8206                   yy_text, (int) moveType);\r
8207         return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
8208 \r
8209       case IllegalMove:\r
8210         if (appData.testLegality) {\r
8211             if (appData.debugMode)\r
8212               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);\r
8213             sprintf(move, "Illegal move: %d.%s%s",\r
8214                     (forwardMostMove / 2) + 1,\r
8215                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8216             DisplayError(move, 0);\r
8217             done = TRUE;\r
8218         } else {\r
8219             if (appData.debugMode)\r
8220               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",\r
8221                       yy_text, currentMoveString);\r
8222             fromX = currentMoveString[0] - AAA;\r
8223             fromY = currentMoveString[1] - ONE;\r
8224             toX = currentMoveString[2] - AAA;\r
8225             toY = currentMoveString[3] - ONE;\r
8226             promoChar = currentMoveString[4];\r
8227         }\r
8228         break;\r
8229 \r
8230       case AmbiguousMove:\r
8231         if (appData.debugMode)\r
8232           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);\r
8233         sprintf(move, "Ambiguous move: %d.%s%s",\r
8234                 (forwardMostMove / 2) + 1,\r
8235                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8236         DisplayError(move, 0);\r
8237         done = TRUE;\r
8238         break;\r
8239 \r
8240       default:\r
8241       case ImpossibleMove:\r
8242         if (appData.debugMode)\r
8243           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);\r
8244         sprintf(move, "Illegal move: %d.%s%s",\r
8245                 (forwardMostMove / 2) + 1,\r
8246                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8247         DisplayError(move, 0);\r
8248         done = TRUE;\r
8249         break;\r
8250     }\r
8251 \r
8252     if (done) {\r
8253         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {\r
8254             DrawPosition(FALSE, boards[currentMove]);\r
8255             DisplayBothClocks();\r
8256             if (!appData.matchMode) // [HGM] PV info: routine tests if empty\r
8257               DisplayComment(currentMove - 1, commentList[currentMove]);\r
8258         }\r
8259         (void) StopLoadGameTimer();\r
8260         gameFileFP = NULL;\r
8261         cmailOldMove = forwardMostMove;\r
8262         return FALSE;\r
8263     } else {\r
8264         /* currentMoveString is set as a side-effect of yylex */\r
8265         strcat(currentMoveString, "\n");\r
8266         strcpy(moveList[forwardMostMove], currentMoveString);\r
8267         \r
8268         thinkOutput[0] = NULLCHAR;\r
8269         MakeMove(fromX, fromY, toX, toY, promoChar);\r
8270         currentMove = forwardMostMove;\r
8271         return TRUE;\r
8272     }\r
8273 }\r
8274 \r
8275 /* Load the nth game from the given file */\r
8276 int\r
8277 LoadGameFromFile(filename, n, title, useList)\r
8278      char *filename;\r
8279      int n;\r
8280      char *title;\r
8281      /*Boolean*/ int useList;\r
8282 {\r
8283     FILE *f;\r
8284     char buf[MSG_SIZ];\r
8285 \r
8286     if (strcmp(filename, "-") == 0) {\r
8287         f = stdin;\r
8288         title = "stdin";\r
8289     } else {\r
8290         f = fopen(filename, "rb");\r
8291         if (f == NULL) {\r
8292             sprintf(buf, "Can't open \"%s\"", filename);\r
8293             DisplayError(buf, errno);\r
8294             return FALSE;\r
8295         }\r
8296     }\r
8297     if (fseek(f, 0, 0) == -1) {\r
8298         /* f is not seekable; probably a pipe */\r
8299         useList = FALSE;\r
8300     }\r
8301     if (useList && n == 0) {\r
8302         int error = GameListBuild(f);\r
8303         if (error) {\r
8304             DisplayError("Cannot build game list", error);\r
8305         } else if (!ListEmpty(&gameList) &&\r
8306                    ((ListGame *) gameList.tailPred)->number > 1) {\r
8307             GameListPopUp(f, title);\r
8308             return TRUE;\r
8309         }\r
8310         GameListDestroy();\r
8311         n = 1;\r
8312     }\r
8313     if (n == 0) n = 1;\r
8314     return LoadGame(f, n, title, FALSE);\r
8315 }\r
8316 \r
8317 \r
8318 void\r
8319 MakeRegisteredMove()\r
8320 {\r
8321     int fromX, fromY, toX, toY;\r
8322     char promoChar;\r
8323     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8324         switch (cmailMoveType[lastLoadGameNumber - 1]) {\r
8325           case CMAIL_MOVE:\r
8326           case CMAIL_DRAW:\r
8327             if (appData.debugMode)\r
8328               fprintf(debugFP, "Restoring %s for game %d\n",\r
8329                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
8330     \r
8331             thinkOutput[0] = NULLCHAR;\r
8332             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);\r
8333             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;\r
8334             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;\r
8335             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;\r
8336             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;\r
8337             promoChar = cmailMove[lastLoadGameNumber - 1][4];\r
8338             MakeMove(fromX, fromY, toX, toY, promoChar);\r
8339             ShowMove(fromX, fromY, toX, toY);\r
8340               \r
8341             switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8342                              EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8343               case MT_NONE:\r
8344               case MT_CHECK:\r
8345                 break;\r
8346                 \r
8347               case MT_CHECKMATE:\r
8348                 if (WhiteOnMove(currentMove)) {\r
8349                     GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
8350                 } else {\r
8351                     GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
8352                 }\r
8353                 break;\r
8354                 \r
8355               case MT_STALEMATE:\r
8356                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
8357                 break;\r
8358             }\r
8359 \r
8360             break;\r
8361             \r
8362           case CMAIL_RESIGN:\r
8363             if (WhiteOnMove(currentMove)) {\r
8364                 GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
8365             } else {\r
8366                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
8367             }\r
8368             break;\r
8369             \r
8370           case CMAIL_ACCEPT:\r
8371             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
8372             break;\r
8373               \r
8374           default:\r
8375             break;\r
8376         }\r
8377     }\r
8378 \r
8379     return;\r
8380 }\r
8381 \r
8382 /* Wrapper around LoadGame for use when a Cmail message is loaded */\r
8383 int\r
8384 CmailLoadGame(f, gameNumber, title, useList)\r
8385      FILE *f;\r
8386      int gameNumber;\r
8387      char *title;\r
8388      int useList;\r
8389 {\r
8390     int retVal;\r
8391 \r
8392     if (gameNumber > nCmailGames) {\r
8393         DisplayError("No more games in this message", 0);\r
8394         return FALSE;\r
8395     }\r
8396     if (f == lastLoadGameFP) {\r
8397         int offset = gameNumber - lastLoadGameNumber;\r
8398         if (offset == 0) {\r
8399             cmailMsg[0] = NULLCHAR;\r
8400             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8401                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
8402                 nCmailMovesRegistered--;\r
8403             }\r
8404             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
8405             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {\r
8406                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;\r
8407             }\r
8408         } else {\r
8409             if (! RegisterMove()) return FALSE;\r
8410         }\r
8411     }\r
8412 \r
8413     retVal = LoadGame(f, gameNumber, title, useList);\r
8414 \r
8415     /* Make move registered during previous look at this game, if any */\r
8416     MakeRegisteredMove();\r
8417 \r
8418     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {\r
8419         commentList[currentMove]\r
8420           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);\r
8421         DisplayComment(currentMove - 1, commentList[currentMove]);\r
8422     }\r
8423 \r
8424     return retVal;\r
8425 }\r
8426 \r
8427 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */\r
8428 int\r
8429 ReloadGame(offset)\r
8430      int offset;\r
8431 {\r
8432     int gameNumber = lastLoadGameNumber + offset;\r
8433     if (lastLoadGameFP == NULL) {\r
8434         DisplayError("No game has been loaded yet", 0);\r
8435         return FALSE;\r
8436     }\r
8437     if (gameNumber <= 0) {\r
8438         DisplayError("Can't back up any further", 0);\r
8439         return FALSE;\r
8440     }\r
8441     if (cmailMsgLoaded) {\r
8442         return CmailLoadGame(lastLoadGameFP, gameNumber,\r
8443                              lastLoadGameTitle, lastLoadGameUseList);\r
8444     } else {\r
8445         return LoadGame(lastLoadGameFP, gameNumber,\r
8446                         lastLoadGameTitle, lastLoadGameUseList);\r
8447     }\r
8448 }\r
8449 \r
8450 \r
8451 \r
8452 /* Load the nth game from open file f */\r
8453 int\r
8454 LoadGame(f, gameNumber, title, useList)\r
8455      FILE *f;\r
8456      int gameNumber;\r
8457      char *title;\r
8458      int useList;\r
8459 {\r
8460     ChessMove cm;\r
8461     char buf[MSG_SIZ];\r
8462     int gn = gameNumber;\r
8463     ListGame *lg = NULL;\r
8464     int numPGNTags = 0;\r
8465     int err;\r
8466     GameMode oldGameMode;\r
8467     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */\r
8468 \r
8469     if (appData.debugMode) \r
8470         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);\r
8471 \r
8472     if (gameMode == Training )\r
8473         SetTrainingModeOff();\r
8474 \r
8475     oldGameMode = gameMode;\r
8476     if (gameMode != BeginningOfGame) {\r
8477       Reset(FALSE, TRUE);\r
8478     }\r
8479 \r
8480     gameFileFP = f;\r
8481     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {\r
8482         fclose(lastLoadGameFP);\r
8483     }\r
8484 \r
8485     if (useList) {\r
8486         lg = (ListGame *) ListElem(&gameList, gameNumber-1);\r
8487         \r
8488         if (lg) {\r
8489             fseek(f, lg->offset, 0);\r
8490             GameListHighlight(gameNumber);\r
8491             gn = 1;\r
8492         }\r
8493         else {\r
8494             DisplayError("Game number out of range", 0);\r
8495             return FALSE;\r
8496         }\r
8497     } else {\r
8498         GameListDestroy();\r
8499         if (fseek(f, 0, 0) == -1) {\r
8500             if (f == lastLoadGameFP ?\r
8501                 gameNumber == lastLoadGameNumber + 1 :\r
8502                 gameNumber == 1) {\r
8503                 gn = 1;\r
8504             } else {\r
8505                 DisplayError("Can't seek on game file", 0);\r
8506                 return FALSE;\r
8507             }\r
8508         }\r
8509     }\r
8510     lastLoadGameFP = f;\r
8511     lastLoadGameNumber = gameNumber;\r
8512     strcpy(lastLoadGameTitle, title);\r
8513     lastLoadGameUseList = useList;\r
8514 \r
8515     yynewfile(f);\r
8516 \r
8517     if (lg && lg->gameInfo.white && lg->gameInfo.black) {\r
8518         sprintf(buf, "%s vs. %s", lg->gameInfo.white,\r
8519                 lg->gameInfo.black);\r
8520             DisplayTitle(buf);\r
8521     } else if (*title != NULLCHAR) {\r
8522         if (gameNumber > 1) {\r
8523             sprintf(buf, "%s %d", title, gameNumber);\r
8524             DisplayTitle(buf);\r
8525         } else {\r
8526             DisplayTitle(title);\r
8527         }\r
8528     }\r
8529 \r
8530     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {\r
8531         gameMode = PlayFromGameFile;\r
8532         ModeHighlight();\r
8533     }\r
8534 \r
8535     currentMove = forwardMostMove = backwardMostMove = 0;\r
8536     CopyBoard(boards[0], initialPosition);\r
8537     StopClocks();\r
8538 \r
8539     /*\r
8540      * Skip the first gn-1 games in the file.\r
8541      * Also skip over anything that precedes an identifiable \r
8542      * start of game marker, to avoid being confused by \r
8543      * garbage at the start of the file.  Currently \r
8544      * recognized start of game markers are the move number "1",\r
8545      * the pattern "gnuchess .* game", the pattern\r
8546      * "^[#;%] [^ ]* game file", and a PGN tag block.  \r
8547      * A game that starts with one of the latter two patterns\r
8548      * will also have a move number 1, possibly\r
8549      * following a position diagram.\r
8550      * 5-4-02: Let's try being more lenient and allowing a game to\r
8551      * start with an unnumbered move.  Does that break anything?\r
8552      */\r
8553     cm = lastLoadGameStart = (ChessMove) 0;\r
8554     while (gn > 0) {\r
8555         yyboardindex = forwardMostMove;\r
8556         cm = (ChessMove) yylex();\r
8557         switch (cm) {\r
8558           case (ChessMove) 0:\r
8559             if (cmailMsgLoaded) {\r
8560                 nCmailGames = CMAIL_MAX_GAMES - gn;\r
8561             } else {\r
8562                 Reset(TRUE, TRUE);\r
8563                 DisplayError("Game not found in file", 0);\r
8564             }\r
8565             return FALSE;\r
8566 \r
8567           case GNUChessGame:\r
8568           case XBoardGame:\r
8569             gn--;\r
8570             lastLoadGameStart = cm;\r
8571             break;\r
8572             \r
8573           case MoveNumberOne:\r
8574             switch (lastLoadGameStart) {\r
8575               case GNUChessGame:\r
8576               case XBoardGame:\r
8577               case PGNTag:\r
8578                 break;\r
8579               case MoveNumberOne:\r
8580               case (ChessMove) 0:\r
8581                 gn--;           /* count this game */\r
8582                 lastLoadGameStart = cm;\r
8583                 break;\r
8584               default:\r
8585                 /* impossible */\r
8586                 break;\r
8587             }\r
8588             break;\r
8589 \r
8590           case PGNTag:\r
8591             switch (lastLoadGameStart) {\r
8592               case GNUChessGame:\r
8593               case PGNTag:\r
8594               case MoveNumberOne:\r
8595               case (ChessMove) 0:\r
8596                 gn--;           /* count this game */\r
8597                 lastLoadGameStart = cm;\r
8598                 break;\r
8599               case XBoardGame:\r
8600                 lastLoadGameStart = cm; /* game counted already */\r
8601                 break;\r
8602               default:\r
8603                 /* impossible */\r
8604                 break;\r
8605             }\r
8606             if (gn > 0) {\r
8607                 do {\r
8608                     yyboardindex = forwardMostMove;\r
8609                     cm = (ChessMove) yylex();\r
8610                 } while (cm == PGNTag || cm == Comment);\r
8611             }\r
8612             break;\r
8613 \r
8614           case WhiteWins:\r
8615           case BlackWins:\r
8616           case GameIsDrawn:\r
8617             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {\r
8618                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]\r
8619                     != CMAIL_OLD_RESULT) {\r
8620                     nCmailResults ++ ;\r
8621                     cmailResult[  CMAIL_MAX_GAMES\r
8622                                 - gn - 1] = CMAIL_OLD_RESULT;\r
8623                 }\r
8624             }\r
8625             break;\r
8626 \r
8627           case NormalMove:\r
8628             /* Only a NormalMove can be at the start of a game\r
8629              * without a position diagram. */\r
8630             if (lastLoadGameStart == (ChessMove) 0) {\r
8631               gn--;\r
8632               lastLoadGameStart = MoveNumberOne;\r
8633             }\r
8634             break;\r
8635 \r
8636           default:\r
8637             break;\r
8638         }\r
8639     }\r
8640     \r
8641     if (appData.debugMode)\r
8642       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);\r
8643 \r
8644     if (cm == XBoardGame) {\r
8645         /* Skip any header junk before position diagram and/or move 1 */\r
8646         for (;;) {\r
8647             yyboardindex = forwardMostMove;\r
8648             cm = (ChessMove) yylex();\r
8649 \r
8650             if (cm == (ChessMove) 0 ||\r
8651                 cm == GNUChessGame || cm == XBoardGame) {\r
8652                 /* Empty game; pretend end-of-file and handle later */\r
8653                 cm = (ChessMove) 0;\r
8654                 break;\r
8655             }\r
8656 \r
8657             if (cm == MoveNumberOne || cm == PositionDiagram ||\r
8658                 cm == PGNTag || cm == Comment)\r
8659               break;\r
8660         }\r
8661     } else if (cm == GNUChessGame) {\r
8662         if (gameInfo.event != NULL) {\r
8663             free(gameInfo.event);\r
8664         }\r
8665         gameInfo.event = StrSave(yy_text);\r
8666     }   \r
8667 \r
8668     startedFromSetupPosition = FALSE;\r
8669     while (cm == PGNTag) {\r
8670         if (appData.debugMode) \r
8671           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);\r
8672         err = ParsePGNTag(yy_text, &gameInfo);\r
8673         if (!err) numPGNTags++;\r
8674 \r
8675         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */\r
8676         if(gameInfo.variant != oldVariant) {\r
8677             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */\r
8678             InitPosition(TRUE);\r
8679             oldVariant = gameInfo.variant;\r
8680             if (appData.debugMode) \r
8681               fprintf(debugFP, "New variant %d\n", (int) oldVariant);\r
8682         }\r
8683 \r
8684 \r
8685         if (gameInfo.fen != NULL) {\r
8686           Board initial_position;\r
8687           startedFromSetupPosition = TRUE;\r
8688           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {\r
8689             Reset(TRUE, TRUE);\r
8690             DisplayError("Bad FEN position in file", 0);\r
8691             return FALSE;\r
8692           }\r
8693           CopyBoard(boards[0], initial_position);\r
8694           if (blackPlaysFirst) {\r
8695             currentMove = forwardMostMove = backwardMostMove = 1;\r
8696             CopyBoard(boards[1], initial_position);\r
8697             strcpy(moveList[0], "");\r
8698             strcpy(parseList[0], "");\r
8699             timeRemaining[0][1] = whiteTimeRemaining;\r
8700             timeRemaining[1][1] = blackTimeRemaining;\r
8701             if (commentList[0] != NULL) {\r
8702               commentList[1] = commentList[0];\r
8703               commentList[0] = NULL;\r
8704             }\r
8705           } else {\r
8706             currentMove = forwardMostMove = backwardMostMove = 0;\r
8707           }\r
8708           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */\r
8709           {   int i;\r
8710               initialRulePlies = FENrulePlies;\r
8711               epStatus[forwardMostMove] = FENepStatus;\r
8712               for( i=0; i< nrCastlingRights; i++ )\r
8713                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];\r
8714           }\r
8715           yyboardindex = forwardMostMove;\r
8716           free(gameInfo.fen);\r
8717           gameInfo.fen = NULL;\r
8718         }\r
8719 \r
8720         yyboardindex = forwardMostMove;\r
8721         cm = (ChessMove) yylex();\r
8722 \r
8723         /* Handle comments interspersed among the tags */\r
8724         while (cm == Comment) {\r
8725             char *p;\r
8726             if (appData.debugMode) \r
8727               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
8728             p = yy_text;\r
8729             if (*p == '{' || *p == '[' || *p == '(') {\r
8730                 p[strlen(p) - 1] = NULLCHAR;\r
8731                 p++;\r
8732             }\r
8733             while (*p == '\n') p++;\r
8734             AppendComment(currentMove, p);\r
8735             yyboardindex = forwardMostMove;\r
8736             cm = (ChessMove) yylex();\r
8737         }\r
8738     }\r
8739 \r
8740     /* don't rely on existence of Event tag since if game was\r
8741      * pasted from clipboard the Event tag may not exist\r
8742      */\r
8743     if (numPGNTags > 0){\r
8744         char *tags;\r
8745         if (gameInfo.variant == VariantNormal) {\r
8746           gameInfo.variant = StringToVariant(gameInfo.event);\r
8747         }\r
8748         if (!matchMode) {\r
8749           if( appData.autoDisplayTags ) {\r
8750             tags = PGNTags(&gameInfo);\r
8751             TagsPopUp(tags, CmailMsg());\r
8752             free(tags);\r
8753           }\r
8754         }\r
8755     } else {\r
8756         /* Make something up, but don't display it now */\r
8757         SetGameInfo();\r
8758         TagsPopDown();\r
8759     }\r
8760 \r
8761     if (cm == PositionDiagram) {\r
8762         int i, j;\r
8763         char *p;\r
8764         Board initial_position;\r
8765 \r
8766         if (appData.debugMode)\r
8767           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);\r
8768 \r
8769         if (!startedFromSetupPosition) {\r
8770             p = yy_text;\r
8771             for (i = BOARD_HEIGHT - 1; i >= 0; i--)\r
8772               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)\r
8773                 switch (*p) {\r
8774                   case '[':\r
8775                   case '-':\r
8776                   case ' ':\r
8777                   case '\t':\r
8778                   case '\n':\r
8779                   case '\r':\r
8780                     break;\r
8781                   default:\r
8782                     initial_position[i][j++] = CharToPiece(*p);\r
8783                     break;\r
8784                 }\r
8785             while (*p == ' ' || *p == '\t' ||\r
8786                    *p == '\n' || *p == '\r') p++;\r
8787         \r
8788             if (strncmp(p, "black", strlen("black"))==0)\r
8789               blackPlaysFirst = TRUE;\r
8790             else\r
8791               blackPlaysFirst = FALSE;\r
8792             startedFromSetupPosition = TRUE;\r
8793         \r
8794             CopyBoard(boards[0], initial_position);\r
8795             if (blackPlaysFirst) {\r
8796                 currentMove = forwardMostMove = backwardMostMove = 1;\r
8797                 CopyBoard(boards[1], initial_position);\r
8798                 strcpy(moveList[0], "");\r
8799                 strcpy(parseList[0], "");\r
8800                 timeRemaining[0][1] = whiteTimeRemaining;\r
8801                 timeRemaining[1][1] = blackTimeRemaining;\r
8802                 if (commentList[0] != NULL) {\r
8803                     commentList[1] = commentList[0];\r
8804                     commentList[0] = NULL;\r
8805                 }\r
8806             } else {\r
8807                 currentMove = forwardMostMove = backwardMostMove = 0;\r
8808             }\r
8809         }\r
8810         yyboardindex = forwardMostMove;\r
8811         cm = (ChessMove) yylex();\r
8812     }\r
8813 \r
8814     if (first.pr == NoProc) {\r
8815         StartChessProgram(&first);\r
8816     }\r
8817     InitChessProgram(&first, FALSE);\r
8818     SendToProgram("force\n", &first);\r
8819     if (startedFromSetupPosition) {\r
8820         SendBoard(&first, forwardMostMove);\r
8821     if (appData.debugMode) {\r
8822         fprintf(debugFP, "Load Game\n");\r
8823     }\r
8824         DisplayBothClocks();\r
8825     }      \r
8826 \r
8827     /* [HGM] server: flag to write setup moves in broadcast file as one */\r
8828     loadFlag = appData.suppressLoadMoves;\r
8829 \r
8830     while (cm == Comment) {\r
8831         char *p;\r
8832         if (appData.debugMode) \r
8833           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
8834         p = yy_text;\r
8835         if (*p == '{' || *p == '[' || *p == '(') {\r
8836             p[strlen(p) - 1] = NULLCHAR;\r
8837             p++;\r
8838         }\r
8839         while (*p == '\n') p++;\r
8840         AppendComment(currentMove, p);\r
8841         yyboardindex = forwardMostMove;\r
8842         cm = (ChessMove) yylex();\r
8843     }\r
8844 \r
8845     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||\r
8846         cm == WhiteWins || cm == BlackWins ||\r
8847         cm == GameIsDrawn || cm == GameUnfinished) {\r
8848         DisplayMessage("", "No moves in game");\r
8849         if (cmailMsgLoaded) {\r
8850             if (appData.debugMode)\r
8851               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);\r
8852             ClearHighlights();\r
8853             flipView = FALSE;\r
8854         }\r
8855         DrawPosition(FALSE, boards[currentMove]);\r
8856         DisplayBothClocks();\r
8857         gameMode = EditGame;\r
8858         ModeHighlight();\r
8859         gameFileFP = NULL;\r
8860         cmailOldMove = 0;\r
8861         return TRUE;\r
8862     }\r
8863 \r
8864     // [HGM] PV info: routine tests if comment empty\r
8865     if (!matchMode && (pausing || appData.timeDelay != 0)) {\r
8866         DisplayComment(currentMove - 1, commentList[currentMove]);\r
8867     }\r
8868     if (!matchMode && appData.timeDelay != 0) \r
8869       DrawPosition(FALSE, boards[currentMove]);\r
8870 \r
8871     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {\r
8872       programStats.ok_to_send = 1;\r
8873     }\r
8874 \r
8875     /* if the first token after the PGN tags is a move\r
8876      * and not move number 1, retrieve it from the parser \r
8877      */\r
8878     if (cm != MoveNumberOne)\r
8879         LoadGameOneMove(cm);\r
8880 \r
8881     /* load the remaining moves from the file */\r
8882     while (LoadGameOneMove((ChessMove)0)) {\r
8883       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
8884       timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
8885     }\r
8886 \r
8887     /* rewind to the start of the game */\r
8888     currentMove = backwardMostMove;\r
8889 \r
8890     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
8891 \r
8892     if (oldGameMode == AnalyzeFile ||\r
8893         oldGameMode == AnalyzeMode) {\r
8894       AnalyzeFileEvent();\r
8895     }\r
8896 \r
8897     if (matchMode || appData.timeDelay == 0) {\r
8898       ToEndEvent();\r
8899       gameMode = EditGame;\r
8900       ModeHighlight();\r
8901     } else if (appData.timeDelay > 0) {\r
8902       AutoPlayGameLoop();\r
8903     }\r
8904 \r
8905     if (appData.debugMode) \r
8906         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);\r
8907 \r
8908     loadFlag = 0; /* [HGM] true game starts */\r
8909     return TRUE;\r
8910 }\r
8911 \r
8912 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */\r
8913 int\r
8914 ReloadPosition(offset)\r
8915      int offset;\r
8916 {\r
8917     int positionNumber = lastLoadPositionNumber + offset;\r
8918     if (lastLoadPositionFP == NULL) {\r
8919         DisplayError("No position has been loaded yet", 0);\r
8920         return FALSE;\r
8921     }\r
8922     if (positionNumber <= 0) {\r
8923         DisplayError("Can't back up any further", 0);\r
8924         return FALSE;\r
8925     }\r
8926     return LoadPosition(lastLoadPositionFP, positionNumber,\r
8927                         lastLoadPositionTitle);\r
8928 }\r
8929 \r
8930 /* Load the nth position from the given file */\r
8931 int\r
8932 LoadPositionFromFile(filename, n, title)\r
8933      char *filename;\r
8934      int n;\r
8935      char *title;\r
8936 {\r
8937     FILE *f;\r
8938     char buf[MSG_SIZ];\r
8939 \r
8940     if (strcmp(filename, "-") == 0) {\r
8941         return LoadPosition(stdin, n, "stdin");\r
8942     } else {\r
8943         f = fopen(filename, "rb");\r
8944         if (f == NULL) {\r
8945             sprintf(buf, "Can't open \"%s\"", filename);\r
8946             DisplayError(buf, errno);\r
8947             return FALSE;\r
8948         } else {\r
8949             return LoadPosition(f, n, title);\r
8950         }\r
8951     }\r
8952 }\r
8953 \r
8954 /* Load the nth position from the given open file, and close it */\r
8955 int\r
8956 LoadPosition(f, positionNumber, title)\r
8957      FILE *f;\r
8958      int positionNumber;\r
8959      char *title;\r
8960 {\r
8961     char *p, line[MSG_SIZ];\r
8962     Board initial_position;\r
8963     int i, j, fenMode, pn;\r
8964     \r
8965     if (gameMode == Training )\r
8966         SetTrainingModeOff();\r
8967 \r
8968     if (gameMode != BeginningOfGame) {\r
8969         Reset(FALSE, TRUE);\r
8970     }\r
8971     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {\r
8972         fclose(lastLoadPositionFP);\r
8973     }\r
8974     if (positionNumber == 0) positionNumber = 1;\r
8975     lastLoadPositionFP = f;\r
8976     lastLoadPositionNumber = positionNumber;\r
8977     strcpy(lastLoadPositionTitle, title);\r
8978     if (first.pr == NoProc) {\r
8979       StartChessProgram(&first);\r
8980       InitChessProgram(&first, FALSE);\r
8981     }    \r
8982     pn = positionNumber;\r
8983     if (positionNumber < 0) {\r
8984         /* Negative position number means to seek to that byte offset */\r
8985         if (fseek(f, -positionNumber, 0) == -1) {\r
8986             DisplayError("Can't seek on position file", 0);\r
8987             return FALSE;\r
8988         };\r
8989         pn = 1;\r
8990     } else {\r
8991         if (fseek(f, 0, 0) == -1) {\r
8992             if (f == lastLoadPositionFP ?\r
8993                 positionNumber == lastLoadPositionNumber + 1 :\r
8994                 positionNumber == 1) {\r
8995                 pn = 1;\r
8996             } else {\r
8997                 DisplayError("Can't seek on position file", 0);\r
8998                 return FALSE;\r
8999             }\r
9000         }\r
9001     }\r
9002     /* See if this file is FEN or old-style xboard */\r
9003     if (fgets(line, MSG_SIZ, f) == NULL) {\r
9004         DisplayError("Position not found in file", 0);\r
9005         return FALSE;\r
9006     }\r
9007 #if 0\r
9008     switch (line[0]) {\r
9009       case '#':  case 'x':\r
9010       default:\r
9011         fenMode = FALSE;\r
9012         break;\r
9013       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':\r
9014       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':\r
9015       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':\r
9016       case '7':  case '8':  case '9':\r
9017       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':\r
9018       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':\r
9019       case 'C':  case 'W':             case 'c':  case 'w': \r
9020         fenMode = TRUE;\r
9021         break;\r
9022     }\r
9023 #else\r
9024     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces\r
9025     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;\r
9026 #endif\r
9027 \r
9028     if (pn >= 2) {\r
9029         if (fenMode || line[0] == '#') pn--;\r
9030         while (pn > 0) {\r
9031             /* skip positions before number pn */\r
9032             if (fgets(line, MSG_SIZ, f) == NULL) {\r
9033                 Reset(TRUE, TRUE);\r
9034                 DisplayError("Position not found in file", 0);\r
9035                 return FALSE;\r
9036             }\r
9037             if (fenMode || line[0] == '#') pn--;\r
9038         }\r
9039     }\r
9040 \r
9041     if (fenMode) {\r
9042         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {\r
9043             DisplayError("Bad FEN position in file", 0);\r
9044             return FALSE;\r
9045         }\r
9046     } else {\r
9047         (void) fgets(line, MSG_SIZ, f);\r
9048         (void) fgets(line, MSG_SIZ, f);\r
9049     \r
9050         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
9051             (void) fgets(line, MSG_SIZ, f);\r
9052             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {\r
9053                 if (*p == ' ')\r
9054                   continue;\r
9055                 initial_position[i][j++] = CharToPiece(*p);\r
9056             }\r
9057         }\r
9058     \r
9059         blackPlaysFirst = FALSE;\r
9060         if (!feof(f)) {\r
9061             (void) fgets(line, MSG_SIZ, f);\r
9062             if (strncmp(line, "black", strlen("black"))==0)\r
9063               blackPlaysFirst = TRUE;\r
9064         }\r
9065     }\r
9066     startedFromSetupPosition = TRUE;\r
9067     \r
9068     SendToProgram("force\n", &first);\r
9069     CopyBoard(boards[0], initial_position);\r
9070     if (blackPlaysFirst) {\r
9071         currentMove = forwardMostMove = backwardMostMove = 1;\r
9072         strcpy(moveList[0], "");\r
9073         strcpy(parseList[0], "");\r
9074         CopyBoard(boards[1], initial_position);\r
9075         DisplayMessage("", "Black to play");\r
9076     } else {\r
9077         currentMove = forwardMostMove = backwardMostMove = 0;\r
9078         DisplayMessage("", "White to play");\r
9079     }\r
9080           /* [HGM] copy FEN attributes as well */\r
9081           {   int i;\r
9082               initialRulePlies = FENrulePlies;\r
9083               epStatus[forwardMostMove] = FENepStatus;\r
9084               for( i=0; i< nrCastlingRights; i++ )\r
9085                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];\r
9086           }\r
9087     SendBoard(&first, forwardMostMove);\r
9088     if (appData.debugMode) {\r
9089 int i, j;\r
9090   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}\r
9091   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");\r
9092         fprintf(debugFP, "Load Position\n");\r
9093     }\r
9094 \r
9095     if (positionNumber > 1) {\r
9096         sprintf(line, "%s %d", title, positionNumber);\r
9097         DisplayTitle(line);\r
9098     } else {\r
9099         DisplayTitle(title);\r
9100     }\r
9101     gameMode = EditGame;\r
9102     ModeHighlight();\r
9103     ResetClocks();\r
9104     timeRemaining[0][1] = whiteTimeRemaining;\r
9105     timeRemaining[1][1] = blackTimeRemaining;\r
9106     DrawPosition(FALSE, boards[currentMove]);\r
9107    \r
9108     return TRUE;\r
9109 }\r
9110 \r
9111 \r
9112 void\r
9113 CopyPlayerNameIntoFileName(dest, src)\r
9114      char **dest, *src;\r
9115 {\r
9116     while (*src != NULLCHAR && *src != ',') {\r
9117         if (*src == ' ') {\r
9118             *(*dest)++ = '_';\r
9119             src++;\r
9120         } else {\r
9121             *(*dest)++ = *src++;\r
9122         }\r
9123     }\r
9124 }\r
9125 \r
9126 char *DefaultFileName(ext)\r
9127      char *ext;\r
9128 {\r
9129     static char def[MSG_SIZ];\r
9130     char *p;\r
9131 \r
9132     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {\r
9133         p = def;\r
9134         CopyPlayerNameIntoFileName(&p, gameInfo.white);\r
9135         *p++ = '-';\r
9136         CopyPlayerNameIntoFileName(&p, gameInfo.black);\r
9137         *p++ = '.';\r
9138         strcpy(p, ext);\r
9139     } else {\r
9140         def[0] = NULLCHAR;\r
9141     }\r
9142     return def;\r
9143 }\r
9144 \r
9145 /* Save the current game to the given file */\r
9146 int\r
9147 SaveGameToFile(filename, append)\r
9148      char *filename;\r
9149      int append;\r
9150 {\r
9151     FILE *f;\r
9152     char buf[MSG_SIZ];\r
9153 \r
9154     if (strcmp(filename, "-") == 0) {\r
9155         return SaveGame(stdout, 0, NULL);\r
9156     } else {\r
9157         f = fopen(filename, append ? "a" : "w");\r
9158         if (f == NULL) {\r
9159             sprintf(buf, "Can't open \"%s\"", filename);\r
9160             DisplayError(buf, errno);\r
9161             return FALSE;\r
9162         } else {\r
9163             return SaveGame(f, 0, NULL);\r
9164         }\r
9165     }\r
9166 }\r
9167 \r
9168 char *\r
9169 SavePart(str)\r
9170      char *str;\r
9171 {\r
9172     static char buf[MSG_SIZ];\r
9173     char *p;\r
9174     \r
9175     p = strchr(str, ' ');\r
9176     if (p == NULL) return str;\r
9177     strncpy(buf, str, p - str);\r
9178     buf[p - str] = NULLCHAR;\r
9179     return buf;\r
9180 }\r
9181 \r
9182 #define PGN_MAX_LINE 75\r
9183 \r
9184 #define PGN_SIDE_WHITE  0\r
9185 #define PGN_SIDE_BLACK  1\r
9186 \r
9187 /* [AS] */\r
9188 static int FindFirstMoveOutOfBook( int side )\r
9189 {\r
9190     int result = -1;\r
9191 \r
9192     if( backwardMostMove == 0 && ! startedFromSetupPosition) {\r
9193         int index = backwardMostMove;\r
9194         int has_book_hit = 0;\r
9195 \r
9196         if( (index % 2) != side ) {\r
9197             index++;\r
9198         }\r
9199 \r
9200         while( index < forwardMostMove ) {\r
9201             /* Check to see if engine is in book */\r
9202             int depth = pvInfoList[index].depth;\r
9203             int score = pvInfoList[index].score;\r
9204             int in_book = 0;\r
9205 \r
9206             if( depth <= 2 ) {\r
9207                 in_book = 1;\r
9208             }\r
9209             else if( score == 0 && depth == 63 ) {\r
9210                 in_book = 1; /* Zappa */\r
9211             }\r
9212             else if( score == 2 && depth == 99 ) {\r
9213                 in_book = 1; /* Abrok */\r
9214             }\r
9215 \r
9216             has_book_hit += in_book;\r
9217 \r
9218             if( ! in_book ) {\r
9219                 result = index;\r
9220 \r
9221                 break;\r
9222             }\r
9223 \r
9224             index += 2;\r
9225         }\r
9226     }\r
9227 \r
9228     return result;\r
9229 }\r
9230 \r
9231 /* [AS] */\r
9232 void GetOutOfBookInfo( char * buf )\r
9233 {\r
9234     int oob[2];\r
9235     int i;\r
9236     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9237 \r
9238     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );\r
9239     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );\r
9240 \r
9241     *buf = '\0';\r
9242 \r
9243     if( oob[0] >= 0 || oob[1] >= 0 ) {\r
9244         for( i=0; i<2; i++ ) {\r
9245             int idx = oob[i];\r
9246 \r
9247             if( idx >= 0 ) {\r
9248                 if( i > 0 && oob[0] >= 0 ) {\r
9249                     strcat( buf, "   " );\r
9250                 }\r
9251 \r
9252                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );\r
9253                 sprintf( buf+strlen(buf), "%s%.2f", \r
9254                     pvInfoList[idx].score >= 0 ? "+" : "",\r
9255                     pvInfoList[idx].score / 100.0 );\r
9256             }\r
9257         }\r
9258     }\r
9259 }\r
9260 \r
9261 /* Save game in PGN style and close the file */\r
9262 int\r
9263 SaveGamePGN(f)\r
9264      FILE *f;\r
9265 {\r
9266     int i, offset, linelen, newblock;\r
9267     time_t tm;\r
9268     char *movetext;\r
9269     char numtext[32];\r
9270     int movelen, numlen, blank;\r
9271     char move_buffer[100]; /* [AS] Buffer for move+PV info */\r
9272 \r
9273     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9274     \r
9275     tm = time((time_t *) NULL);\r
9276     \r
9277     PrintPGNTags(f, &gameInfo);\r
9278     \r
9279     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
9280         char *fen = PositionToFEN(backwardMostMove, 1);\r
9281         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);\r
9282         fprintf(f, "\n{--------------\n");\r
9283         PrintPosition(f, backwardMostMove);\r
9284         fprintf(f, "--------------}\n");\r
9285         free(fen);\r
9286     }\r
9287     else {\r
9288         /* [AS] Out of book annotation */\r
9289         if( appData.saveOutOfBookInfo ) {\r
9290             char buf[64];\r
9291 \r
9292             GetOutOfBookInfo( buf );\r
9293 \r
9294             if( buf[0] != '\0' ) {\r
9295                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); \r
9296             }\r
9297         }\r
9298 \r
9299         fprintf(f, "\n");\r
9300     }\r
9301 \r
9302     i = backwardMostMove;\r
9303     linelen = 0;\r
9304     newblock = TRUE;\r
9305 \r
9306     while (i < forwardMostMove) {\r
9307         /* Print comments preceding this move */\r
9308         if (commentList[i] != NULL) {\r
9309             if (linelen > 0) fprintf(f, "\n");\r
9310             fprintf(f, "{\n%s}\n", commentList[i]);\r
9311             linelen = 0;\r
9312             newblock = TRUE;\r
9313         }\r
9314 \r
9315         /* Format move number */\r
9316         if ((i % 2) == 0) {\r
9317             sprintf(numtext, "%d.", (i - offset)/2 + 1);\r
9318         } else {\r
9319             if (newblock) {\r
9320                 sprintf(numtext, "%d...", (i - offset)/2 + 1);\r
9321             } else {\r
9322                 numtext[0] = NULLCHAR;\r
9323             }\r
9324         }\r
9325         numlen = strlen(numtext);\r
9326         newblock = FALSE;\r
9327 \r
9328         /* Print move number */\r
9329         blank = linelen > 0 && numlen > 0;\r
9330         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {\r
9331             fprintf(f, "\n");\r
9332             linelen = 0;\r
9333             blank = 0;\r
9334         }\r
9335         if (blank) {\r
9336             fprintf(f, " ");\r
9337             linelen++;\r
9338         }\r
9339         fprintf(f, numtext);\r
9340         linelen += numlen;\r
9341 \r
9342         /* Get move */\r
9343         movelen = strlen(parseList[i]); /* [HGM] pgn: line-break point before move */\r
9344 \r
9345         /* Print move */\r
9346         blank = linelen > 0 && movelen > 0;\r
9347         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
9348             fprintf(f, "\n");\r
9349             linelen = 0;\r
9350             blank = 0;\r
9351         }\r
9352         if (blank) {\r
9353             fprintf(f, " ");\r
9354             linelen++;\r
9355         }\r
9356         fprintf(f, parseList[i]);\r
9357         linelen += movelen;\r
9358 \r
9359         /* [AS] Add PV info if present */\r
9360         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {\r
9361             /* [HGM] add time */\r
9362             char buf[MSG_SIZ]; int seconds = 0;\r
9363 \r
9364 #if 0\r
9365             if(i >= backwardMostMove) {\r
9366                 if(WhiteOnMove(i))\r
9367                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]\r
9368                                   + GetTimeQuota(i/2) / WhitePlayer()->timeOdds;\r
9369                 else\r
9370                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]\r
9371                                   + GetTimeQuota(i/2) / WhitePlayer()->other->timeOdds;\r
9372             }\r
9373             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest\r
9374 #else\r
9375             seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time\r
9376 #endif\r
9377     if (appData.debugMode,0) {\r
9378         fprintf(debugFP, "times = %d %d %d %d, seconds=%d\n",\r
9379                 timeRemaining[0][i+1], timeRemaining[0][i],\r
9380                      timeRemaining[1][i+1], timeRemaining[1][i], seconds\r
9381         );\r
9382     }\r
9383 \r
9384             if( seconds <= 0) buf[0] = 0; else\r
9385             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {\r
9386                 seconds = (seconds + 4)/10; // round to full seconds\r
9387                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else\r
9388                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);\r
9389             }\r
9390 \r
9391             sprintf( move_buffer, "{%s%.2f/%d%s}", \r
9392                 pvInfoList[i].score >= 0 ? "+" : "",\r
9393                 pvInfoList[i].score / 100.0,\r
9394                 pvInfoList[i].depth,\r
9395                 buf );\r
9396 \r
9397             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */\r
9398 \r
9399             /* Print score/depth */\r
9400             blank = linelen > 0 && movelen > 0;\r
9401             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
9402                 fprintf(f, "\n");\r
9403                 linelen = 0;\r
9404                 blank = 0;\r
9405             }\r
9406             if (blank) {\r
9407                 fprintf(f, " ");\r
9408                 linelen++;\r
9409             }\r
9410             fprintf(f, move_buffer);\r
9411             linelen += movelen;\r
9412         }\r
9413 \r
9414         i++;\r
9415     }\r
9416     \r
9417     /* Start a new line */\r
9418     if (linelen > 0) fprintf(f, "\n");\r
9419 \r
9420     /* Print comments after last move */\r
9421     if (commentList[i] != NULL) {\r
9422         fprintf(f, "{\n%s}\n", commentList[i]);\r
9423     }\r
9424 \r
9425     /* Print result */\r
9426     if (gameInfo.resultDetails != NULL &&\r
9427         gameInfo.resultDetails[0] != NULLCHAR) {\r
9428         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,\r
9429                 PGNResult(gameInfo.result));\r
9430     } else {\r
9431         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
9432     }\r
9433 \r
9434     fclose(f);\r
9435     return TRUE;\r
9436 }\r
9437 \r
9438 /* Save game in old style and close the file */\r
9439 int\r
9440 SaveGameOldStyle(f)\r
9441      FILE *f;\r
9442 {\r
9443     int i, offset;\r
9444     time_t tm;\r
9445     \r
9446     tm = time((time_t *) NULL);\r
9447     \r
9448     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));\r
9449     PrintOpponents(f);\r
9450     \r
9451     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
9452         fprintf(f, "\n[--------------\n");\r
9453         PrintPosition(f, backwardMostMove);\r
9454         fprintf(f, "--------------]\n");\r
9455     } else {\r
9456         fprintf(f, "\n");\r
9457     }\r
9458 \r
9459     i = backwardMostMove;\r
9460     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9461 \r
9462     while (i < forwardMostMove) {\r
9463         if (commentList[i] != NULL) {\r
9464             fprintf(f, "[%s]\n", commentList[i]);\r
9465         }\r
9466 \r
9467         if ((i % 2) == 1) {\r
9468             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);\r
9469             i++;\r
9470         } else {\r
9471             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);\r
9472             i++;\r
9473             if (commentList[i] != NULL) {\r
9474                 fprintf(f, "\n");\r
9475                 continue;\r
9476             }\r
9477             if (i >= forwardMostMove) {\r
9478                 fprintf(f, "\n");\r
9479                 break;\r
9480             }\r
9481             fprintf(f, "%s\n", parseList[i]);\r
9482             i++;\r
9483         }\r
9484     }\r
9485     \r
9486     if (commentList[i] != NULL) {\r
9487         fprintf(f, "[%s]\n", commentList[i]);\r
9488     }\r
9489 \r
9490     /* This isn't really the old style, but it's close enough */\r
9491     if (gameInfo.resultDetails != NULL &&\r
9492         gameInfo.resultDetails[0] != NULLCHAR) {\r
9493         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),\r
9494                 gameInfo.resultDetails);\r
9495     } else {\r
9496         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
9497     }\r
9498 \r
9499     fclose(f);\r
9500     return TRUE;\r
9501 }\r
9502 \r
9503 /* Save the current game to open file f and close the file */\r
9504 int\r
9505 SaveGame(f, dummy, dummy2)\r
9506      FILE *f;\r
9507      int dummy;\r
9508      char *dummy2;\r
9509 {\r
9510     if (gameMode == EditPosition) EditPositionDone();\r
9511     if (appData.oldSaveStyle)\r
9512       return SaveGameOldStyle(f);\r
9513     else\r
9514       return SaveGamePGN(f);\r
9515 }\r
9516 \r
9517 /* Save the current position to the given file */\r
9518 int\r
9519 SavePositionToFile(filename)\r
9520      char *filename;\r
9521 {\r
9522     FILE *f;\r
9523     char buf[MSG_SIZ];\r
9524 \r
9525     if (strcmp(filename, "-") == 0) {\r
9526         return SavePosition(stdout, 0, NULL);\r
9527     } else {\r
9528         f = fopen(filename, "a");\r
9529         if (f == NULL) {\r
9530             sprintf(buf, "Can't open \"%s\"", filename);\r
9531             DisplayError(buf, errno);\r
9532             return FALSE;\r
9533         } else {\r
9534             SavePosition(f, 0, NULL);\r
9535             return TRUE;\r
9536         }\r
9537     }\r
9538 }\r
9539 \r
9540 /* Save the current position to the given open file and close the file */\r
9541 int\r
9542 SavePosition(f, dummy, dummy2)\r
9543      FILE *f;\r
9544      int dummy;\r
9545      char *dummy2;\r
9546 {\r
9547     time_t tm;\r
9548     char *fen;\r
9549     \r
9550     if (appData.oldSaveStyle) {\r
9551         tm = time((time_t *) NULL);\r
9552     \r
9553         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));\r
9554         PrintOpponents(f);\r
9555         fprintf(f, "[--------------\n");\r
9556         PrintPosition(f, currentMove);\r
9557         fprintf(f, "--------------]\n");\r
9558     } else {\r
9559         fen = PositionToFEN(currentMove, 1);\r
9560         fprintf(f, "%s\n", fen);\r
9561         free(fen);\r
9562     }\r
9563     fclose(f);\r
9564     return TRUE;\r
9565 }\r
9566 \r
9567 void\r
9568 ReloadCmailMsgEvent(unregister)\r
9569      int unregister;\r
9570 {\r
9571 #if !WIN32\r
9572     static char *inFilename = NULL;\r
9573     static char *outFilename;\r
9574     int i;\r
9575     struct stat inbuf, outbuf;\r
9576     int status;\r
9577     \r
9578     /* Any registered moves are unregistered if unregister is set, */\r
9579     /* i.e. invoked by the signal handler */\r
9580     if (unregister) {\r
9581         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
9582             cmailMoveRegistered[i] = FALSE;\r
9583             if (cmailCommentList[i] != NULL) {\r
9584                 free(cmailCommentList[i]);\r
9585                 cmailCommentList[i] = NULL;\r
9586             }\r
9587         }\r
9588         nCmailMovesRegistered = 0;\r
9589     }\r
9590 \r
9591     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
9592         cmailResult[i] = CMAIL_NOT_RESULT;\r
9593     }\r
9594     nCmailResults = 0;\r
9595 \r
9596     if (inFilename == NULL) {\r
9597         /* Because the filenames are static they only get malloced once  */\r
9598         /* and they never get freed                                      */\r
9599         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);\r
9600         sprintf(inFilename, "%s.game.in", appData.cmailGameName);\r
9601 \r
9602         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);\r
9603         sprintf(outFilename, "%s.out", appData.cmailGameName);\r
9604     }\r
9605     \r
9606     status = stat(outFilename, &outbuf);\r
9607     if (status < 0) {\r
9608         cmailMailedMove = FALSE;\r
9609     } else {\r
9610         status = stat(inFilename, &inbuf);\r
9611         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);\r
9612     }\r
9613     \r
9614     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE\r
9615        counts the games, notes how each one terminated, etc.\r
9616        \r
9617        It would be nice to remove this kludge and instead gather all\r
9618        the information while building the game list.  (And to keep it\r
9619        in the game list nodes instead of having a bunch of fixed-size\r
9620        parallel arrays.)  Note this will require getting each game's\r
9621        termination from the PGN tags, as the game list builder does\r
9622        not process the game moves.  --mann\r
9623        */\r
9624     cmailMsgLoaded = TRUE;\r
9625     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);\r
9626     \r
9627     /* Load first game in the file or popup game menu */\r
9628     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);\r
9629 \r
9630 #endif /* !WIN32 */\r
9631     return;\r
9632 }\r
9633 \r
9634 int\r
9635 RegisterMove()\r
9636 {\r
9637     FILE *f;\r
9638     char string[MSG_SIZ];\r
9639 \r
9640     if (   cmailMailedMove\r
9641         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {\r
9642         return TRUE;            /* Allow free viewing  */\r
9643     }\r
9644 \r
9645     /* Unregister move to ensure that we don't leave RegisterMove        */\r
9646     /* with the move registered when the conditions for registering no   */\r
9647     /* longer hold                                                       */\r
9648     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
9649         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
9650         nCmailMovesRegistered --;\r
9651 \r
9652         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) \r
9653           {\r
9654               free(cmailCommentList[lastLoadGameNumber - 1]);\r
9655               cmailCommentList[lastLoadGameNumber - 1] = NULL;\r
9656           }\r
9657     }\r
9658 \r
9659     if (cmailOldMove == -1) {\r
9660         DisplayError("You have edited the game history.\nUse Reload Same Game and make your move again.", 0);\r
9661         return FALSE;\r
9662     }\r
9663 \r
9664     if (currentMove > cmailOldMove + 1) {\r
9665         DisplayError("You have entered too many moves.\nBack up to the correct position and try again.", 0);\r
9666         return FALSE;\r
9667     }\r
9668 \r
9669     if (currentMove < cmailOldMove) {\r
9670         DisplayError("Displayed position is not current.\nStep forward to the correct position and try again.", 0);\r
9671         return FALSE;\r
9672     }\r
9673 \r
9674     if (forwardMostMove > currentMove) {\r
9675         /* Silently truncate extra moves */\r
9676         TruncateGame();\r
9677     }\r
9678 \r
9679     if (   (currentMove == cmailOldMove + 1)\r
9680         || (   (currentMove == cmailOldMove)\r
9681             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)\r
9682                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {\r
9683         if (gameInfo.result != GameUnfinished) {\r
9684             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;\r
9685         }\r
9686 \r
9687         if (commentList[currentMove] != NULL) {\r
9688             cmailCommentList[lastLoadGameNumber - 1]\r
9689               = StrSave(commentList[currentMove]);\r
9690         }\r
9691         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);\r
9692 \r
9693         if (appData.debugMode)\r
9694           fprintf(debugFP, "Saving %s for game %d\n",\r
9695                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
9696 \r
9697         sprintf(string,\r
9698                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);\r
9699         \r
9700         f = fopen(string, "w");\r
9701         if (appData.oldSaveStyle) {\r
9702             SaveGameOldStyle(f); /* also closes the file */\r
9703             \r
9704             sprintf(string, "%s.pos.out", appData.cmailGameName);\r
9705             f = fopen(string, "w");\r
9706             SavePosition(f, 0, NULL); /* also closes the file */\r
9707         } else {\r
9708             fprintf(f, "{--------------\n");\r
9709             PrintPosition(f, currentMove);\r
9710             fprintf(f, "--------------}\n\n");\r
9711             \r
9712             SaveGame(f, 0, NULL); /* also closes the file*/\r
9713         }\r
9714         \r
9715         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;\r
9716         nCmailMovesRegistered ++;\r
9717     } else if (nCmailGames == 1) {\r
9718         DisplayError("You have not made a move yet", 0);\r
9719         return FALSE;\r
9720     }\r
9721 \r
9722     return TRUE;\r
9723 }\r
9724 \r
9725 void\r
9726 MailMoveEvent()\r
9727 {\r
9728 #if !WIN32\r
9729     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";\r
9730     FILE *commandOutput;\r
9731     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];\r
9732     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */\r
9733     int nBuffers;\r
9734     int i;\r
9735     int archived;\r
9736     char *arcDir;\r
9737 \r
9738     if (! cmailMsgLoaded) {\r
9739         DisplayError("The cmail message is not loaded.\nUse Reload CMail Message and make your move again.", 0);\r
9740         return;\r
9741     }\r
9742 \r
9743     if (nCmailGames == nCmailResults) {\r
9744         DisplayError("No unfinished games", 0);\r
9745         return;\r
9746     }\r
9747 \r
9748 #if CMAIL_PROHIBIT_REMAIL\r
9749     if (cmailMailedMove) {\r
9750         sprintf(msg, "You have already mailed a move.\nWait until a move arrives from your opponent.\nTo resend the same move, type\n\"cmail -remail -game %s\"\non the command line.", appData.cmailGameName);\r
9751         DisplayError(msg, 0);\r
9752         return;\r
9753     }\r
9754 #endif\r
9755 \r
9756     if (! (cmailMailedMove || RegisterMove())) return;\r
9757     \r
9758     if (   cmailMailedMove\r
9759         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {\r
9760         sprintf(string, partCommandString,\r
9761                 appData.debugMode ? " -v" : "", appData.cmailGameName);\r
9762         commandOutput = popen(string, "rb");\r
9763 \r
9764         if (commandOutput == NULL) {\r
9765             DisplayError("Failed to invoke cmail", 0);\r
9766         } else {\r
9767             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {\r
9768                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);\r
9769             }\r
9770             if (nBuffers > 1) {\r
9771                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);\r
9772                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);\r
9773                 nBytes = MSG_SIZ - 1;\r
9774             } else {\r
9775                 (void) memcpy(msg, buffer, nBytes);\r
9776             }\r
9777             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/\r
9778 \r
9779             if(StrStr(msg, "Mailed cmail message to ") != NULL) {\r
9780                 cmailMailedMove = TRUE; /* Prevent >1 moves    */\r
9781 \r
9782                 archived = TRUE;\r
9783                 for (i = 0; i < nCmailGames; i ++) {\r
9784                     if (cmailResult[i] == CMAIL_NOT_RESULT) {\r
9785                         archived = FALSE;\r
9786                     }\r
9787                 }\r
9788                 if (   archived\r
9789                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))\r
9790                         != NULL)) {\r
9791                     sprintf(buffer, "%s/%s.%s.archive",\r
9792                             arcDir,\r
9793                             appData.cmailGameName,\r
9794                             gameInfo.date);\r
9795                     LoadGameFromFile(buffer, 1, buffer, FALSE);\r
9796                     cmailMsgLoaded = FALSE;\r
9797                 }\r
9798             }\r
9799 \r
9800             DisplayInformation(msg);\r
9801             pclose(commandOutput);\r
9802         }\r
9803     } else {\r
9804         if ((*cmailMsg) != '\0') {\r
9805             DisplayInformation(cmailMsg);\r
9806         }\r
9807     }\r
9808 \r
9809     return;\r
9810 #endif /* !WIN32 */\r
9811 }\r
9812 \r
9813 char *\r
9814 CmailMsg()\r
9815 {\r
9816 #if WIN32\r
9817     return NULL;\r
9818 #else\r
9819     int  prependComma = 0;\r
9820     char number[5];\r
9821     char string[MSG_SIZ];       /* Space for game-list */\r
9822     int  i;\r
9823     \r
9824     if (!cmailMsgLoaded) return "";\r
9825 \r
9826     if (cmailMailedMove) {\r
9827         sprintf(cmailMsg, "Waiting for reply from opponent\n");\r
9828     } else {\r
9829         /* Create a list of games left */\r
9830         sprintf(string, "[");\r
9831         for (i = 0; i < nCmailGames; i ++) {\r
9832             if (! (   cmailMoveRegistered[i]\r
9833                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {\r
9834                 if (prependComma) {\r
9835                     sprintf(number, ",%d", i + 1);\r
9836                 } else {\r
9837                     sprintf(number, "%d", i + 1);\r
9838                     prependComma = 1;\r
9839                 }\r
9840                 \r
9841                 strcat(string, number);\r
9842             }\r
9843         }\r
9844         strcat(string, "]");\r
9845 \r
9846         if (nCmailMovesRegistered + nCmailResults == 0) {\r
9847             switch (nCmailGames) {\r
9848               case 1:\r
9849                 sprintf(cmailMsg,\r
9850                         "Still need to make move for game\n");\r
9851                 break;\r
9852                 \r
9853               case 2:\r
9854                 sprintf(cmailMsg,\r
9855                         "Still need to make moves for both games\n");\r
9856                 break;\r
9857                 \r
9858               default:\r
9859                 sprintf(cmailMsg,\r
9860                         "Still need to make moves for all %d games\n",\r
9861                         nCmailGames);\r
9862                 break;\r
9863             }\r
9864         } else {\r
9865             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {\r
9866               case 1:\r
9867                 sprintf(cmailMsg,\r
9868                         "Still need to make a move for game %s\n",\r
9869                         string);\r
9870                 break;\r
9871                 \r
9872               case 0:\r
9873                 if (nCmailResults == nCmailGames) {\r
9874                     sprintf(cmailMsg, "No unfinished games\n");\r
9875                 } else {\r
9876                     sprintf(cmailMsg, "Ready to send mail\n");\r
9877                 }\r
9878                 break;\r
9879                 \r
9880               default:\r
9881                 sprintf(cmailMsg,\r
9882                         "Still need to make moves for games %s\n",\r
9883                         string);\r
9884             }\r
9885         }\r
9886     }\r
9887     return cmailMsg;\r
9888 #endif /* WIN32 */\r
9889 }\r
9890 \r
9891 void\r
9892 ResetGameEvent()\r
9893 {\r
9894     if (gameMode == Training)\r
9895       SetTrainingModeOff();\r
9896 \r
9897     Reset(TRUE, TRUE);\r
9898     cmailMsgLoaded = FALSE;\r
9899     if (appData.icsActive) {\r
9900       SendToICS(ics_prefix);\r
9901       SendToICS("refresh\n");\r
9902     }\r
9903 }\r
9904 \r
9905 void\r
9906 ExitEvent(status)\r
9907      int status;\r
9908 {\r
9909     exiting++;\r
9910     if (exiting > 2) {\r
9911       /* Give up on clean exit */\r
9912       exit(status);\r
9913     }\r
9914     if (exiting > 1) {\r
9915       /* Keep trying for clean exit */\r
9916       return;\r
9917     }\r
9918 \r
9919     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);\r
9920 \r
9921     if (telnetISR != NULL) {\r
9922       RemoveInputSource(telnetISR);\r
9923     }\r
9924     if (icsPR != NoProc) {\r
9925       DestroyChildProcess(icsPR, TRUE);\r
9926     }\r
9927 #if 0\r
9928     /* Save game if resource set and not already saved by GameEnds() */\r
9929     if ((gameInfo.resultDetails == NULL || errorExitFlag )\r
9930                              && forwardMostMove > 0) {\r
9931       if (*appData.saveGameFile != NULLCHAR) {\r
9932         SaveGameToFile(appData.saveGameFile, TRUE);\r
9933       } else if (appData.autoSaveGames) {\r
9934         AutoSaveGame();\r
9935       }\r
9936       if (*appData.savePositionFile != NULLCHAR) {\r
9937         SavePositionToFile(appData.savePositionFile);\r
9938       }\r
9939     }\r
9940     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
9941 #else\r
9942     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */\r
9943     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);\r
9944 #endif\r
9945     /* [HGM] crash: the above GameEnds() is a dud if another one was running */\r
9946     /* make sure this other one finishes before killing it!                  */\r
9947     if(endingGame) { int count = 0;\r
9948         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");\r
9949         while(endingGame && count++ < 10) DoSleep(1);\r
9950         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");\r
9951     }\r
9952 \r
9953     /* Kill off chess programs */\r
9954     if (first.pr != NoProc) {\r
9955         ExitAnalyzeMode();\r
9956         \r
9957         DoSleep( appData.delayBeforeQuit );\r
9958         SendToProgram("quit\n", &first);\r
9959         DoSleep( appData.delayAfterQuit );\r
9960         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );\r
9961     }\r
9962     if (second.pr != NoProc) {\r
9963         DoSleep( appData.delayBeforeQuit );\r
9964         SendToProgram("quit\n", &second);\r
9965         DoSleep( appData.delayAfterQuit );\r
9966         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );\r
9967     }\r
9968     if (first.isr != NULL) {\r
9969         RemoveInputSource(first.isr);\r
9970     }\r
9971     if (second.isr != NULL) {\r
9972         RemoveInputSource(second.isr);\r
9973     }\r
9974 \r
9975     ShutDownFrontEnd();\r
9976     exit(status);\r
9977 }\r
9978 \r
9979 void\r
9980 PauseEvent()\r
9981 {\r
9982     if (appData.debugMode)\r
9983         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);\r
9984     if (pausing) {\r
9985         pausing = FALSE;\r
9986         ModeHighlight();\r
9987         if (gameMode == MachinePlaysWhite ||\r
9988             gameMode == MachinePlaysBlack) {\r
9989             StartClocks();\r
9990         } else {\r
9991             DisplayBothClocks();\r
9992         }\r
9993         if (gameMode == PlayFromGameFile) {\r
9994             if (appData.timeDelay >= 0) \r
9995                 AutoPlayGameLoop();\r
9996         } else if (gameMode == IcsExamining && pauseExamInvalid) {\r
9997             Reset(FALSE, TRUE);\r
9998             SendToICS(ics_prefix);\r
9999             SendToICS("refresh\n");\r
10000         } else if (currentMove < forwardMostMove) {\r
10001             ForwardInner(forwardMostMove);\r
10002         }\r
10003         pauseExamInvalid = FALSE;\r
10004     } else {\r
10005         switch (gameMode) {\r
10006           default:\r
10007             return;\r
10008           case IcsExamining:\r
10009             pauseExamForwardMostMove = forwardMostMove;\r
10010             pauseExamInvalid = FALSE;\r
10011             /* fall through */\r
10012           case IcsObserving:\r
10013           case IcsPlayingWhite:\r
10014           case IcsPlayingBlack:\r
10015             pausing = TRUE;\r
10016             ModeHighlight();\r
10017             return;\r
10018           case PlayFromGameFile:\r
10019             (void) StopLoadGameTimer();\r
10020             pausing = TRUE;\r
10021             ModeHighlight();\r
10022             break;\r
10023           case BeginningOfGame:\r
10024             if (appData.icsActive) return;\r
10025             /* else fall through */\r
10026           case MachinePlaysWhite:\r
10027           case MachinePlaysBlack:\r
10028           case TwoMachinesPlay:\r
10029             if (forwardMostMove == 0)\r
10030               return;           /* don't pause if no one has moved */\r
10031             if ((gameMode == MachinePlaysWhite &&\r
10032                  !WhiteOnMove(forwardMostMove)) ||\r
10033                 (gameMode == MachinePlaysBlack &&\r
10034                  WhiteOnMove(forwardMostMove))) {\r
10035                 StopClocks();\r
10036             }\r
10037             pausing = TRUE;\r
10038             ModeHighlight();\r
10039             break;\r
10040         }\r
10041     }\r
10042 }\r
10043 \r
10044 void\r
10045 EditCommentEvent()\r
10046 {\r
10047     char title[MSG_SIZ];\r
10048 \r
10049     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {\r
10050         strcpy(title, "Edit comment");\r
10051     } else {\r
10052         sprintf(title, "Edit comment on %d.%s%s", (currentMove - 1) / 2 + 1,\r
10053                 WhiteOnMove(currentMove - 1) ? " " : ".. ",\r
10054                 parseList[currentMove - 1]);\r
10055     }\r
10056 \r
10057     EditCommentPopUp(currentMove, title, commentList[currentMove]);\r
10058 }\r
10059 \r
10060 \r
10061 void\r
10062 EditTagsEvent()\r
10063 {\r
10064     char *tags = PGNTags(&gameInfo);\r
10065     EditTagsPopUp(tags);\r
10066     free(tags);\r
10067 }\r
10068 \r
10069 void\r
10070 AnalyzeModeEvent()\r
10071 {\r
10072     if (appData.noChessProgram || gameMode == AnalyzeMode)\r
10073       return;\r
10074 \r
10075     if (gameMode != AnalyzeFile) {\r
10076         EditGameEvent();\r
10077         if (gameMode != EditGame) return;\r
10078         ResurrectChessProgram();\r
10079         SendToProgram("analyze\n", &first);\r
10080         first.analyzing = TRUE;\r
10081         /*first.maybeThinking = TRUE;*/\r
10082         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10083         AnalysisPopUp("Analysis",\r
10084                       "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");\r
10085     }\r
10086     gameMode = AnalyzeMode;\r
10087     pausing = FALSE;\r
10088     ModeHighlight();\r
10089     SetGameInfo();\r
10090 \r
10091     StartAnalysisClock();\r
10092     GetTimeMark(&lastNodeCountTime);\r
10093     lastNodeCount = 0;\r
10094 }\r
10095 \r
10096 void\r
10097 AnalyzeFileEvent()\r
10098 {\r
10099     if (appData.noChessProgram || gameMode == AnalyzeFile)\r
10100       return;\r
10101 \r
10102     if (gameMode != AnalyzeMode) {\r
10103         EditGameEvent();\r
10104         if (gameMode != EditGame) return;\r
10105         ResurrectChessProgram();\r
10106         SendToProgram("analyze\n", &first);\r
10107         first.analyzing = TRUE;\r
10108         /*first.maybeThinking = TRUE;*/\r
10109         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10110         AnalysisPopUp("Analysis",\r
10111                       "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");\r
10112     }\r
10113     gameMode = AnalyzeFile;\r
10114     pausing = FALSE;\r
10115     ModeHighlight();\r
10116     SetGameInfo();\r
10117 \r
10118     StartAnalysisClock();\r
10119     GetTimeMark(&lastNodeCountTime);\r
10120     lastNodeCount = 0;\r
10121 }\r
10122 \r
10123 void\r
10124 MachineWhiteEvent()\r
10125 {\r
10126     char buf[MSG_SIZ];\r
10127     char *bookHit = NULL;\r
10128 \r
10129     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))\r
10130       return;\r
10131 \r
10132 \r
10133     if (gameMode == PlayFromGameFile || \r
10134         gameMode == TwoMachinesPlay  || \r
10135         gameMode == Training         || \r
10136         gameMode == AnalyzeMode      || \r
10137         gameMode == EndOfGame)\r
10138         EditGameEvent();\r
10139 \r
10140     if (gameMode == EditPosition) \r
10141         EditPositionDone();\r
10142 \r
10143     if (!WhiteOnMove(currentMove)) {\r
10144         DisplayError("It is not White's turn", 0);\r
10145         return;\r
10146     }\r
10147   \r
10148     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
10149       ExitAnalyzeMode();\r
10150 \r
10151     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
10152         gameMode == AnalyzeFile)\r
10153         TruncateGame();\r
10154 \r
10155     ResurrectChessProgram();    /* in case it isn't running */\r
10156     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */\r
10157         gameMode = MachinePlaysWhite;\r
10158         ResetClocks();\r
10159     } else\r
10160     gameMode = MachinePlaysWhite;\r
10161     pausing = FALSE;\r
10162     ModeHighlight();\r
10163     SetGameInfo();\r
10164     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10165     DisplayTitle(buf);\r
10166     if (first.sendName) {\r
10167       sprintf(buf, "name %s\n", gameInfo.black);\r
10168       SendToProgram(buf, &first);\r
10169     }\r
10170     if (first.sendTime) {\r
10171       if (first.useColors) {\r
10172         SendToProgram("black\n", &first); /*gnu kludge*/\r
10173       }\r
10174       SendTimeRemaining(&first, TRUE);\r
10175     }\r
10176     if (first.useColors) {\r
10177       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately\r
10178     }\r
10179     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move\r
10180     SetMachineThinkingEnables();\r
10181     first.maybeThinking = TRUE;\r
10182     StartClocks();\r
10183 \r
10184     if (appData.autoFlipView && !flipView) {\r
10185       flipView = !flipView;\r
10186       DrawPosition(FALSE, NULL);\r
10187     }\r
10188 \r
10189     if(bookHit) { // [HGM] book: simulate book reply\r
10190         static char bookMove[MSG_SIZ]; // a bit generous?\r
10191 \r
10192         programStats.depth = programStats.nodes = programStats.time = \r
10193         programStats.score = programStats.got_only_move = 0;\r
10194         sprintf(programStats.movelist, "%s (xbook)", bookMove);\r
10195 \r
10196         strcpy(bookMove, "move ");\r
10197         strcat(bookMove, bookHit);\r
10198         HandleMachineMove(bookMove, &first);\r
10199     }\r
10200 }\r
10201 \r
10202 void\r
10203 MachineBlackEvent()\r
10204 {\r
10205     char buf[MSG_SIZ];\r
10206    char *bookHit = NULL;\r
10207 \r
10208     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))\r
10209         return;\r
10210 \r
10211 \r
10212     if (gameMode == PlayFromGameFile || \r
10213         gameMode == TwoMachinesPlay  || \r
10214         gameMode == Training         || \r
10215         gameMode == AnalyzeMode      || \r
10216         gameMode == EndOfGame)\r
10217         EditGameEvent();\r
10218 \r
10219     if (gameMode == EditPosition) \r
10220         EditPositionDone();\r
10221 \r
10222     if (WhiteOnMove(currentMove)) {\r
10223         DisplayError("It is not Black's turn", 0);\r
10224         return;\r
10225     }\r
10226     \r
10227     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
10228       ExitAnalyzeMode();\r
10229 \r
10230     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
10231         gameMode == AnalyzeFile)\r
10232         TruncateGame();\r
10233 \r
10234     ResurrectChessProgram();    /* in case it isn't running */\r
10235     gameMode = MachinePlaysBlack;\r
10236     pausing = FALSE;\r
10237     ModeHighlight();\r
10238     SetGameInfo();\r
10239     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10240     DisplayTitle(buf);\r
10241     if (first.sendName) {\r
10242       sprintf(buf, "name %s\n", gameInfo.white);\r
10243       SendToProgram(buf, &first);\r
10244     }\r
10245     if (first.sendTime) {\r
10246       if (first.useColors) {\r
10247         SendToProgram("white\n", &first); /*gnu kludge*/\r
10248       }\r
10249       SendTimeRemaining(&first, FALSE);\r
10250     }\r
10251     if (first.useColors) {\r
10252       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately\r
10253     }\r
10254     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move\r
10255     SetMachineThinkingEnables();\r
10256     first.maybeThinking = TRUE;\r
10257     StartClocks();\r
10258 \r
10259     if (appData.autoFlipView && flipView) {\r
10260       flipView = !flipView;\r
10261       DrawPosition(FALSE, NULL);\r
10262     }\r
10263     if(bookHit) { // [HGM] book: simulate book reply\r
10264         static char bookMove[MSG_SIZ]; // a bit generous?\r
10265 \r
10266         programStats.depth = programStats.nodes = programStats.time = \r
10267         programStats.score = programStats.got_only_move = 0;\r
10268         sprintf(programStats.movelist, "%s (xbook)", bookMove);\r
10269 \r
10270         strcpy(bookMove, "move ");\r
10271         strcat(bookMove, bookHit);\r
10272         HandleMachineMove(bookMove, &first);\r
10273     }\r
10274 }\r
10275 \r
10276 \r
10277 void\r
10278 DisplayTwoMachinesTitle()\r
10279 {\r
10280     char buf[MSG_SIZ];\r
10281     if (appData.matchGames > 0) {\r
10282         if (first.twoMachinesColor[0] == 'w') {\r
10283             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
10284                     gameInfo.white, gameInfo.black,\r
10285                     first.matchWins, second.matchWins,\r
10286                     matchGame - 1 - (first.matchWins + second.matchWins));\r
10287         } else {\r
10288             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
10289                     gameInfo.white, gameInfo.black,\r
10290                     second.matchWins, first.matchWins,\r
10291                     matchGame - 1 - (first.matchWins + second.matchWins));\r
10292         }\r
10293     } else {\r
10294         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10295     }\r
10296     DisplayTitle(buf);\r
10297 }\r
10298 \r
10299 void\r
10300 TwoMachinesEvent P((void))\r
10301 {\r
10302     int i;\r
10303     char buf[MSG_SIZ];\r
10304     ChessProgramState *onmove;\r
10305     char *bookHit = NULL;\r
10306     \r
10307     if (appData.noChessProgram) return;\r
10308 \r
10309     switch (gameMode) {\r
10310       case TwoMachinesPlay:\r
10311         return;\r
10312       case MachinePlaysWhite:\r
10313       case MachinePlaysBlack:\r
10314         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
10315             DisplayError("Wait until your turn,\nor select Move Now", 0);\r
10316             return;\r
10317         }\r
10318         /* fall through */\r
10319       case BeginningOfGame:\r
10320       case PlayFromGameFile:\r
10321       case EndOfGame:\r
10322         EditGameEvent();\r
10323         if (gameMode != EditGame) return;\r
10324         break;\r
10325       case EditPosition:\r
10326         EditPositionDone();\r
10327         break;\r
10328       case AnalyzeMode:\r
10329       case AnalyzeFile:\r
10330         ExitAnalyzeMode();\r
10331         break;\r
10332       case EditGame:\r
10333       default:\r
10334         break;\r
10335     }\r
10336 \r
10337     forwardMostMove = currentMove;\r
10338     ResurrectChessProgram();    /* in case first program isn't running */\r
10339 \r
10340     if (second.pr == NULL) {\r
10341         StartChessProgram(&second);\r
10342         if (second.protocolVersion == 1) {\r
10343           TwoMachinesEventIfReady();\r
10344         } else {\r
10345           /* kludge: allow timeout for initial "feature" command */\r
10346           FreezeUI();\r
10347           DisplayMessage("", "Starting second chess program");\r
10348           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);\r
10349         }\r
10350         return;\r
10351     }\r
10352     DisplayMessage("", "");\r
10353     InitChessProgram(&second, FALSE);\r
10354     SendToProgram("force\n", &second);\r
10355     if (startedFromSetupPosition) {\r
10356         SendBoard(&second, backwardMostMove);\r
10357     if (appData.debugMode) {\r
10358         fprintf(debugFP, "Two Machines\n");\r
10359     }\r
10360     }\r
10361     for (i = backwardMostMove; i < forwardMostMove; i++) {\r
10362         SendMoveToProgram(i, &second);\r
10363     }\r
10364 \r
10365     gameMode = TwoMachinesPlay;\r
10366     pausing = FALSE;\r
10367     ModeHighlight();\r
10368     SetGameInfo();\r
10369     DisplayTwoMachinesTitle();\r
10370     firstMove = TRUE;\r
10371     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {\r
10372         onmove = &first;\r
10373     } else {\r
10374         onmove = &second;\r
10375     }\r
10376 \r
10377     SendToProgram(first.computerString, &first);\r
10378     if (first.sendName) {\r
10379       sprintf(buf, "name %s\n", second.tidy);\r
10380       SendToProgram(buf, &first);\r
10381     }\r
10382     SendToProgram(second.computerString, &second);\r
10383     if (second.sendName) {\r
10384       sprintf(buf, "name %s\n", first.tidy);\r
10385       SendToProgram(buf, &second);\r
10386     }\r
10387 \r
10388     ResetClocks();\r
10389     if (!first.sendTime || !second.sendTime) {\r
10390         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
10391         timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
10392     }\r
10393     if (onmove->sendTime) {\r
10394       if (onmove->useColors) {\r
10395         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/\r
10396       }\r
10397       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));\r
10398     }\r
10399     if (onmove->useColors) {\r
10400       SendToProgram(onmove->twoMachinesColor, onmove);\r
10401     }\r
10402     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move\r
10403 //    SendToProgram("go\n", onmove);\r
10404     onmove->maybeThinking = TRUE;\r
10405     SetMachineThinkingEnables();\r
10406 \r
10407     StartClocks();\r
10408 \r
10409     if(bookHit) { // [HGM] book: simulate book reply\r
10410         static char bookMove[MSG_SIZ]; // a bit generous?\r
10411 \r
10412         programStats.depth = programStats.nodes = programStats.time = \r
10413         programStats.score = programStats.got_only_move = 0;\r
10414         sprintf(programStats.movelist, "%s (xbook)", bookMove);\r
10415 \r
10416         strcpy(bookMove, "move ");\r
10417         strcat(bookMove, bookHit);\r
10418         HandleMachineMove(bookMove, &first);\r
10419     }\r
10420 }\r
10421 \r
10422 void\r
10423 TrainingEvent()\r
10424 {\r
10425     if (gameMode == Training) {\r
10426       SetTrainingModeOff();\r
10427       gameMode = PlayFromGameFile;\r
10428       DisplayMessage("", "Training mode off");\r
10429     } else {\r
10430       gameMode = Training;\r
10431       animateTraining = appData.animate;\r
10432 \r
10433       /* make sure we are not already at the end of the game */\r
10434       if (currentMove < forwardMostMove) {\r
10435         SetTrainingModeOn();\r
10436         DisplayMessage("", "Training mode on");\r
10437       } else {\r
10438         gameMode = PlayFromGameFile;\r
10439         DisplayError("Already at end of game", 0);\r
10440       }\r
10441     }\r
10442     ModeHighlight();\r
10443 }\r
10444 \r
10445 void\r
10446 IcsClientEvent()\r
10447 {\r
10448     if (!appData.icsActive) return;\r
10449     switch (gameMode) {\r
10450       case IcsPlayingWhite:\r
10451       case IcsPlayingBlack:\r
10452       case IcsObserving:\r
10453       case IcsIdle:\r
10454       case BeginningOfGame:\r
10455       case IcsExamining:\r
10456         return;\r
10457 \r
10458       case EditGame:\r
10459         break;\r
10460 \r
10461       case EditPosition:\r
10462         EditPositionDone();\r
10463         break;\r
10464 \r
10465       case AnalyzeMode:\r
10466       case AnalyzeFile:\r
10467         ExitAnalyzeMode();\r
10468         break;\r
10469         \r
10470       default:\r
10471         EditGameEvent();\r
10472         break;\r
10473     }\r
10474 \r
10475     gameMode = IcsIdle;\r
10476     ModeHighlight();\r
10477     return;\r
10478 }\r
10479 \r
10480 \r
10481 void\r
10482 EditGameEvent()\r
10483 {\r
10484     int i;\r
10485 \r
10486     switch (gameMode) {\r
10487       case Training:\r
10488         SetTrainingModeOff();\r
10489         break;\r
10490       case MachinePlaysWhite:\r
10491       case MachinePlaysBlack:\r
10492       case BeginningOfGame:\r
10493         SendToProgram("force\n", &first);\r
10494         SetUserThinkingEnables();\r
10495         break;\r
10496       case PlayFromGameFile:\r
10497         (void) StopLoadGameTimer();\r
10498         if (gameFileFP != NULL) {\r
10499             gameFileFP = NULL;\r
10500         }\r
10501         break;\r
10502       case EditPosition:\r
10503         EditPositionDone();\r
10504         break;\r
10505       case AnalyzeMode:\r
10506       case AnalyzeFile:\r
10507         ExitAnalyzeMode();\r
10508         SendToProgram("force\n", &first);\r
10509         break;\r
10510       case TwoMachinesPlay:\r
10511         GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
10512         ResurrectChessProgram();\r
10513         SetUserThinkingEnables();\r
10514         break;\r
10515       case EndOfGame:\r
10516         ResurrectChessProgram();\r
10517         break;\r
10518       case IcsPlayingBlack:\r
10519       case IcsPlayingWhite:\r
10520         DisplayError("Warning: You are still playing a game", 0);\r
10521         break;\r
10522       case IcsObserving:\r
10523         DisplayError("Warning: You are still observing a game", 0);\r
10524         break;\r
10525       case IcsExamining:\r
10526         DisplayError("Warning: You are still examining a game", 0);\r
10527         break;\r
10528       case IcsIdle:\r
10529         break;\r
10530       case EditGame:\r
10531       default:\r
10532         return;\r
10533     }\r
10534     \r
10535     pausing = FALSE;\r
10536     StopClocks();\r
10537     first.offeredDraw = second.offeredDraw = 0;\r
10538 \r
10539     if (gameMode == PlayFromGameFile) {\r
10540         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10541         blackTimeRemaining = timeRemaining[1][currentMove];\r
10542         DisplayTitle("");\r
10543     }\r
10544 \r
10545     if (gameMode == MachinePlaysWhite ||\r
10546         gameMode == MachinePlaysBlack ||\r
10547         gameMode == TwoMachinesPlay ||\r
10548         gameMode == EndOfGame) {\r
10549         i = forwardMostMove;\r
10550         while (i > currentMove) {\r
10551             SendToProgram("undo\n", &first);\r
10552             i--;\r
10553         }\r
10554         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10555         blackTimeRemaining = timeRemaining[1][currentMove];\r
10556         DisplayBothClocks();\r
10557         if (whiteFlag || blackFlag) {\r
10558             whiteFlag = blackFlag = 0;\r
10559         }\r
10560         DisplayTitle("");\r
10561     }           \r
10562     \r
10563     gameMode = EditGame;\r
10564     ModeHighlight();\r
10565     SetGameInfo();\r
10566 }\r
10567 \r
10568 \r
10569 void\r
10570 EditPositionEvent()\r
10571 {\r
10572     if (gameMode == EditPosition) {\r
10573         EditGameEvent();\r
10574         return;\r
10575     }\r
10576     \r
10577     EditGameEvent();\r
10578     if (gameMode != EditGame) return;\r
10579     \r
10580     gameMode = EditPosition;\r
10581     ModeHighlight();\r
10582     SetGameInfo();\r
10583     if (currentMove > 0)\r
10584       CopyBoard(boards[0], boards[currentMove]);\r
10585     \r
10586     blackPlaysFirst = !WhiteOnMove(currentMove);\r
10587     ResetClocks();\r
10588     currentMove = forwardMostMove = backwardMostMove = 0;\r
10589     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
10590     DisplayMove(-1);\r
10591 }\r
10592 \r
10593 void\r
10594 ExitAnalyzeMode()\r
10595 {\r
10596     if (first.analysisSupport && first.analyzing) {\r
10597       SendToProgram("exit\n", &first);\r
10598       first.analyzing = FALSE;\r
10599     }\r
10600     AnalysisPopDown();\r
10601     thinkOutput[0] = NULLCHAR;\r
10602 }\r
10603 \r
10604 void\r
10605 EditPositionDone()\r
10606 {\r
10607     startedFromSetupPosition = TRUE;\r
10608     InitChessProgram(&first, FALSE);\r
10609     SendToProgram("force\n", &first);\r
10610     if (blackPlaysFirst) {\r
10611         strcpy(moveList[0], "");\r
10612         strcpy(parseList[0], "");\r
10613         currentMove = forwardMostMove = backwardMostMove = 1;\r
10614         CopyBoard(boards[1], boards[0]);\r
10615         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */\r
10616         { int i;\r
10617           epStatus[1] = epStatus[0];\r
10618           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];\r
10619         }\r
10620     } else {\r
10621         currentMove = forwardMostMove = backwardMostMove = 0;\r
10622     }\r
10623     SendBoard(&first, forwardMostMove);\r
10624     if (appData.debugMode) {\r
10625         fprintf(debugFP, "EditPosDone\n");\r
10626     }\r
10627     DisplayTitle("");\r
10628     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
10629     timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
10630     gameMode = EditGame;\r
10631     ModeHighlight();\r
10632     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
10633     ClearHighlights(); /* [AS] */\r
10634 }\r
10635 \r
10636 /* Pause for `ms' milliseconds */\r
10637 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
10638 void\r
10639 TimeDelay(ms)\r
10640      long ms;\r
10641 {\r
10642     TimeMark m1, m2;\r
10643 \r
10644     GetTimeMark(&m1);\r
10645     do {\r
10646         GetTimeMark(&m2);\r
10647     } while (SubtractTimeMarks(&m2, &m1) < ms);\r
10648 }\r
10649 \r
10650 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
10651 void\r
10652 SendMultiLineToICS(buf)\r
10653      char *buf;\r
10654 {\r
10655     char temp[MSG_SIZ+1], *p;\r
10656     int len;\r
10657 \r
10658     len = strlen(buf);\r
10659     if (len > MSG_SIZ)\r
10660       len = MSG_SIZ;\r
10661   \r
10662     strncpy(temp, buf, len);\r
10663     temp[len] = 0;\r
10664 \r
10665     p = temp;\r
10666     while (*p) {\r
10667         if (*p == '\n' || *p == '\r')\r
10668           *p = ' ';\r
10669         ++p;\r
10670     }\r
10671 \r
10672     strcat(temp, "\n");\r
10673     SendToICS(temp);\r
10674     SendToPlayer(temp, strlen(temp));\r
10675 }\r
10676 \r
10677 void\r
10678 SetWhiteToPlayEvent()\r
10679 {\r
10680     if (gameMode == EditPosition) {\r
10681         blackPlaysFirst = FALSE;\r
10682         DisplayBothClocks();    /* works because currentMove is 0 */\r
10683     } else if (gameMode == IcsExamining) {\r
10684         SendToICS(ics_prefix);\r
10685         SendToICS("tomove white\n");\r
10686     }\r
10687 }\r
10688 \r
10689 void\r
10690 SetBlackToPlayEvent()\r
10691 {\r
10692     if (gameMode == EditPosition) {\r
10693         blackPlaysFirst = TRUE;\r
10694         currentMove = 1;        /* kludge */\r
10695         DisplayBothClocks();\r
10696         currentMove = 0;\r
10697     } else if (gameMode == IcsExamining) {\r
10698         SendToICS(ics_prefix);\r
10699         SendToICS("tomove black\n");\r
10700     }\r
10701 }\r
10702 \r
10703 void\r
10704 EditPositionMenuEvent(selection, x, y)\r
10705      ChessSquare selection;\r
10706      int x, y;\r
10707 {\r
10708     char buf[MSG_SIZ];\r
10709     ChessSquare piece = boards[0][y][x];\r
10710 \r
10711     if (gameMode != EditPosition && gameMode != IcsExamining) return;\r
10712 \r
10713     switch (selection) {\r
10714       case ClearBoard:\r
10715         if (gameMode == IcsExamining && ics_type == ICS_FICS) {\r
10716             SendToICS(ics_prefix);\r
10717             SendToICS("bsetup clear\n");\r
10718         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {\r
10719             SendToICS(ics_prefix);\r
10720             SendToICS("clearboard\n");\r
10721         } else {\r
10722             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;\r
10723                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */\r
10724                 for (y = 0; y < BOARD_HEIGHT; y++) {\r
10725                     if (gameMode == IcsExamining) {\r
10726                         if (boards[currentMove][y][x] != EmptySquare) {\r
10727                             sprintf(buf, "%sx@%c%c\n", ics_prefix,\r
10728                                     AAA + x, ONE + y);\r
10729                             SendToICS(buf);\r
10730                         }\r
10731                     } else {\r
10732                         boards[0][y][x] = p;\r
10733                     }\r
10734                 }\r
10735             }\r
10736         }\r
10737         if (gameMode == EditPosition) {\r
10738             DrawPosition(FALSE, boards[0]);\r
10739         }\r
10740         break;\r
10741 \r
10742       case WhitePlay:\r
10743         SetWhiteToPlayEvent();\r
10744         break;\r
10745 \r
10746       case BlackPlay:\r
10747         SetBlackToPlayEvent();\r
10748         break;\r
10749 \r
10750       case EmptySquare:\r
10751         if (gameMode == IcsExamining) {\r
10752             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);\r
10753             SendToICS(buf);\r
10754         } else {\r
10755             boards[0][y][x] = EmptySquare;\r
10756             DrawPosition(FALSE, boards[0]);\r
10757         }\r
10758         break;\r
10759 \r
10760       case PromotePiece:\r
10761         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||\r
10762            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {\r
10763             selection = (ChessSquare) (PROMOTED piece);\r
10764         } else if(piece == EmptySquare) selection = WhiteSilver;\r
10765         else selection = (ChessSquare)((int)piece - 1);\r
10766         goto defaultlabel;\r
10767 \r
10768       case DemotePiece:\r
10769         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||\r
10770            piece > (int)BlackMan && piece <= (int)BlackKing   ) {\r
10771             selection = (ChessSquare) (DEMOTED piece);\r
10772         } else if(piece == EmptySquare) selection = BlackSilver;\r
10773         else selection = (ChessSquare)((int)piece + 1);       \r
10774         goto defaultlabel;\r
10775 \r
10776       case WhiteQueen:\r
10777       case BlackQueen:\r
10778         if(gameInfo.variant == VariantShatranj ||\r
10779            gameInfo.variant == VariantXiangqi  ||\r
10780            gameInfo.variant == VariantCourier    )\r
10781             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);\r
10782         goto defaultlabel;\r
10783 \r
10784       case WhiteKing:\r
10785       case BlackKing:\r
10786         if(gameInfo.variant == VariantXiangqi)\r
10787             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);\r
10788         if(gameInfo.variant == VariantKnightmate)\r
10789             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);\r
10790       default:\r
10791         defaultlabel:\r
10792         if (gameMode == IcsExamining) {\r
10793             sprintf(buf, "%s%c@%c%c\n", ics_prefix,\r
10794                     PieceToChar(selection), AAA + x, ONE + y);\r
10795             SendToICS(buf);\r
10796         } else {\r
10797             boards[0][y][x] = selection;\r
10798             DrawPosition(FALSE, boards[0]);\r
10799         }\r
10800         break;\r
10801     }\r
10802 }\r
10803 \r
10804 \r
10805 void\r
10806 DropMenuEvent(selection, x, y)\r
10807      ChessSquare selection;\r
10808      int x, y;\r
10809 {\r
10810     ChessMove moveType;\r
10811 \r
10812     switch (gameMode) {\r
10813       case IcsPlayingWhite:\r
10814       case MachinePlaysBlack:\r
10815         if (!WhiteOnMove(currentMove)) {\r
10816             DisplayMoveError("It is Black's turn");\r
10817             return;\r
10818         }\r
10819         moveType = WhiteDrop;\r
10820         break;\r
10821       case IcsPlayingBlack:\r
10822       case MachinePlaysWhite:\r
10823         if (WhiteOnMove(currentMove)) {\r
10824             DisplayMoveError("It is White's turn");\r
10825             return;\r
10826         }\r
10827         moveType = BlackDrop;\r
10828         break;\r
10829       case EditGame:\r
10830         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
10831         break;\r
10832       default:\r
10833         return;\r
10834     }\r
10835 \r
10836     if (moveType == BlackDrop && selection < BlackPawn) {\r
10837       selection = (ChessSquare) ((int) selection\r
10838                                  + (int) BlackPawn - (int) WhitePawn);\r
10839     }\r
10840     if (boards[currentMove][y][x] != EmptySquare) {\r
10841         DisplayMoveError("That square is occupied");\r
10842         return;\r
10843     }\r
10844 \r
10845     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);\r
10846 }\r
10847 \r
10848 void\r
10849 AcceptEvent()\r
10850 {\r
10851     /* Accept a pending offer of any kind from opponent */\r
10852     \r
10853     if (appData.icsActive) {\r
10854         SendToICS(ics_prefix);\r
10855         SendToICS("accept\n");\r
10856     } else if (cmailMsgLoaded) {\r
10857         if (currentMove == cmailOldMove &&\r
10858             commentList[cmailOldMove] != NULL &&\r
10859             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
10860                    "Black offers a draw" : "White offers a draw")) {\r
10861             TruncateGame();\r
10862             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
10863             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
10864         } else {\r
10865             DisplayError("There is no pending offer on this move", 0);\r
10866             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
10867         }\r
10868     } else {\r
10869         /* Not used for offers from chess program */\r
10870     }\r
10871 }\r
10872 \r
10873 void\r
10874 DeclineEvent()\r
10875 {\r
10876     /* Decline a pending offer of any kind from opponent */\r
10877     \r
10878     if (appData.icsActive) {\r
10879         SendToICS(ics_prefix);\r
10880         SendToICS("decline\n");\r
10881     } else if (cmailMsgLoaded) {\r
10882         if (currentMove == cmailOldMove &&\r
10883             commentList[cmailOldMove] != NULL &&\r
10884             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
10885                    "Black offers a draw" : "White offers a draw")) {\r
10886 #ifdef NOTDEF\r
10887             AppendComment(cmailOldMove, "Draw declined");\r
10888             DisplayComment(cmailOldMove - 1, "Draw declined");\r
10889 #endif /*NOTDEF*/\r
10890         } else {\r
10891             DisplayError("There is no pending offer on this move", 0);\r
10892         }\r
10893     } else {\r
10894         /* Not used for offers from chess program */\r
10895     }\r
10896 }\r
10897 \r
10898 void\r
10899 RematchEvent()\r
10900 {\r
10901     /* Issue ICS rematch command */\r
10902     if (appData.icsActive) {\r
10903         SendToICS(ics_prefix);\r
10904         SendToICS("rematch\n");\r
10905     }\r
10906 }\r
10907 \r
10908 void\r
10909 CallFlagEvent()\r
10910 {\r
10911     /* Call your opponent's flag (claim a win on time) */\r
10912     if (appData.icsActive) {\r
10913         SendToICS(ics_prefix);\r
10914         SendToICS("flag\n");\r
10915     } else {\r
10916         switch (gameMode) {\r
10917           default:\r
10918             return;\r
10919           case MachinePlaysWhite:\r
10920             if (whiteFlag) {\r
10921                 if (blackFlag)\r
10922                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
10923                            GE_PLAYER);\r
10924                 else\r
10925                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);\r
10926             } else {\r
10927                 DisplayError("Your opponent is not out of time", 0);\r
10928             }\r
10929             break;\r
10930           case MachinePlaysBlack:\r
10931             if (blackFlag) {\r
10932                 if (whiteFlag)\r
10933                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
10934                            GE_PLAYER);\r
10935                 else\r
10936                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);\r
10937             } else {\r
10938                 DisplayError("Your opponent is not out of time", 0);\r
10939             }\r
10940             break;\r
10941         }\r
10942     }\r
10943 }\r
10944 \r
10945 void\r
10946 DrawEvent()\r
10947 {\r
10948     /* Offer draw or accept pending draw offer from opponent */\r
10949     \r
10950     if (appData.icsActive) {\r
10951         /* Note: tournament rules require draw offers to be\r
10952            made after you make your move but before you punch\r
10953            your clock.  Currently ICS doesn't let you do that;\r
10954            instead, you immediately punch your clock after making\r
10955            a move, but you can offer a draw at any time. */\r
10956         \r
10957         SendToICS(ics_prefix);\r
10958         SendToICS("draw\n");\r
10959     } else if (cmailMsgLoaded) {\r
10960         if (currentMove == cmailOldMove &&\r
10961             commentList[cmailOldMove] != NULL &&\r
10962             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
10963                    "Black offers a draw" : "White offers a draw")) {\r
10964             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
10965             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
10966         } else if (currentMove == cmailOldMove + 1) {\r
10967             char *offer = WhiteOnMove(cmailOldMove) ?\r
10968               "White offers a draw" : "Black offers a draw";\r
10969             AppendComment(currentMove, offer);\r
10970             DisplayComment(currentMove - 1, offer);\r
10971             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;\r
10972         } else {\r
10973             DisplayError("You must make your move before offering a draw", 0);\r
10974             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
10975         }\r
10976     } else if (first.offeredDraw) {\r
10977         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
10978     } else {\r
10979         if (first.sendDrawOffers) {\r
10980             SendToProgram("draw\n", &first);\r
10981             userOfferedDraw = TRUE;\r
10982         }\r
10983     }\r
10984 }\r
10985 \r
10986 void\r
10987 AdjournEvent()\r
10988 {\r
10989     /* Offer Adjourn or accept pending Adjourn offer from opponent */\r
10990     \r
10991     if (appData.icsActive) {\r
10992         SendToICS(ics_prefix);\r
10993         SendToICS("adjourn\n");\r
10994     } else {\r
10995         /* Currently GNU Chess doesn't offer or accept Adjourns */\r
10996     }\r
10997 }\r
10998 \r
10999 \r
11000 void\r
11001 AbortEvent()\r
11002 {\r
11003     /* Offer Abort or accept pending Abort offer from opponent */\r
11004     \r
11005     if (appData.icsActive) {\r
11006         SendToICS(ics_prefix);\r
11007         SendToICS("abort\n");\r
11008     } else {\r
11009         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);\r
11010     }\r
11011 }\r
11012 \r
11013 void\r
11014 ResignEvent()\r
11015 {\r
11016     /* Resign.  You can do this even if it's not your turn. */\r
11017     \r
11018     if (appData.icsActive) {\r
11019         SendToICS(ics_prefix);\r
11020         SendToICS("resign\n");\r
11021     } else {\r
11022         switch (gameMode) {\r
11023           case MachinePlaysWhite:\r
11024             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
11025             break;\r
11026           case MachinePlaysBlack:\r
11027             GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
11028             break;\r
11029           case EditGame:\r
11030             if (cmailMsgLoaded) {\r
11031                 TruncateGame();\r
11032                 if (WhiteOnMove(cmailOldMove)) {\r
11033                     GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
11034                 } else {\r
11035                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
11036                 }\r
11037                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;\r
11038             }\r
11039             break;\r
11040           default:\r
11041             break;\r
11042         }\r
11043     }\r
11044 }\r
11045 \r
11046 \r
11047 void\r
11048 StopObservingEvent()\r
11049 {\r
11050     /* Stop observing current games */\r
11051     SendToICS(ics_prefix);\r
11052     SendToICS("unobserve\n");\r
11053 }\r
11054 \r
11055 void\r
11056 StopExaminingEvent()\r
11057 {\r
11058     /* Stop observing current game */\r
11059     SendToICS(ics_prefix);\r
11060     SendToICS("unexamine\n");\r
11061 }\r
11062 \r
11063 void\r
11064 ForwardInner(target)\r
11065      int target;\r
11066 {\r
11067     int limit;\r
11068 \r
11069     if (appData.debugMode)\r
11070         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",\r
11071                 target, currentMove, forwardMostMove);\r
11072 \r
11073     if (gameMode == EditPosition)\r
11074       return;\r
11075 \r
11076     if (gameMode == PlayFromGameFile && !pausing)\r
11077       PauseEvent();\r
11078     \r
11079     if (gameMode == IcsExamining && pausing)\r
11080       limit = pauseExamForwardMostMove;\r
11081     else\r
11082       limit = forwardMostMove;\r
11083     \r
11084     if (target > limit) target = limit;\r
11085 \r
11086     if (target > 0 && moveList[target - 1][0]) {\r
11087         int fromX, fromY, toX, toY;\r
11088         toX = moveList[target - 1][2] - AAA;\r
11089         toY = moveList[target - 1][3] - ONE;\r
11090         if (moveList[target - 1][1] == '@') {\r
11091             if (appData.highlightLastMove) {\r
11092                 SetHighlights(-1, -1, toX, toY);\r
11093             }\r
11094         } else {\r
11095             fromX = moveList[target - 1][0] - AAA;\r
11096             fromY = moveList[target - 1][1] - ONE;\r
11097             if (target == currentMove + 1) {\r
11098                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
11099             }\r
11100             if (appData.highlightLastMove) {\r
11101                 SetHighlights(fromX, fromY, toX, toY);\r
11102             }\r
11103         }\r
11104     }\r
11105     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
11106         gameMode == Training || gameMode == PlayFromGameFile || \r
11107         gameMode == AnalyzeFile) {\r
11108         while (currentMove < target) {\r
11109             SendMoveToProgram(currentMove++, &first);\r
11110         }\r
11111     } else {\r
11112         currentMove = target;\r
11113     }\r
11114     \r
11115     if (gameMode == EditGame || gameMode == EndOfGame) {\r
11116         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11117         blackTimeRemaining = timeRemaining[1][currentMove];\r
11118     }\r
11119     DisplayBothClocks();\r
11120     DisplayMove(currentMove - 1);\r
11121     DrawPosition(FALSE, boards[currentMove]);\r
11122     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
11123     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty\r
11124         DisplayComment(currentMove - 1, commentList[currentMove]);\r
11125     }\r
11126 }\r
11127 \r
11128 \r
11129 void\r
11130 ForwardEvent()\r
11131 {\r
11132     if (gameMode == IcsExamining && !pausing) {\r
11133         SendToICS(ics_prefix);\r
11134         SendToICS("forward\n");\r
11135     } else {\r
11136         ForwardInner(currentMove + 1);\r
11137     }\r
11138 }\r
11139 \r
11140 void\r
11141 ToEndEvent()\r
11142 {\r
11143     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11144         /* to optimze, we temporarily turn off analysis mode while we feed\r
11145          * the remaining moves to the engine. Otherwise we get analysis output\r
11146          * after each move.\r
11147          */ \r
11148         if (first.analysisSupport) {\r
11149           SendToProgram("exit\nforce\n", &first);\r
11150           first.analyzing = FALSE;\r
11151         }\r
11152     }\r
11153         \r
11154     if (gameMode == IcsExamining && !pausing) {\r
11155         SendToICS(ics_prefix);\r
11156         SendToICS("forward 999999\n");\r
11157     } else {\r
11158         ForwardInner(forwardMostMove);\r
11159     }\r
11160 \r
11161     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11162         /* we have fed all the moves, so reactivate analysis mode */\r
11163         SendToProgram("analyze\n", &first);\r
11164         first.analyzing = TRUE;\r
11165         /*first.maybeThinking = TRUE;*/\r
11166         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
11167     }\r
11168 }\r
11169 \r
11170 void\r
11171 BackwardInner(target)\r
11172      int target;\r
11173 {\r
11174     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */\r
11175 \r
11176     if (appData.debugMode)\r
11177         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",\r
11178                 target, currentMove, forwardMostMove);\r
11179 \r
11180     if (gameMode == EditPosition) return;\r
11181     if (currentMove <= backwardMostMove) {\r
11182         ClearHighlights();\r
11183         DrawPosition(full_redraw, boards[currentMove]);\r
11184         return;\r
11185     }\r
11186     if (gameMode == PlayFromGameFile && !pausing)\r
11187       PauseEvent();\r
11188     \r
11189     if (moveList[target][0]) {\r
11190         int fromX, fromY, toX, toY;\r
11191         toX = moveList[target][2] - AAA;\r
11192         toY = moveList[target][3] - ONE;\r
11193         if (moveList[target][1] == '@') {\r
11194             if (appData.highlightLastMove) {\r
11195                 SetHighlights(-1, -1, toX, toY);\r
11196             }\r
11197         } else {\r
11198             fromX = moveList[target][0] - AAA;\r
11199             fromY = moveList[target][1] - ONE;\r
11200             if (target == currentMove - 1) {\r
11201                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);\r
11202             }\r
11203             if (appData.highlightLastMove) {\r
11204                 SetHighlights(fromX, fromY, toX, toY);\r
11205             }\r
11206         }\r
11207     }\r
11208     if (gameMode == EditGame || gameMode==AnalyzeMode ||\r
11209         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
11210         while (currentMove > target) {\r
11211             SendToProgram("undo\n", &first);\r
11212             currentMove--;\r
11213         }\r
11214     } else {\r
11215         currentMove = target;\r
11216     }\r
11217     \r
11218     if (gameMode == EditGame || gameMode == EndOfGame) {\r
11219         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11220         blackTimeRemaining = timeRemaining[1][currentMove];\r
11221     }\r
11222     DisplayBothClocks();\r
11223     DisplayMove(currentMove - 1);\r
11224     DrawPosition(full_redraw, boards[currentMove]);\r
11225     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
11226     // [HGM] PV info: routine tests if comment empty\r
11227     DisplayComment(currentMove - 1, commentList[currentMove]);\r
11228 }\r
11229 \r
11230 void\r
11231 BackwardEvent()\r
11232 {\r
11233     if (gameMode == IcsExamining && !pausing) {\r
11234         SendToICS(ics_prefix);\r
11235         SendToICS("backward\n");\r
11236     } else {\r
11237         BackwardInner(currentMove - 1);\r
11238     }\r
11239 }\r
11240 \r
11241 void\r
11242 ToStartEvent()\r
11243 {\r
11244     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11245         /* to optimze, we temporarily turn off analysis mode while we undo\r
11246          * all the moves. Otherwise we get analysis output after each undo.\r
11247          */ \r
11248         if (first.analysisSupport) {\r
11249           SendToProgram("exit\nforce\n", &first);\r
11250           first.analyzing = FALSE;\r
11251         }\r
11252     }\r
11253 \r
11254     if (gameMode == IcsExamining && !pausing) {\r
11255         SendToICS(ics_prefix);\r
11256         SendToICS("backward 999999\n");\r
11257     } else {\r
11258         BackwardInner(backwardMostMove);\r
11259     }\r
11260 \r
11261     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11262         /* we have fed all the moves, so reactivate analysis mode */\r
11263         SendToProgram("analyze\n", &first);\r
11264         first.analyzing = TRUE;\r
11265         /*first.maybeThinking = TRUE;*/\r
11266         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
11267     }\r
11268 }\r
11269 \r
11270 void\r
11271 ToNrEvent(int to)\r
11272 {\r
11273   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();\r
11274   if (to >= forwardMostMove) to = forwardMostMove;\r
11275   if (to <= backwardMostMove) to = backwardMostMove;\r
11276   if (to < currentMove) {\r
11277     BackwardInner(to);\r
11278   } else {\r
11279     ForwardInner(to);\r
11280   }\r
11281 }\r
11282 \r
11283 void\r
11284 RevertEvent()\r
11285 {\r
11286     if (gameMode != IcsExamining) {\r
11287         DisplayError("You are not examining a game", 0);\r
11288         return;\r
11289     }\r
11290     if (pausing) {\r
11291         DisplayError("You can't revert while pausing", 0);\r
11292         return;\r
11293     }\r
11294     SendToICS(ics_prefix);\r
11295     SendToICS("revert\n");\r
11296 }\r
11297 \r
11298 void\r
11299 RetractMoveEvent()\r
11300 {\r
11301     switch (gameMode) {\r
11302       case MachinePlaysWhite:\r
11303       case MachinePlaysBlack:\r
11304         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
11305             DisplayError("Wait until your turn,\nor select Move Now", 0);\r
11306             return;\r
11307         }\r
11308         if (forwardMostMove < 2) return;\r
11309         currentMove = forwardMostMove = forwardMostMove - 2;\r
11310         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11311         blackTimeRemaining = timeRemaining[1][currentMove];\r
11312         DisplayBothClocks();\r
11313         DisplayMove(currentMove - 1);\r
11314         ClearHighlights();/*!! could figure this out*/\r
11315         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */\r
11316         SendToProgram("remove\n", &first);\r
11317         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */\r
11318         break;\r
11319 \r
11320       case BeginningOfGame:\r
11321       default:\r
11322         break;\r
11323 \r
11324       case IcsPlayingWhite:\r
11325       case IcsPlayingBlack:\r
11326         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {\r
11327             SendToICS(ics_prefix);\r
11328             SendToICS("takeback 2\n");\r
11329         } else {\r
11330             SendToICS(ics_prefix);\r
11331             SendToICS("takeback 1\n");\r
11332         }\r
11333         break;\r
11334     }\r
11335 }\r
11336 \r
11337 void\r
11338 MoveNowEvent()\r
11339 {\r
11340     ChessProgramState *cps;\r
11341 \r
11342     switch (gameMode) {\r
11343       case MachinePlaysWhite:\r
11344         if (!WhiteOnMove(forwardMostMove)) {\r
11345             DisplayError("It is your turn", 0);\r
11346             return;\r
11347         }\r
11348         cps = &first;\r
11349         break;\r
11350       case MachinePlaysBlack:\r
11351         if (WhiteOnMove(forwardMostMove)) {\r
11352             DisplayError("It is your turn", 0);\r
11353             return;\r
11354         }\r
11355         cps = &first;\r
11356         break;\r
11357       case TwoMachinesPlay:\r
11358         if (WhiteOnMove(forwardMostMove) ==\r
11359             (first.twoMachinesColor[0] == 'w')) {\r
11360             cps = &first;\r
11361         } else {\r
11362             cps = &second;\r
11363         }\r
11364         break;\r
11365       case BeginningOfGame:\r
11366       default:\r
11367         return;\r
11368     }\r
11369     SendToProgram("?\n", cps);\r
11370 }\r
11371 \r
11372 void\r
11373 TruncateGameEvent()\r
11374 {\r
11375     EditGameEvent();\r
11376     if (gameMode != EditGame) return;\r
11377     TruncateGame();\r
11378 }\r
11379 \r
11380 void\r
11381 TruncateGame()\r
11382 {\r
11383     if (forwardMostMove > currentMove) {\r
11384         if (gameInfo.resultDetails != NULL) {\r
11385             free(gameInfo.resultDetails);\r
11386             gameInfo.resultDetails = NULL;\r
11387             gameInfo.result = GameUnfinished;\r
11388         }\r
11389         forwardMostMove = currentMove;\r
11390         HistorySet(parseList, backwardMostMove, forwardMostMove,\r
11391                    currentMove-1);\r
11392     }\r
11393 }\r
11394 \r
11395 void\r
11396 HintEvent()\r
11397 {\r
11398     if (appData.noChessProgram) return;\r
11399     switch (gameMode) {\r
11400       case MachinePlaysWhite:\r
11401         if (WhiteOnMove(forwardMostMove)) {\r
11402             DisplayError("Wait until your turn", 0);\r
11403             return;\r
11404         }\r
11405         break;\r
11406       case BeginningOfGame:\r
11407       case MachinePlaysBlack:\r
11408         if (!WhiteOnMove(forwardMostMove)) {\r
11409             DisplayError("Wait until your turn", 0);\r
11410             return;\r
11411         }\r
11412         break;\r
11413       default:\r
11414         DisplayError("No hint available", 0);\r
11415         return;\r
11416     }\r
11417     SendToProgram("hint\n", &first);\r
11418     hintRequested = TRUE;\r
11419 }\r
11420 \r
11421 void\r
11422 BookEvent()\r
11423 {\r
11424     if (appData.noChessProgram) return;\r
11425     switch (gameMode) {\r
11426       case MachinePlaysWhite:\r
11427         if (WhiteOnMove(forwardMostMove)) {\r
11428             DisplayError("Wait until your turn", 0);\r
11429             return;\r
11430         }\r
11431         break;\r
11432       case BeginningOfGame:\r
11433       case MachinePlaysBlack:\r
11434         if (!WhiteOnMove(forwardMostMove)) {\r
11435             DisplayError("Wait until your turn", 0);\r
11436             return;\r
11437         }\r
11438         break;\r
11439       case EditPosition:\r
11440         EditPositionDone();\r
11441         break;\r
11442       case TwoMachinesPlay:\r
11443         return;\r
11444       default:\r
11445         break;\r
11446     }\r
11447     SendToProgram("bk\n", &first);\r
11448     bookOutput[0] = NULLCHAR;\r
11449     bookRequested = TRUE;\r
11450 }\r
11451 \r
11452 void\r
11453 AboutGameEvent()\r
11454 {\r
11455     char *tags = PGNTags(&gameInfo);\r
11456     TagsPopUp(tags, CmailMsg());\r
11457     free(tags);\r
11458 }\r
11459 \r
11460 /* end button procedures */\r
11461 \r
11462 void\r
11463 PrintPosition(fp, move)\r
11464      FILE *fp;\r
11465      int move;\r
11466 {\r
11467     int i, j;\r
11468     \r
11469     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
11470         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
11471             char c = PieceToChar(boards[move][i][j]);\r
11472             fputc(c == 'x' ? '.' : c, fp);\r
11473             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);\r
11474         }\r
11475     }\r
11476     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))\r
11477       fprintf(fp, "white to play\n");\r
11478     else\r
11479       fprintf(fp, "black to play\n");\r
11480 }\r
11481 \r
11482 void\r
11483 PrintOpponents(fp)\r
11484      FILE *fp;\r
11485 {\r
11486     if (gameInfo.white != NULL) {\r
11487         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);\r
11488     } else {\r
11489         fprintf(fp, "\n");\r
11490     }\r
11491 }\r
11492 \r
11493 /* Find last component of program's own name, using some heuristics */\r
11494 void\r
11495 TidyProgramName(prog, host, buf)\r
11496      char *prog, *host, buf[MSG_SIZ];\r
11497 {\r
11498     char *p, *q;\r
11499     int local = (strcmp(host, "localhost") == 0);\r
11500     while (!local && (p = strchr(prog, ';')) != NULL) {\r
11501         p++;\r
11502         while (*p == ' ') p++;\r
11503         prog = p;\r
11504     }\r
11505     if (*prog == '"' || *prog == '\'') {\r
11506         q = strchr(prog + 1, *prog);\r
11507     } else {\r
11508         q = strchr(prog, ' ');\r
11509     }\r
11510     if (q == NULL) q = prog + strlen(prog);\r
11511     p = q;\r
11512     while (p >= prog && *p != '/' && *p != '\\') p--;\r
11513     p++;\r
11514     if(p == prog && *p == '"') p++;\r
11515     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;\r
11516     memcpy(buf, p, q - p);\r
11517     buf[q - p] = NULLCHAR;\r
11518     if (!local) {\r
11519         strcat(buf, "@");\r
11520         strcat(buf, host);\r
11521     }\r
11522 }\r
11523 \r
11524 char *\r
11525 TimeControlTagValue()\r
11526 {\r
11527     char buf[MSG_SIZ];\r
11528     if (!appData.clockMode) {\r
11529         strcpy(buf, "-");\r
11530     } else if (movesPerSession > 0) {\r
11531         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);\r
11532     } else if (timeIncrement == 0) {\r
11533         sprintf(buf, "%ld", timeControl/1000);\r
11534     } else {\r
11535         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);\r
11536     }\r
11537     return StrSave(buf);\r
11538 }\r
11539 \r
11540 void\r
11541 SetGameInfo()\r
11542 {\r
11543     /* This routine is used only for certain modes */\r
11544     VariantClass v = gameInfo.variant;\r
11545     ClearGameInfo(&gameInfo);\r
11546     gameInfo.variant = v;\r
11547 \r
11548     switch (gameMode) {\r
11549       case MachinePlaysWhite:\r
11550         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11551         gameInfo.site = StrSave(HostName());\r
11552         gameInfo.date = PGNDate();\r
11553         gameInfo.round = StrSave("-");\r
11554         gameInfo.white = StrSave(first.tidy);\r
11555         gameInfo.black = StrSave(UserName());\r
11556         gameInfo.timeControl = TimeControlTagValue();\r
11557         break;\r
11558 \r
11559       case MachinePlaysBlack:\r
11560         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11561         gameInfo.site = StrSave(HostName());\r
11562         gameInfo.date = PGNDate();\r
11563         gameInfo.round = StrSave("-");\r
11564         gameInfo.white = StrSave(UserName());\r
11565         gameInfo.black = StrSave(first.tidy);\r
11566         gameInfo.timeControl = TimeControlTagValue();\r
11567         break;\r
11568 \r
11569       case TwoMachinesPlay:\r
11570         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11571         gameInfo.site = StrSave(HostName());\r
11572         gameInfo.date = PGNDate();\r
11573         if (matchGame > 0) {\r
11574             char buf[MSG_SIZ];\r
11575             sprintf(buf, "%d", matchGame);\r
11576             gameInfo.round = StrSave(buf);\r
11577         } else {\r
11578             gameInfo.round = StrSave("-");\r
11579         }\r
11580         if (first.twoMachinesColor[0] == 'w') {\r
11581             gameInfo.white = StrSave(first.tidy);\r
11582             gameInfo.black = StrSave(second.tidy);\r
11583         } else {\r
11584             gameInfo.white = StrSave(second.tidy);\r
11585             gameInfo.black = StrSave(first.tidy);\r
11586         }\r
11587         gameInfo.timeControl = TimeControlTagValue();\r
11588         break;\r
11589 \r
11590       case EditGame:\r
11591         gameInfo.event = StrSave("Edited game");\r
11592         gameInfo.site = StrSave(HostName());\r
11593         gameInfo.date = PGNDate();\r
11594         gameInfo.round = StrSave("-");\r
11595         gameInfo.white = StrSave("-");\r
11596         gameInfo.black = StrSave("-");\r
11597         break;\r
11598 \r
11599       case EditPosition:\r
11600         gameInfo.event = StrSave("Edited position");\r
11601         gameInfo.site = StrSave(HostName());\r
11602         gameInfo.date = PGNDate();\r
11603         gameInfo.round = StrSave("-");\r
11604         gameInfo.white = StrSave("-");\r
11605         gameInfo.black = StrSave("-");\r
11606         break;\r
11607 \r
11608       case IcsPlayingWhite:\r
11609       case IcsPlayingBlack:\r
11610       case IcsObserving:\r
11611       case IcsExamining:\r
11612         break;\r
11613 \r
11614       case PlayFromGameFile:\r
11615         gameInfo.event = StrSave("Game from non-PGN file");\r
11616         gameInfo.site = StrSave(HostName());\r
11617         gameInfo.date = PGNDate();\r
11618         gameInfo.round = StrSave("-");\r
11619         gameInfo.white = StrSave("?");\r
11620         gameInfo.black = StrSave("?");\r
11621         break;\r
11622 \r
11623       default:\r
11624         break;\r
11625     }\r
11626 }\r
11627 \r
11628 void\r
11629 ReplaceComment(index, text)\r
11630      int index;\r
11631      char *text;\r
11632 {\r
11633     int len;\r
11634 \r
11635     while (*text == '\n') text++;\r
11636     len = strlen(text);\r
11637     while (len > 0 && text[len - 1] == '\n') len--;\r
11638 \r
11639     if (commentList[index] != NULL)\r
11640       free(commentList[index]);\r
11641 \r
11642     if (len == 0) {\r
11643         commentList[index] = NULL;\r
11644         return;\r
11645     }\r
11646     commentList[index] = (char *) malloc(len + 2);\r
11647     strncpy(commentList[index], text, len);\r
11648     commentList[index][len] = '\n';\r
11649     commentList[index][len + 1] = NULLCHAR;\r
11650 }\r
11651 \r
11652 void\r
11653 CrushCRs(text)\r
11654      char *text;\r
11655 {\r
11656   char *p = text;\r
11657   char *q = text;\r
11658   char ch;\r
11659 \r
11660   do {\r
11661     ch = *p++;\r
11662     if (ch == '\r') continue;\r
11663     *q++ = ch;\r
11664   } while (ch != '\0');\r
11665 }\r
11666 \r
11667 void\r
11668 AppendComment(index, text)\r
11669      int index;\r
11670      char *text;\r
11671 {\r
11672     int oldlen, len;\r
11673     char *old;\r
11674 \r
11675     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */\r
11676 \r
11677     CrushCRs(text);\r
11678     while (*text == '\n') text++;\r
11679     len = strlen(text);\r
11680     while (len > 0 && text[len - 1] == '\n') len--;\r
11681 \r
11682     if (len == 0) return;\r
11683 \r
11684     if (commentList[index] != NULL) {\r
11685         old = commentList[index];\r
11686         oldlen = strlen(old);\r
11687         commentList[index] = (char *) malloc(oldlen + len + 2);\r
11688         strcpy(commentList[index], old);\r
11689         free(old);\r
11690         strncpy(&commentList[index][oldlen], text, len);\r
11691         commentList[index][oldlen + len] = '\n';\r
11692         commentList[index][oldlen + len + 1] = NULLCHAR;\r
11693     } else {\r
11694         commentList[index] = (char *) malloc(len + 2);\r
11695         strncpy(commentList[index], text, len);\r
11696         commentList[index][len] = '\n';\r
11697         commentList[index][len + 1] = NULLCHAR;\r
11698     }\r
11699 }\r
11700 \r
11701 static char * FindStr( char * text, char * sub_text )\r
11702 {\r
11703     char * result = strstr( text, sub_text );\r
11704 \r
11705     if( result != NULL ) {\r
11706         result += strlen( sub_text );\r
11707     }\r
11708 \r
11709     return result;\r
11710 }\r
11711 \r
11712 /* [AS] Try to extract PV info from PGN comment */\r
11713 /* [HGM] PV time: and then remove it, to prevent it appearing twice */\r
11714 char *GetInfoFromComment( int index, char * text )\r
11715 {\r
11716     char * sep = text;\r
11717 \r
11718     if( text != NULL && index > 0 ) {\r
11719         int score = 0;\r
11720         int depth = 0;\r
11721         int time = -1, sec = 0, deci;\r
11722         char * s_eval = FindStr( text, "[%eval " );\r
11723         char * s_emt = FindStr( text, "[%emt " );\r
11724 \r
11725         if( s_eval != NULL || s_emt != NULL ) {\r
11726             /* New style */\r
11727             char delim;\r
11728 \r
11729             if( s_eval != NULL ) {\r
11730                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {\r
11731                     return text;\r
11732                 }\r
11733 \r
11734                 if( delim != ']' ) {\r
11735                     return text;\r
11736                 }\r
11737             }\r
11738 \r
11739             if( s_emt != NULL ) {\r
11740             }\r
11741         }\r
11742         else {\r
11743             /* We expect something like: [+|-]nnn.nn/dd */\r
11744             int score_lo = 0;\r
11745 \r
11746             sep = strchr( text, '/' );\r
11747             if( sep == NULL || sep < (text+4) ) {\r
11748                 return text;\r
11749             }\r
11750 \r
11751             time = -1; sec = -1; deci = -1;\r
11752             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&\r
11753                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&\r
11754                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&\r
11755                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {\r
11756                 return text;\r
11757             }\r
11758 \r
11759             if( score_lo < 0 || score_lo >= 100 ) {\r
11760                 return text;\r
11761             }\r
11762 \r
11763             if(sec >= 0) time = 600*time + 10*sec; else\r
11764             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec\r
11765 \r
11766             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;\r
11767 \r
11768             /* [HGM] PV time: now locate end of PV info */\r
11769             while( *++sep >= '0' && *sep <= '9'); // strip depth\r
11770             if(time >= 0)\r
11771             while( *++sep >= '0' && *sep <= '9'); // strip time\r
11772             if(sec >= 0)\r
11773             while( *++sep >= '0' && *sep <= '9'); // strip seconds\r
11774             if(deci >= 0)\r
11775             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds\r
11776             while(*sep == ' ') sep++;\r
11777         }\r
11778 \r
11779         if( depth <= 0 ) {\r
11780             return text;\r
11781         }\r
11782 \r
11783         if( time < 0 ) {\r
11784             time = -1;\r
11785         }\r
11786 \r
11787         pvInfoList[index-1].depth = depth;\r
11788         pvInfoList[index-1].score = score;\r
11789         pvInfoList[index-1].time  = 10*time; // centi-sec\r
11790     }\r
11791     return sep;\r
11792 }\r
11793 \r
11794 void\r
11795 SendToProgram(message, cps)\r
11796      char *message;\r
11797      ChessProgramState *cps;\r
11798 {\r
11799     int count, outCount, error;\r
11800     char buf[MSG_SIZ];\r
11801 \r
11802     if (cps->pr == NULL) return;\r
11803     Attention(cps);\r
11804     \r
11805     if (appData.debugMode) {\r
11806         TimeMark now;\r
11807         GetTimeMark(&now);\r
11808         fprintf(debugFP, "%ld >%-6s: %s", \r
11809                 SubtractTimeMarks(&now, &programStartTime),\r
11810                 cps->which, message);\r
11811     }\r
11812     \r
11813     count = strlen(message);\r
11814     outCount = OutputToProcess(cps->pr, message, count, &error);\r
11815     if (outCount < count && !exiting \r
11816                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */\r
11817         sprintf(buf, "Error writing to %s chess program", cps->which);\r
11818         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
11819             if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
11820                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
11821                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);\r
11822             } else {\r
11823                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
11824             }\r
11825             gameInfo.resultDetails = buf;\r
11826         }\r
11827         DisplayFatalError(buf, error, 1);\r
11828     }\r
11829 }\r
11830 \r
11831 void\r
11832 ReceiveFromProgram(isr, closure, message, count, error)\r
11833      InputSourceRef isr;\r
11834      VOIDSTAR closure;\r
11835      char *message;\r
11836      int count;\r
11837      int error;\r
11838 {\r
11839     char *end_str;\r
11840     char buf[MSG_SIZ];\r
11841     ChessProgramState *cps = (ChessProgramState *)closure;\r
11842 \r
11843     if (isr != cps->isr) return; /* Killed intentionally */\r
11844     if (count <= 0) {\r
11845         if (count == 0) {\r
11846             sprintf(buf,\r
11847                     "Error: %s chess program (%s) exited unexpectedly",\r
11848                     cps->which, cps->program);\r
11849         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
11850                 if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
11851                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
11852                     sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);\r
11853                 } else {\r
11854                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
11855                 }\r
11856                 gameInfo.resultDetails = buf;\r
11857             }\r
11858             RemoveInputSource(cps->isr);\r
11859             DisplayFatalError(buf, 0, 1);\r
11860         } else {\r
11861             sprintf(buf,\r
11862                     "Error reading from %s chess program (%s)",\r
11863                     cps->which, cps->program);\r
11864             RemoveInputSource(cps->isr);\r
11865 \r
11866             /* [AS] Program is misbehaving badly... kill it */\r
11867             if( count == -2 ) {\r
11868                 DestroyChildProcess( cps->pr, 9 );\r
11869                 cps->pr = NoProc;\r
11870             }\r
11871 \r
11872             DisplayFatalError(buf, error, 1);\r
11873         }\r
11874         return;\r
11875     }\r
11876     \r
11877     if ((end_str = strchr(message, '\r')) != NULL)\r
11878       *end_str = NULLCHAR;\r
11879     if ((end_str = strchr(message, '\n')) != NULL)\r
11880       *end_str = NULLCHAR;\r
11881     \r
11882     if (appData.debugMode) {\r
11883         TimeMark now; int print = 1;\r
11884         char *quote = ""; char c; int i;\r
11885 \r
11886         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */\r
11887                 char start = message[0];\r
11888                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing\r
11889                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && \r
11890                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&\r
11891                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&\r
11892                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&\r
11893                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&\r
11894                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')\r
11895                         { quote = "# "; print = (appData.engineComments == 2); }\r
11896                 message[0] = start; // restore original message\r
11897         }\r
11898         if(print) {\r
11899                 GetTimeMark(&now);\r
11900                 fprintf(debugFP, "%ld <%-6s: %s%s\n", \r
11901                         SubtractTimeMarks(&now, &programStartTime), cps->which, \r
11902                         quote,\r
11903                         message);\r
11904         }\r
11905     }\r
11906     HandleMachineMove(message, cps);\r
11907 }\r
11908 \r
11909 \r
11910 void\r
11911 SendTimeControl(cps, mps, tc, inc, sd, st)\r
11912      ChessProgramState *cps;\r
11913      int mps, inc, sd, st;\r
11914      long tc;\r
11915 {\r
11916     char buf[MSG_SIZ];\r
11917     int seconds;\r
11918 \r
11919     if( timeControl_2 > 0 ) {\r
11920         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {\r
11921             tc = timeControl_2;\r
11922         }\r
11923     }\r
11924     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */\r
11925     inc /= cps->timeOdds;\r
11926     st  /= cps->timeOdds;\r
11927 \r
11928     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */\r
11929 \r
11930     if (st > 0) {\r
11931       /* Set exact time per move, normally using st command */\r
11932       if (cps->stKludge) {\r
11933         /* GNU Chess 4 has no st command; uses level in a nonstandard way */\r
11934         seconds = st % 60;\r
11935         if (seconds == 0) {\r
11936           sprintf(buf, "level 1 %d\n", st/60);\r
11937         } else {\r
11938           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);\r
11939         }\r
11940       } else {\r
11941         sprintf(buf, "st %d\n", st);\r
11942       }\r
11943     } else {\r
11944       /* Set conventional or incremental time control, using level command */\r
11945       if (seconds == 0) {\r
11946         /* Note old gnuchess bug -- minutes:seconds used to not work.\r
11947            Fixed in later versions, but still avoid :seconds\r
11948            when seconds is 0. */\r
11949         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);\r
11950       } else {\r
11951         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,\r
11952                 seconds, inc/1000);\r
11953       }\r
11954     }\r
11955     SendToProgram(buf, cps);\r
11956 \r
11957     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */\r
11958     /* Orthogonally, limit search to given depth */\r
11959     if (sd > 0) {\r
11960       if (cps->sdKludge) {\r
11961         sprintf(buf, "depth\n%d\n", sd);\r
11962       } else {\r
11963         sprintf(buf, "sd %d\n", sd);\r
11964       }\r
11965       SendToProgram(buf, cps);\r
11966     }\r
11967 \r
11968     if(cps->nps > 0) { /* [HGM] nps */\r
11969         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!\r
11970         else {\r
11971                 sprintf(buf, "nps %d\n", cps->nps);\r
11972               SendToProgram(buf, cps);\r
11973         }\r
11974     }\r
11975 }\r
11976 \r
11977 ChessProgramState *WhitePlayer()\r
11978 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */\r
11979 {\r
11980     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || \r
11981        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)\r
11982         return &second;\r
11983     return &first;\r
11984 }\r
11985 \r
11986 void\r
11987 SendTimeRemaining(cps, machineWhite)\r
11988      ChessProgramState *cps;\r
11989      int /*boolean*/ machineWhite;\r
11990 {\r
11991     char message[MSG_SIZ];\r
11992     long time, otime;\r
11993 \r
11994     /* Note: this routine must be called when the clocks are stopped\r
11995        or when they have *just* been set or switched; otherwise\r
11996        it will be off by the time since the current tick started.\r
11997     */\r
11998     if (machineWhite) {\r
11999         time = whiteTimeRemaining / 10;\r
12000         otime = blackTimeRemaining / 10;\r
12001     } else {\r
12002         time = blackTimeRemaining / 10;\r
12003         otime = whiteTimeRemaining / 10;\r
12004     }\r
12005     /* [HGM] translate opponent's time by time-odds factor */\r
12006     otime = (otime * cps->other->timeOdds) / cps->timeOdds;\r
12007     if (appData.debugMode) {\r
12008         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);\r
12009     }\r
12010 \r
12011     if (time <= 0) time = 1;\r
12012     if (otime <= 0) otime = 1;\r
12013     \r
12014     sprintf(message, "time %ld\n", time);\r
12015     SendToProgram(message, cps);\r
12016 \r
12017     sprintf(message, "otim %ld\n", otime);\r
12018     SendToProgram(message, cps);\r
12019 }\r
12020 \r
12021 int\r
12022 BoolFeature(p, name, loc, cps)\r
12023      char **p;\r
12024      char *name;\r
12025      int *loc;\r
12026      ChessProgramState *cps;\r
12027 {\r
12028   char buf[MSG_SIZ];\r
12029   int len = strlen(name);\r
12030   int val;\r
12031   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
12032     (*p) += len + 1;\r
12033     sscanf(*p, "%d", &val);\r
12034     *loc = (val != 0);\r
12035     while (**p && **p != ' ') (*p)++;\r
12036     sprintf(buf, "accepted %s\n", name);\r
12037     SendToProgram(buf, cps);\r
12038     return TRUE;\r
12039   }\r
12040   return FALSE;\r
12041 }\r
12042 \r
12043 int\r
12044 IntFeature(p, name, loc, cps)\r
12045      char **p;\r
12046      char *name;\r
12047      int *loc;\r
12048      ChessProgramState *cps;\r
12049 {\r
12050   char buf[MSG_SIZ];\r
12051   int len = strlen(name);\r
12052   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
12053     (*p) += len + 1;\r
12054     sscanf(*p, "%d", loc);\r
12055     while (**p && **p != ' ') (*p)++;\r
12056     sprintf(buf, "accepted %s\n", name);\r
12057     SendToProgram(buf, cps);\r
12058     return TRUE;\r
12059   }\r
12060   return FALSE;\r
12061 }\r
12062 \r
12063 int\r
12064 StringFeature(p, name, loc, cps)\r
12065      char **p;\r
12066      char *name;\r
12067      char loc[];\r
12068      ChessProgramState *cps;\r
12069 {\r
12070   char buf[MSG_SIZ];\r
12071   int len = strlen(name);\r
12072   if (strncmp((*p), name, len) == 0\r
12073       && (*p)[len] == '=' && (*p)[len+1] == '\"') {\r
12074     (*p) += len + 2;\r
12075     sscanf(*p, "%[^\"]", loc);\r
12076     while (**p && **p != '\"') (*p)++;\r
12077     if (**p == '\"') (*p)++;\r
12078     sprintf(buf, "accepted %s\n", name);\r
12079     SendToProgram(buf, cps);\r
12080     return TRUE;\r
12081   }\r
12082   return FALSE;\r
12083 }\r
12084 \r
12085 void\r
12086 FeatureDone(cps, val)\r
12087      ChessProgramState* cps;\r
12088      int val;\r
12089 {\r
12090   DelayedEventCallback cb = GetDelayedEvent();\r
12091   if ((cb == InitBackEnd3 && cps == &first) ||\r
12092       (cb == TwoMachinesEventIfReady && cps == &second)) {\r
12093     CancelDelayedEvent();\r
12094     ScheduleDelayedEvent(cb, val ? 1 : 3600000);\r
12095   }\r
12096   cps->initDone = val;\r
12097 }\r
12098 \r
12099 /* Parse feature command from engine */\r
12100 void\r
12101 ParseFeatures(args, cps)\r
12102      char* args;\r
12103      ChessProgramState *cps;  \r
12104 {\r
12105   char *p = args;\r
12106   char *q;\r
12107   int val;\r
12108   char buf[MSG_SIZ];\r
12109 \r
12110   for (;;) {\r
12111     while (*p == ' ') p++;\r
12112     if (*p == NULLCHAR) return;\r
12113 \r
12114     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;\r
12115     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    \r
12116     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    \r
12117     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    \r
12118     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    \r
12119     if (BoolFeature(&p, "reuse", &val, cps)) {\r
12120       /* Engine can disable reuse, but can't enable it if user said no */\r
12121       if (!val) cps->reuse = FALSE;\r
12122       continue;\r
12123     }\r
12124     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;\r
12125     if (StringFeature(&p, "myname", &cps->tidy, cps)) {\r
12126       if (gameMode == TwoMachinesPlay) {\r
12127         DisplayTwoMachinesTitle();\r
12128       } else {\r
12129         DisplayTitle("");\r
12130       }\r
12131       continue;\r
12132     }\r
12133     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;\r
12134     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;\r
12135     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;\r
12136     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;\r
12137     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;\r
12138     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;\r
12139     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;\r
12140     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;\r
12141     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */\r
12142     if (IntFeature(&p, "done", &val, cps)) {\r
12143       FeatureDone(cps, val);\r
12144       continue;\r
12145     }\r
12146     /* Added by Tord: */\r
12147     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;\r
12148     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;\r
12149     /* End of additions by Tord */\r
12150 \r
12151     /* [HGM] added features: */\r
12152     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;\r
12153     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;\r
12154     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;\r
12155     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;\r
12156     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;\r
12157     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;\r
12158     /* End of additions by HGM */\r
12159 \r
12160     /* unknown feature: complain and skip */\r
12161     q = p;\r
12162     while (*q && *q != '=') q++;\r
12163     sprintf(buf, "rejected %.*s\n", q-p, p);\r
12164     SendToProgram(buf, cps);\r
12165     p = q;\r
12166     if (*p == '=') {\r
12167       p++;\r
12168       if (*p == '\"') {\r
12169         p++;\r
12170         while (*p && *p != '\"') p++;\r
12171         if (*p == '\"') p++;\r
12172       } else {\r
12173         while (*p && *p != ' ') p++;\r
12174       }\r
12175     }\r
12176   }\r
12177 \r
12178 }\r
12179 \r
12180 void\r
12181 PeriodicUpdatesEvent(newState)\r
12182      int newState;\r
12183 {\r
12184     if (newState == appData.periodicUpdates)\r
12185       return;\r
12186 \r
12187     appData.periodicUpdates=newState;\r
12188 \r
12189     /* Display type changes, so update it now */\r
12190     DisplayAnalysis();\r
12191 \r
12192     /* Get the ball rolling again... */\r
12193     if (newState) {\r
12194         AnalysisPeriodicEvent(1);\r
12195         StartAnalysisClock();\r
12196     }\r
12197 }\r
12198 \r
12199 void\r
12200 PonderNextMoveEvent(newState)\r
12201      int newState;\r
12202 {\r
12203     if (newState == appData.ponderNextMove) return;\r
12204     if (gameMode == EditPosition) EditPositionDone();\r
12205     if (newState) {\r
12206         SendToProgram("hard\n", &first);\r
12207         if (gameMode == TwoMachinesPlay) {\r
12208             SendToProgram("hard\n", &second);\r
12209         }\r
12210     } else {\r
12211         SendToProgram("easy\n", &first);\r
12212         thinkOutput[0] = NULLCHAR;\r
12213         if (gameMode == TwoMachinesPlay) {\r
12214             SendToProgram("easy\n", &second);\r
12215         }\r
12216     }\r
12217     appData.ponderNextMove = newState;\r
12218 }\r
12219 \r
12220 void\r
12221 NewSettingEvent(option, command, value)\r
12222      char *command;\r
12223      int option, value;\r
12224 {\r
12225     char buf[MSG_SIZ];\r
12226 \r
12227     if (gameMode == EditPosition) EditPositionDone();\r
12228     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);\r
12229     SendToProgram(buf, &first);\r
12230     if (gameMode == TwoMachinesPlay) {\r
12231         SendToProgram(buf, &second);\r
12232     }\r
12233 }\r
12234 \r
12235 void\r
12236 ShowThinkingEvent()\r
12237 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup\r
12238 {\r
12239     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated\r
12240     int newState = appData.showThinking\r
12241         // [HGM] thinking: other features now need thinking output as well\r
12242         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();\r
12243     \r
12244     if (oldState == newState) return;\r
12245     oldState = newState;\r
12246     if (gameMode == EditPosition) EditPositionDone();\r
12247     if (oldState) {\r
12248         SendToProgram("post\n", &first);\r
12249         if (gameMode == TwoMachinesPlay) {\r
12250             SendToProgram("post\n", &second);\r
12251         }\r
12252     } else {\r
12253         SendToProgram("nopost\n", &first);\r
12254         thinkOutput[0] = NULLCHAR;\r
12255         if (gameMode == TwoMachinesPlay) {\r
12256             SendToProgram("nopost\n", &second);\r
12257         }\r
12258     }\r
12259 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!\r
12260 }\r
12261 \r
12262 void\r
12263 AskQuestionEvent(title, question, replyPrefix, which)\r
12264      char *title; char *question; char *replyPrefix; char *which;\r
12265 {\r
12266   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;\r
12267   if (pr == NoProc) return;\r
12268   AskQuestion(title, question, replyPrefix, pr);\r
12269 }\r
12270 \r
12271 void\r
12272 DisplayMove(moveNumber)\r
12273      int moveNumber;\r
12274 {\r
12275     char message[MSG_SIZ];\r
12276     char res[MSG_SIZ];\r
12277     char cpThinkOutput[MSG_SIZ];\r
12278 \r
12279     if (moveNumber == forwardMostMove - 1 || \r
12280         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
12281 \r
12282         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));\r
12283 \r
12284         if (strchr(cpThinkOutput, '\n')) {\r
12285             *strchr(cpThinkOutput, '\n') = NULLCHAR;\r
12286         }\r
12287     } else {\r
12288         *cpThinkOutput = NULLCHAR;\r
12289     }\r
12290 \r
12291     /* [AS] Hide thinking from human user */\r
12292     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {\r
12293         *cpThinkOutput = NULLCHAR;\r
12294         if( thinkOutput[0] != NULLCHAR ) {\r
12295             int i;\r
12296 \r
12297             for( i=0; i<=hiddenThinkOutputState; i++ ) {\r
12298                 cpThinkOutput[i] = '.';\r
12299             }\r
12300             cpThinkOutput[i] = NULLCHAR;\r
12301             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;\r
12302         }\r
12303     }\r
12304 \r
12305     if (moveNumber == forwardMostMove - 1 &&\r
12306         gameInfo.resultDetails != NULL) {\r
12307         if (gameInfo.resultDetails[0] == NULLCHAR) {\r
12308             sprintf(res, " %s", PGNResult(gameInfo.result));\r
12309         } else {\r
12310             sprintf(res, " {%s} %s",\r
12311                     gameInfo.resultDetails, PGNResult(gameInfo.result));\r
12312         }\r
12313     } else {\r
12314         res[0] = NULLCHAR;\r
12315     }\r
12316     \r
12317     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
12318         DisplayMessage(res, cpThinkOutput);\r
12319     } else {\r
12320         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,\r
12321                 WhiteOnMove(moveNumber) ? " " : ".. ",\r
12322                 parseList[moveNumber], res);\r
12323         DisplayMessage(message, cpThinkOutput);\r
12324     }\r
12325 }\r
12326 \r
12327 void\r
12328 DisplayAnalysisText(text)\r
12329      char *text;\r
12330 {\r
12331     char buf[MSG_SIZ];\r
12332 \r
12333     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
12334         sprintf(buf, "Analysis (%s)", first.tidy);\r
12335         AnalysisPopUp(buf, text);\r
12336     }\r
12337 }\r
12338 \r
12339 static int\r
12340 only_one_move(str)\r
12341      char *str;\r
12342 {\r
12343     while (*str && isspace(*str)) ++str;\r
12344     while (*str && !isspace(*str)) ++str;\r
12345     if (!*str) return 1;\r
12346     while (*str && isspace(*str)) ++str;\r
12347     if (!*str) return 1;\r
12348     return 0;\r
12349 }\r
12350 \r
12351 void\r
12352 DisplayAnalysis()\r
12353 {\r
12354     char buf[MSG_SIZ];\r
12355     char lst[MSG_SIZ / 2];\r
12356     double nps;\r
12357     static char *xtra[] = { "", " (--)", " (++)" };\r
12358     int h, m, s, cs;\r
12359   \r
12360     if (programStats.time == 0) {\r
12361         programStats.time = 1;\r
12362     }\r
12363   \r
12364     if (programStats.got_only_move) {\r
12365         safeStrCpy(buf, programStats.movelist, sizeof(buf));\r
12366     } else {\r
12367         safeStrCpy( lst, programStats.movelist, sizeof(lst));\r
12368 \r
12369         nps = (((double)programStats.nodes) /\r
12370                (((double)programStats.time)/100.0));\r
12371 \r
12372         cs = programStats.time % 100;\r
12373         s = programStats.time / 100;\r
12374         h = (s / (60*60));\r
12375         s = s - h*60*60;\r
12376         m = (s/60);\r
12377         s = s - m*60;\r
12378 \r
12379         if (programStats.moves_left > 0 && appData.periodicUpdates) {\r
12380           if (programStats.move_name[0] != NULLCHAR) {\r
12381             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12382                     programStats.depth,\r
12383                     programStats.nr_moves-programStats.moves_left,\r
12384                     programStats.nr_moves, programStats.move_name,\r
12385                     ((float)programStats.score)/100.0, lst,\r
12386                     only_one_move(lst)?\r
12387                     xtra[programStats.got_fail] : "",\r
12388                     programStats.nodes, (int)nps, h, m, s, cs);\r
12389           } else {\r
12390             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12391                     programStats.depth,\r
12392                     programStats.nr_moves-programStats.moves_left,\r
12393                     programStats.nr_moves, ((float)programStats.score)/100.0,\r
12394                     lst,\r
12395                     only_one_move(lst)?\r
12396                     xtra[programStats.got_fail] : "",\r
12397                     programStats.nodes, (int)nps, h, m, s, cs);\r
12398           }\r
12399         } else {\r
12400             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12401                     programStats.depth,\r
12402                     ((float)programStats.score)/100.0,\r
12403                     lst,\r
12404                     only_one_move(lst)?\r
12405                     xtra[programStats.got_fail] : "",\r
12406                     programStats.nodes, (int)nps, h, m, s, cs);\r
12407         }\r
12408     }\r
12409     DisplayAnalysisText(buf);\r
12410 }\r
12411 \r
12412 void\r
12413 DisplayComment(moveNumber, text)\r
12414      int moveNumber;\r
12415      char *text;\r
12416 {\r
12417     char title[MSG_SIZ];\r
12418     char buf[8000]; // comment can be long!\r
12419     int score, depth;\r
12420 \r
12421     if( appData.autoDisplayComment ) {\r
12422         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
12423             strcpy(title, "Comment");\r
12424         } else {\r
12425             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,\r
12426                     WhiteOnMove(moveNumber) ? " " : ".. ",\r
12427                     parseList[moveNumber]);\r
12428         }\r
12429     } else title[0] = 0;\r
12430 \r
12431     // [HGM] PV info: display PV info together with (or as) comment\r
12432     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {\r
12433         if(text == NULL) text = "";                                           \r
12434         score = pvInfoList[moveNumber].score;\r
12435         sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,\r
12436                               depth, (pvInfoList[moveNumber].time+50)/100, text);\r
12437         CommentPopUp(title, buf);\r
12438     } else\r
12439     if (text != NULL)\r
12440         CommentPopUp(title, text);\r
12441 }\r
12442 \r
12443 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it\r
12444  * might be busy thinking or pondering.  It can be omitted if your\r
12445  * gnuchess is configured to stop thinking immediately on any user\r
12446  * input.  However, that gnuchess feature depends on the FIONREAD\r
12447  * ioctl, which does not work properly on some flavors of Unix.\r
12448  */\r
12449 void\r
12450 Attention(cps)\r
12451      ChessProgramState *cps;\r
12452 {\r
12453 #if ATTENTION\r
12454     if (!cps->useSigint) return;\r
12455     if (appData.noChessProgram || (cps->pr == NoProc)) return;\r
12456     switch (gameMode) {\r
12457       case MachinePlaysWhite:\r
12458       case MachinePlaysBlack:\r
12459       case TwoMachinesPlay:\r
12460       case IcsPlayingWhite:\r
12461       case IcsPlayingBlack:\r
12462       case AnalyzeMode:\r
12463       case AnalyzeFile:\r
12464         /* Skip if we know it isn't thinking */\r
12465         if (!cps->maybeThinking) return;\r
12466         if (appData.debugMode)\r
12467           fprintf(debugFP, "Interrupting %s\n", cps->which);\r
12468         InterruptChildProcess(cps->pr);\r
12469         cps->maybeThinking = FALSE;\r
12470         break;\r
12471       default:\r
12472         break;\r
12473     }\r
12474 #endif /*ATTENTION*/\r
12475 }\r
12476 \r
12477 int\r
12478 CheckFlags()\r
12479 {\r
12480     if (whiteTimeRemaining <= 0) {\r
12481         if (!whiteFlag) {\r
12482             whiteFlag = TRUE;\r
12483             if (appData.icsActive) {\r
12484                 if (appData.autoCallFlag &&\r
12485                     gameMode == IcsPlayingBlack && !blackFlag) {\r
12486                   SendToICS(ics_prefix);\r
12487                   SendToICS("flag\n");\r
12488                 }\r
12489             } else {\r
12490                 if (blackFlag) {\r
12491                     if(gameMode != TwoMachinesPlay) DisplayTitle("Both flags fell");\r
12492                 } else {\r
12493                     if(gameMode != TwoMachinesPlay) DisplayTitle("White's flag fell");\r
12494                     if (appData.autoCallFlag) {\r
12495                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);\r
12496                         return TRUE;\r
12497                     }\r
12498                 }\r
12499             }\r
12500         }\r
12501     }\r
12502     if (blackTimeRemaining <= 0) {\r
12503         if (!blackFlag) {\r
12504             blackFlag = TRUE;\r
12505             if (appData.icsActive) {\r
12506                 if (appData.autoCallFlag &&\r
12507                     gameMode == IcsPlayingWhite && !whiteFlag) {\r
12508                   SendToICS(ics_prefix);\r
12509                   SendToICS("flag\n");\r
12510                 }\r
12511             } else {\r
12512                 if (whiteFlag) {\r
12513                     if(gameMode != TwoMachinesPlay) DisplayTitle("Both flags fell");\r
12514                 } else {\r
12515                     if(gameMode != TwoMachinesPlay) DisplayTitle("Black's flag fell");\r
12516                     if (appData.autoCallFlag) {\r
12517                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);\r
12518                         return TRUE;\r
12519                     }\r
12520                 }\r
12521             }\r
12522         }\r
12523     }\r
12524     return FALSE;\r
12525 }\r
12526 \r
12527 void\r
12528 CheckTimeControl()\r
12529 {\r
12530     if (!appData.clockMode || appData.icsActive ||\r
12531         gameMode == PlayFromGameFile || forwardMostMove == 0) return;\r
12532 \r
12533     /*\r
12534      * add time to clocks when time control is achieved ([HGM] now also used for increment)\r
12535      */\r
12536     if ( !WhiteOnMove(forwardMostMove) )\r
12537         /* White made time control */\r
12538         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
12539         /* [HGM] time odds: correct new time quota for time odds! */\r
12540                                             / WhitePlayer()->timeOdds;\r
12541       else\r
12542         /* Black made time control */\r
12543         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
12544                                             / WhitePlayer()->other->timeOdds;\r
12545 }\r
12546 \r
12547 void\r
12548 DisplayBothClocks()\r
12549 {\r
12550     int wom = gameMode == EditPosition ?\r
12551       !blackPlaysFirst : WhiteOnMove(currentMove);\r
12552     DisplayWhiteClock(whiteTimeRemaining, wom);\r
12553     DisplayBlackClock(blackTimeRemaining, !wom);\r
12554 }\r
12555 \r
12556 \r
12557 /* Timekeeping seems to be a portability nightmare.  I think everyone\r
12558    has ftime(), but I'm really not sure, so I'm including some ifdefs\r
12559    to use other calls if you don't.  Clocks will be less accurate if\r
12560    you have neither ftime nor gettimeofday.\r
12561 */\r
12562 \r
12563 /* Get the current time as a TimeMark */\r
12564 void\r
12565 GetTimeMark(tm)\r
12566      TimeMark *tm;\r
12567 {\r
12568 #if HAVE_GETTIMEOFDAY\r
12569 \r
12570     struct timeval timeVal;\r
12571     struct timezone timeZone;\r
12572 \r
12573     gettimeofday(&timeVal, &timeZone);\r
12574     tm->sec = (long) timeVal.tv_sec; \r
12575     tm->ms = (int) (timeVal.tv_usec / 1000L);\r
12576 \r
12577 #else /*!HAVE_GETTIMEOFDAY*/\r
12578 #if HAVE_FTIME\r
12579 \r
12580 #include <sys/timeb.h>\r
12581     struct timeb timeB;\r
12582 \r
12583     ftime(&timeB);\r
12584     tm->sec = (long) timeB.time;\r
12585     tm->ms = (int) timeB.millitm;\r
12586 \r
12587 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/\r
12588     tm->sec = (long) time(NULL);\r
12589     tm->ms = 0;\r
12590 #endif\r
12591 #endif\r
12592 }\r
12593 \r
12594 /* Return the difference in milliseconds between two\r
12595    time marks.  We assume the difference will fit in a long!\r
12596 */\r
12597 long\r
12598 SubtractTimeMarks(tm2, tm1)\r
12599      TimeMark *tm2, *tm1;\r
12600 {\r
12601     return 1000L*(tm2->sec - tm1->sec) +\r
12602            (long) (tm2->ms - tm1->ms);\r
12603 }\r
12604 \r
12605 \r
12606 /*\r
12607  * Code to manage the game clocks.\r
12608  *\r
12609  * In tournament play, black starts the clock and then white makes a move.\r
12610  * We give the human user a slight advantage if he is playing white---the\r
12611  * clocks don't run until he makes his first move, so it takes zero time.\r
12612  * Also, we don't account for network lag, so we could get out of sync\r
12613  * with GNU Chess's clock -- but then, referees are always right.  \r
12614  */\r
12615 \r
12616 static TimeMark tickStartTM;\r
12617 static long intendedTickLength;\r
12618 \r
12619 long\r
12620 NextTickLength(timeRemaining)\r
12621      long timeRemaining;\r
12622 {\r
12623     long nominalTickLength, nextTickLength;\r
12624 \r
12625     if (timeRemaining > 0L && timeRemaining <= 10000L)\r
12626       nominalTickLength = 100L;\r
12627     else\r
12628       nominalTickLength = 1000L;\r
12629     nextTickLength = timeRemaining % nominalTickLength;\r
12630     if (nextTickLength <= 0) nextTickLength += nominalTickLength;\r
12631 \r
12632     return nextTickLength;\r
12633 }\r
12634 \r
12635 /* Adjust clock one minute up or down */\r
12636 void\r
12637 AdjustClock(Boolean which, int dir)\r
12638 {\r
12639     if(which) blackTimeRemaining += 60000*dir;\r
12640     else      whiteTimeRemaining += 60000*dir;\r
12641     DisplayBothClocks();\r
12642 }\r
12643 \r
12644 /* Stop clocks and reset to a fresh time control */\r
12645 void\r
12646 ResetClocks() \r
12647 {\r
12648     (void) StopClockTimer();\r
12649     if (appData.icsActive) {\r
12650         whiteTimeRemaining = blackTimeRemaining = 0;\r
12651     } else { /* [HGM] correct new time quote for time odds */\r
12652         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;\r
12653         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;\r
12654     }\r
12655     if (whiteFlag || blackFlag) {\r
12656         DisplayTitle("");\r
12657         whiteFlag = blackFlag = FALSE;\r
12658     }\r
12659     DisplayBothClocks();\r
12660 }\r
12661 \r
12662 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */\r
12663 \r
12664 /* Decrement running clock by amount of time that has passed */\r
12665 void\r
12666 DecrementClocks()\r
12667 {\r
12668     long timeRemaining;\r
12669     long lastTickLength, fudge;\r
12670     TimeMark now;\r
12671 \r
12672     if (!appData.clockMode) return;\r
12673     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;\r
12674         \r
12675     GetTimeMark(&now);\r
12676 \r
12677     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
12678 \r
12679     /* Fudge if we woke up a little too soon */\r
12680     fudge = intendedTickLength - lastTickLength;\r
12681     if (fudge < 0 || fudge > FUDGE) fudge = 0;\r
12682 \r
12683     if (WhiteOnMove(forwardMostMove)) {\r
12684         if(whiteNPS >= 0) lastTickLength = 0;\r
12685         timeRemaining = whiteTimeRemaining -= lastTickLength;\r
12686         DisplayWhiteClock(whiteTimeRemaining - fudge,\r
12687                           WhiteOnMove(currentMove));\r
12688     } else {\r
12689         if(blackNPS >= 0) lastTickLength = 0;\r
12690         timeRemaining = blackTimeRemaining -= lastTickLength;\r
12691         DisplayBlackClock(blackTimeRemaining - fudge,\r
12692                           !WhiteOnMove(currentMove));\r
12693     }\r
12694 \r
12695     if (CheckFlags()) return;\r
12696         \r
12697     tickStartTM = now;\r
12698     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;\r
12699     StartClockTimer(intendedTickLength);\r
12700 \r
12701     /* if the time remaining has fallen below the alarm threshold, sound the\r
12702      * alarm. if the alarm has sounded and (due to a takeback or time control\r
12703      * with increment) the time remaining has increased to a level above the\r
12704      * threshold, reset the alarm so it can sound again. \r
12705      */\r
12706     \r
12707     if (appData.icsActive && appData.icsAlarm) {\r
12708 \r
12709         /* make sure we are dealing with the user's clock */\r
12710         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||\r
12711                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))\r
12712            )) return;\r
12713 \r
12714         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {\r
12715             alarmSounded = FALSE;\r
12716         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { \r
12717             PlayAlarmSound();\r
12718             alarmSounded = TRUE;\r
12719         }\r
12720     }\r
12721 }\r
12722 \r
12723 \r
12724 /* A player has just moved, so stop the previously running\r
12725    clock and (if in clock mode) start the other one.\r
12726    We redisplay both clocks in case we're in ICS mode, because\r
12727    ICS gives us an update to both clocks after every move.\r
12728    Note that this routine is called *after* forwardMostMove\r
12729    is updated, so the last fractional tick must be subtracted\r
12730    from the color that is *not* on move now.\r
12731 */\r
12732 void\r
12733 SwitchClocks()\r
12734 {\r
12735     long lastTickLength;\r
12736     TimeMark now;\r
12737     int flagged = FALSE;\r
12738 \r
12739     GetTimeMark(&now);\r
12740 \r
12741     if (StopClockTimer() && appData.clockMode) {\r
12742         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
12743         if (WhiteOnMove(forwardMostMove)) {\r
12744             if(blackNPS >= 0) lastTickLength = 0;\r
12745             blackTimeRemaining -= lastTickLength;\r
12746            /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
12747 //         if(pvInfoList[forwardMostMove-1].time == -1)\r
12748                  pvInfoList[forwardMostMove-1].time =               // use GUI time\r
12749                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;\r
12750         } else {\r
12751            if(whiteNPS >= 0) lastTickLength = 0;\r
12752            whiteTimeRemaining -= lastTickLength;\r
12753            /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
12754 //         if(pvInfoList[forwardMostMove-1].time == -1)\r
12755                  pvInfoList[forwardMostMove-1].time = \r
12756                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;\r
12757         }\r
12758         flagged = CheckFlags();\r
12759     }\r
12760     CheckTimeControl();\r
12761 \r
12762     if (flagged || !appData.clockMode) return;\r
12763 \r
12764     switch (gameMode) {\r
12765       case MachinePlaysBlack:\r
12766       case MachinePlaysWhite:\r
12767       case BeginningOfGame:\r
12768         if (pausing) return;\r
12769         break;\r
12770 \r
12771       case EditGame:\r
12772       case PlayFromGameFile:\r
12773       case IcsExamining:\r
12774         return;\r
12775 \r
12776       default:\r
12777         break;\r
12778     }\r
12779 \r
12780     tickStartTM = now;\r
12781     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
12782       whiteTimeRemaining : blackTimeRemaining);\r
12783     StartClockTimer(intendedTickLength);\r
12784 }\r
12785         \r
12786 \r
12787 /* Stop both clocks */\r
12788 void\r
12789 StopClocks()\r
12790 {       \r
12791     long lastTickLength;\r
12792     TimeMark now;\r
12793 \r
12794     if (!StopClockTimer()) return;\r
12795     if (!appData.clockMode) return;\r
12796 \r
12797     GetTimeMark(&now);\r
12798 \r
12799     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
12800     if (WhiteOnMove(forwardMostMove)) {\r
12801         if(whiteNPS >= 0) lastTickLength = 0;\r
12802         whiteTimeRemaining -= lastTickLength;\r
12803         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));\r
12804     } else {\r
12805         if(blackNPS >= 0) lastTickLength = 0;\r
12806         blackTimeRemaining -= lastTickLength;\r
12807         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));\r
12808     }\r
12809     CheckFlags();\r
12810 }\r
12811         \r
12812 /* Start clock of player on move.  Time may have been reset, so\r
12813    if clock is already running, stop and restart it. */\r
12814 void\r
12815 StartClocks()\r
12816 {\r
12817     (void) StopClockTimer(); /* in case it was running already */\r
12818     DisplayBothClocks();\r
12819     if (CheckFlags()) return;\r
12820 \r
12821     if (!appData.clockMode) return;\r
12822     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;\r
12823 \r
12824     GetTimeMark(&tickStartTM);\r
12825     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
12826       whiteTimeRemaining : blackTimeRemaining);\r
12827 \r
12828    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */\r
12829     whiteNPS = blackNPS = -1; \r
12830     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'\r
12831        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white\r
12832         whiteNPS = first.nps;\r
12833     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'\r
12834        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black\r
12835         blackNPS = first.nps;\r
12836     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode\r
12837         whiteNPS = second.nps;\r
12838     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')\r
12839         blackNPS = second.nps;\r
12840     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);\r
12841 \r
12842     StartClockTimer(intendedTickLength);\r
12843 }\r
12844 \r
12845 char *\r
12846 TimeString(ms)\r
12847      long ms;\r
12848 {\r
12849     long second, minute, hour, day;\r
12850     char *sign = "";\r
12851     static char buf[32];\r
12852     \r
12853     if (ms > 0 && ms <= 9900) {\r
12854       /* convert milliseconds to tenths, rounding up */\r
12855       double tenths = floor( ((double)(ms + 99L)) / 100.00 );\r
12856 \r
12857       sprintf(buf, " %03.1f ", tenths/10.0);\r
12858       return buf;\r
12859     }\r
12860 \r
12861     /* convert milliseconds to seconds, rounding up */\r
12862     /* use floating point to avoid strangeness of integer division\r
12863        with negative dividends on many machines */\r
12864     second = (long) floor(((double) (ms + 999L)) / 1000.0);\r
12865 \r
12866     if (second < 0) {\r
12867         sign = "-";\r
12868         second = -second;\r
12869     }\r
12870     \r
12871     day = second / (60 * 60 * 24);\r
12872     second = second % (60 * 60 * 24);\r
12873     hour = second / (60 * 60);\r
12874     second = second % (60 * 60);\r
12875     minute = second / 60;\r
12876     second = second % 60;\r
12877     \r
12878     if (day > 0)\r
12879       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",\r
12880               sign, day, hour, minute, second);\r
12881     else if (hour > 0)\r
12882       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);\r
12883     else\r
12884       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);\r
12885     \r
12886     return buf;\r
12887 }\r
12888 \r
12889 \r
12890 /*\r
12891  * This is necessary because some C libraries aren't ANSI C compliant yet.\r
12892  */\r
12893 char *\r
12894 StrStr(string, match)\r
12895      char *string, *match;\r
12896 {\r
12897     int i, length;\r
12898     \r
12899     length = strlen(match);\r
12900     \r
12901     for (i = strlen(string) - length; i >= 0; i--, string++)\r
12902       if (!strncmp(match, string, length))\r
12903         return string;\r
12904     \r
12905     return NULL;\r
12906 }\r
12907 \r
12908 char *\r
12909 StrCaseStr(string, match)\r
12910      char *string, *match;\r
12911 {\r
12912     int i, j, length;\r
12913     \r
12914     length = strlen(match);\r
12915     \r
12916     for (i = strlen(string) - length; i >= 0; i--, string++) {\r
12917         for (j = 0; j < length; j++) {\r
12918             if (ToLower(match[j]) != ToLower(string[j]))\r
12919               break;\r
12920         }\r
12921         if (j == length) return string;\r
12922     }\r
12923 \r
12924     return NULL;\r
12925 }\r
12926 \r
12927 #ifndef _amigados\r
12928 int\r
12929 StrCaseCmp(s1, s2)\r
12930      char *s1, *s2;\r
12931 {\r
12932     char c1, c2;\r
12933     \r
12934     for (;;) {\r
12935         c1 = ToLower(*s1++);\r
12936         c2 = ToLower(*s2++);\r
12937         if (c1 > c2) return 1;\r
12938         if (c1 < c2) return -1;\r
12939         if (c1 == NULLCHAR) return 0;\r
12940     }\r
12941 }\r
12942 \r
12943 \r
12944 int\r
12945 ToLower(c)\r
12946      int c;\r
12947 {\r
12948     return isupper(c) ? tolower(c) : c;\r
12949 }\r
12950 \r
12951 \r
12952 int\r
12953 ToUpper(c)\r
12954      int c;\r
12955 {\r
12956     return islower(c) ? toupper(c) : c;\r
12957 }\r
12958 #endif /* !_amigados    */\r
12959 \r
12960 char *\r
12961 StrSave(s)\r
12962      char *s;\r
12963 {\r
12964     char *ret;\r
12965 \r
12966     if ((ret = (char *) malloc(strlen(s) + 1))) {\r
12967         strcpy(ret, s);\r
12968     }\r
12969     return ret;\r
12970 }\r
12971 \r
12972 char *\r
12973 StrSavePtr(s, savePtr)\r
12974      char *s, **savePtr;\r
12975 {\r
12976     if (*savePtr) {\r
12977         free(*savePtr);\r
12978     }\r
12979     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {\r
12980         strcpy(*savePtr, s);\r
12981     }\r
12982     return(*savePtr);\r
12983 }\r
12984 \r
12985 char *\r
12986 PGNDate()\r
12987 {\r
12988     time_t clock;\r
12989     struct tm *tm;\r
12990     char buf[MSG_SIZ];\r
12991 \r
12992     clock = time((time_t *)NULL);\r
12993     tm = localtime(&clock);\r
12994     sprintf(buf, "%04d.%02d.%02d",\r
12995             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);\r
12996     return StrSave(buf);\r
12997 }\r
12998 \r
12999 \r
13000 char *\r
13001 PositionToFEN(move, useFEN960)\r
13002      int move;\r
13003      int useFEN960;\r
13004 {\r
13005     int i, j, fromX, fromY, toX, toY;\r
13006     int whiteToPlay;\r
13007     char buf[128];\r
13008     char *p, *q;\r
13009     int emptycount;\r
13010     ChessSquare piece;\r
13011 \r
13012     whiteToPlay = (gameMode == EditPosition) ?\r
13013       !blackPlaysFirst : (move % 2 == 0);\r
13014     p = buf;\r
13015 \r
13016     /* Piece placement data */\r
13017     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
13018         emptycount = 0;\r
13019         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
13020             if (boards[move][i][j] == EmptySquare) {\r
13021                 emptycount++;\r
13022             } else { ChessSquare piece = boards[move][i][j];\r
13023                 if (emptycount > 0) {\r
13024                     if(emptycount<10) /* [HGM] can be >= 10 */\r
13025                         *p++ = '0' + emptycount;\r
13026                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
13027                     emptycount = 0;\r
13028                 }\r
13029                 if(PieceToChar(piece) == '+') {\r
13030                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */\r
13031                     *p++ = '+';\r
13032                     piece = (ChessSquare)(DEMOTED piece);\r
13033                 } \r
13034                 *p++ = PieceToChar(piece);\r
13035                 if(p[-1] == '~') {\r
13036                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */\r
13037                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));\r
13038                     *p++ = '~';\r
13039                 }\r
13040             }\r
13041         }\r
13042         if (emptycount > 0) {\r
13043             if(emptycount<10) /* [HGM] can be >= 10 */\r
13044                 *p++ = '0' + emptycount;\r
13045             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
13046             emptycount = 0;\r
13047         }\r
13048         *p++ = '/';\r
13049     }\r
13050     *(p - 1) = ' ';\r
13051 \r
13052     /* [HGM] print Crazyhouse or Shogi holdings */\r
13053     if( gameInfo.holdingsWidth ) {\r
13054         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */\r
13055         q = p;\r
13056         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */\r
13057             piece = boards[move][i][BOARD_WIDTH-1];\r
13058             if( piece != EmptySquare )\r
13059               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)\r
13060                   *p++ = PieceToChar(piece);\r
13061         }\r
13062         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */\r
13063             piece = boards[move][BOARD_HEIGHT-i-1][0];\r
13064             if( piece != EmptySquare )\r
13065               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)\r
13066                   *p++ = PieceToChar(piece);\r
13067         }\r
13068 \r
13069         if( q == p ) *p++ = '-';\r
13070         *p++ = ']';\r
13071         *p++ = ' ';\r
13072     }\r
13073 \r
13074     /* Active color */\r
13075     *p++ = whiteToPlay ? 'w' : 'b';\r
13076     *p++ = ' ';\r
13077 \r
13078   if(nrCastlingRights) {\r
13079      q = p;\r
13080      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {\r
13081        /* [HGM] write directly from rights */\r
13082            if(castlingRights[move][2] >= 0 &&\r
13083               castlingRights[move][0] >= 0   )\r
13084                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';\r
13085            if(castlingRights[move][2] >= 0 &&\r
13086               castlingRights[move][1] >= 0   )\r
13087                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';\r
13088            if(castlingRights[move][5] >= 0 &&\r
13089               castlingRights[move][3] >= 0   )\r
13090                 *p++ = castlingRights[move][3] + AAA;\r
13091            if(castlingRights[move][5] >= 0 &&\r
13092               castlingRights[move][4] >= 0   )\r
13093                 *p++ = castlingRights[move][4] + AAA;\r
13094      } else {\r
13095 \r
13096         /* [HGM] write true castling rights */\r
13097         if( nrCastlingRights == 6 ) {\r
13098             if(castlingRights[move][0] == BOARD_RGHT-1 &&\r
13099                castlingRights[move][2] >= 0  ) *p++ = 'K';\r
13100             if(castlingRights[move][1] == BOARD_LEFT &&\r
13101                castlingRights[move][2] >= 0  ) *p++ = 'Q';\r
13102             if(castlingRights[move][3] == BOARD_RGHT-1 &&\r
13103                castlingRights[move][5] >= 0  ) *p++ = 'k';\r
13104             if(castlingRights[move][4] == BOARD_LEFT &&\r
13105                castlingRights[move][5] >= 0  ) *p++ = 'q';\r
13106         }\r
13107      }\r
13108      if (q == p) *p++ = '-'; /* No castling rights */\r
13109      *p++ = ' ';\r
13110   }\r
13111 \r
13112   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
13113      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
13114     /* En passant target square */\r
13115     if (move > backwardMostMove) {\r
13116         fromX = moveList[move - 1][0] - AAA;\r
13117         fromY = moveList[move - 1][1] - ONE;\r
13118         toX = moveList[move - 1][2] - AAA;\r
13119         toY = moveList[move - 1][3] - ONE;\r
13120         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&\r
13121             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&\r
13122             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&\r
13123             fromX == toX) {\r
13124             /* 2-square pawn move just happened */\r
13125             *p++ = toX + AAA;\r
13126             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';\r
13127         } else {\r
13128             *p++ = '-';\r
13129         }\r
13130     } else {\r
13131         *p++ = '-';\r
13132     }\r
13133     *p++ = ' ';\r
13134   }\r
13135 \r
13136     /* [HGM] find reversible plies */\r
13137     {   int i = 0, j=move;\r
13138 \r
13139         if (appData.debugMode) { int k;\r
13140             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);\r
13141             for(k=backwardMostMove; k<=forwardMostMove; k++)\r
13142                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);\r
13143 \r
13144         }\r
13145 \r
13146         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;\r
13147         if( j == backwardMostMove ) i += initialRulePlies;\r
13148         sprintf(p, "%d ", i);\r
13149         p += i>=100 ? 4 : i >= 10 ? 3 : 2;\r
13150     }\r
13151     /* Fullmove number */\r
13152     sprintf(p, "%d", (move / 2) + 1);\r
13153     \r
13154     return StrSave(buf);\r
13155 }\r
13156 \r
13157 Boolean\r
13158 ParseFEN(board, blackPlaysFirst, fen)\r
13159     Board board;\r
13160      int *blackPlaysFirst;\r
13161      char *fen;\r
13162 {\r
13163     int i, j;\r
13164     char *p;\r
13165     int emptycount;\r
13166     ChessSquare piece;\r
13167 \r
13168     p = fen;\r
13169 \r
13170     /* [HGM] by default clear Crazyhouse holdings, if present */\r
13171     if(gameInfo.holdingsWidth) {\r
13172        for(i=0; i<BOARD_HEIGHT; i++) {\r
13173            board[i][0]             = EmptySquare; /* black holdings */\r
13174            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */\r
13175            board[i][1]             = (ChessSquare) 0; /* black counts */\r
13176            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */\r
13177        }\r
13178     }\r
13179 \r
13180     /* Piece placement data */\r
13181     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
13182         j = 0;\r
13183         for (;;) {\r
13184             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {\r
13185                 if (*p == '/') p++;\r
13186                 emptycount = gameInfo.boardWidth - j;\r
13187                 while (emptycount--)\r
13188                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13189                 break;\r
13190 #if(BOARD_SIZE >= 10)\r
13191             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */\r
13192                 p++; emptycount=10;\r
13193                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
13194                 while (emptycount--)\r
13195                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13196 #endif\r
13197             } else if (isdigit(*p)) {\r
13198                 emptycount = *p++ - '0';\r
13199                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */\r
13200                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
13201                 while (emptycount--)\r
13202                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13203             } else if (*p == '+' || isalpha(*p)) {\r
13204                 if (j >= gameInfo.boardWidth) return FALSE;\r
13205                 if(*p=='+') {\r
13206                     piece = CharToPiece(*++p);\r
13207                     if(piece == EmptySquare) return FALSE; /* unknown piece */\r
13208                     piece = (ChessSquare) (PROMOTED piece ); p++;\r
13209                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */\r
13210                 } else piece = CharToPiece(*p++);\r
13211 \r
13212                 if(piece==EmptySquare) return FALSE; /* unknown piece */\r
13213                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */\r
13214                     piece = (ChessSquare) (PROMOTED piece);\r
13215                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */\r
13216                     p++;\r
13217                 }\r
13218                 board[i][(j++)+gameInfo.holdingsWidth] = piece;\r
13219             } else {\r
13220                 return FALSE;\r
13221             }\r
13222         }\r
13223     }\r
13224     while (*p == '/' || *p == ' ') p++;\r
13225 \r
13226     /* [HGM] look for Crazyhouse holdings here */\r
13227     while(*p==' ') p++;\r
13228     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {\r
13229         if(*p == '[') p++;\r
13230         if(*p == '-' ) *p++; /* empty holdings */ else {\r
13231             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */\r
13232             /* if we would allow FEN reading to set board size, we would   */\r
13233             /* have to add holdings and shift the board read so far here   */\r
13234             while( (piece = CharToPiece(*p) ) != EmptySquare ) {\r
13235                 *p++;\r
13236                 if((int) piece >= (int) BlackPawn ) {\r
13237                     i = (int)piece - (int)BlackPawn;\r
13238                     i = PieceToNumber((ChessSquare)i);\r
13239                     if( i >= gameInfo.holdingsSize ) return FALSE;\r
13240                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */\r
13241                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */\r
13242                 } else {\r
13243                     i = (int)piece - (int)WhitePawn;\r
13244                     i = PieceToNumber((ChessSquare)i);\r
13245                     if( i >= gameInfo.holdingsSize ) return FALSE;\r
13246                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */\r
13247                     board[i][BOARD_WIDTH-2]++;          /* black holdings */\r
13248                 }\r
13249             }\r
13250         }\r
13251         if(*p == ']') *p++;\r
13252     }\r
13253 \r
13254     while(*p == ' ') p++;\r
13255 \r
13256     /* Active color */\r
13257     switch (*p++) {\r
13258       case 'w':\r
13259         *blackPlaysFirst = FALSE;\r
13260         break;\r
13261       case 'b': \r
13262         *blackPlaysFirst = TRUE;\r
13263         break;\r
13264       default:\r
13265         return FALSE;\r
13266     }\r
13267 \r
13268     /* [HGM] We NO LONGER ignore the rest of the FEN notation */\r
13269     /* return the extra info in global variiables             */\r
13270 \r
13271     /* set defaults in case FEN is incomplete */\r
13272     FENepStatus = EP_UNKNOWN;\r
13273     for(i=0; i<nrCastlingRights; i++ ) {\r
13274         FENcastlingRights[i] =\r
13275             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];\r
13276     }   /* assume possible unless obviously impossible */\r
13277     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;\r
13278     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;\r
13279     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;\r
13280     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;\r
13281     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;\r
13282     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;\r
13283     FENrulePlies = 0;\r
13284 \r
13285     while(*p==' ') p++;\r
13286     if(nrCastlingRights) {\r
13287       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {\r
13288           /* castling indicator present, so default becomes no castlings */\r
13289           for(i=0; i<nrCastlingRights; i++ ) {\r
13290                  FENcastlingRights[i] = -1;\r
13291           }\r
13292       }\r
13293       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||\r
13294              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&\r
13295              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||\r
13296              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {\r
13297         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;\r
13298 \r
13299         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {\r
13300             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;\r
13301             if(board[0             ][i] == WhiteKing) whiteKingFile = i;\r
13302         }\r
13303         switch(c) {\r
13304           case'K':\r
13305               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);\r
13306               FENcastlingRights[0] = i != whiteKingFile ? i : -1;\r
13307               FENcastlingRights[2] = whiteKingFile;\r
13308               break;\r
13309           case'Q':\r
13310               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);\r
13311               FENcastlingRights[1] = i != whiteKingFile ? i : -1;\r
13312               FENcastlingRights[2] = whiteKingFile;\r
13313               break;\r
13314           case'k':\r
13315               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);\r
13316               FENcastlingRights[3] = i != blackKingFile ? i : -1;\r
13317               FENcastlingRights[5] = blackKingFile;\r
13318               break;\r
13319           case'q':\r
13320               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);\r
13321               FENcastlingRights[4] = i != blackKingFile ? i : -1;\r
13322               FENcastlingRights[5] = blackKingFile;\r
13323           case '-':\r
13324               break;\r
13325           default: /* FRC castlings */\r
13326               if(c >= 'a') { /* black rights */\r
13327                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
13328                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;\r
13329                   if(i == BOARD_RGHT) break;\r
13330                   FENcastlingRights[5] = i;\r
13331                   c -= AAA;\r
13332                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||\r
13333                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;\r
13334                   if(c > i)\r
13335                       FENcastlingRights[3] = c;\r
13336                   else\r
13337                       FENcastlingRights[4] = c;\r
13338               } else { /* white rights */\r
13339                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
13340                     if(board[0][i] == WhiteKing) break;\r
13341                   if(i == BOARD_RGHT) break;\r
13342                   FENcastlingRights[2] = i;\r
13343                   c -= AAA - 'a' + 'A';\r
13344                   if(board[0][c] >= WhiteKing) break;\r
13345                   if(c > i)\r
13346                       FENcastlingRights[0] = c;\r
13347                   else\r
13348                       FENcastlingRights[1] = c;\r
13349               }\r
13350         }\r
13351       }\r
13352     if (appData.debugMode) {\r
13353         fprintf(debugFP, "FEN castling rights:");\r
13354         for(i=0; i<nrCastlingRights; i++)\r
13355         fprintf(debugFP, " %d", FENcastlingRights[i]);\r
13356         fprintf(debugFP, "\n");\r
13357     }\r
13358 \r
13359       while(*p==' ') p++;\r
13360     }\r
13361 \r
13362     /* read e.p. field in games that know e.p. capture */\r
13363     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
13364        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
13365       if(*p=='-') {\r
13366         p++; FENepStatus = EP_NONE;\r
13367       } else {\r
13368          char c = *p++ - AAA;\r
13369 \r
13370          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;\r
13371          if(*p >= '0' && *p <='9') *p++;\r
13372          FENepStatus = c;\r
13373       }\r
13374     }\r
13375 \r
13376 \r
13377     if(sscanf(p, "%d", &i) == 1) {\r
13378         FENrulePlies = i; /* 50-move ply counter */\r
13379         /* (The move number is still ignored)    */\r
13380     }\r
13381 \r
13382     return TRUE;\r
13383 }\r
13384       \r
13385 void\r
13386 EditPositionPasteFEN(char *fen)\r
13387 {\r
13388   if (fen != NULL) {\r
13389     Board initial_position;\r
13390 \r
13391     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {\r
13392       DisplayError("Bad FEN position in clipboard", 0);\r
13393       return ;\r
13394     } else {\r
13395       int savedBlackPlaysFirst = blackPlaysFirst;\r
13396       EditPositionEvent();\r
13397       blackPlaysFirst = savedBlackPlaysFirst;\r
13398       CopyBoard(boards[0], initial_position);\r
13399           /* [HGM] copy FEN attributes as well */\r
13400           {   int i;\r
13401               initialRulePlies = FENrulePlies;\r
13402               epStatus[0] = FENepStatus;\r
13403               for( i=0; i<nrCastlingRights; i++ )\r
13404                   castlingRights[0][i] = FENcastlingRights[i];\r
13405           }\r
13406       EditPositionDone();\r
13407       DisplayBothClocks();\r
13408       DrawPosition(FALSE, boards[currentMove]);\r
13409     }\r
13410   }\r
13411 }\r