fixed forteitary wins for special game variations
[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,\r
6  * Massachusetts.  Enhancements Copyright\r
7  * 1992-2001,2002,2003,2004,2005,2006,2007,2008,2009 Free Software\r
8  * Foundation, Inc.\r
9  *\r
10  * The following terms apply to Digital Equipment Corporation's copyright\r
11  * interest in XBoard:\r
12  * ------------------------------------------------------------------------\r
13  * All Rights Reserved\r
14  *\r
15  * Permission to use, copy, modify, and distribute this software and its\r
16  * documentation for any purpose and without fee is hereby granted,\r
17  * provided that the above copyright notice appear in all copies and that\r
18  * both that copyright notice and this permission notice appear in\r
19  * supporting documentation, and that the name of Digital not be\r
20  * used in advertising or publicity pertaining to distribution of the\r
21  * software without specific, written prior permission.\r
22  *\r
23  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING\r
24  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL\r
25  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR\r
26  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,\r
27  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,\r
28  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS\r
29  * SOFTWARE.\r
30  * ------------------------------------------------------------------------\r
31  *\r
32  * The following terms apply to the enhanced version of XBoard\r
33  * distributed by the Free Software Foundation:\r
34  * ------------------------------------------------------------------------\r
35  *\r
36  * GNU XBoard is free software: you can redistribute it and/or modify\r
37  * it under the terms of the GNU General Public License as published by\r
38  * the Free Software Foundation, either version 3 of the License, or (at\r
39  * your option) any later version.\r
40  *\r
41  * GNU XBoard is distributed in the hope that it will be useful, but\r
42  * WITHOUT ANY WARRANTY; without even the implied warranty of\r
43  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\r
44  * General Public License for more details.\r
45  *\r
46  * You should have received a copy of the GNU General Public License\r
47  * along with this program. If not, see http://www.gnu.org/licenses/.  *\r
48  *\r
49  *------------------------------------------------------------------------\r
50  ** See the file ChangeLog for a revision history.  */\r
51 \r
52 /* [AS] Also useful here for debugging */\r
53 #ifdef WIN32\r
54 #include <windows.h>\r
55 \r
56 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );\r
57 \r
58 #else\r
59 \r
60 #define DoSleep( n ) if( (n) >= 0) sleep(n)\r
61 \r
62 #endif\r
63 \r
64 #include "config.h"\r
65 \r
66 #include <assert.h>\r
67 #include <stdio.h>\r
68 #include <ctype.h>\r
69 #include <errno.h>\r
70 #include <sys/types.h>\r
71 #include <sys/stat.h>\r
72 #include <math.h>\r
73 #include <ctype.h>\r
74 \r
75 #if STDC_HEADERS\r
76 # include <stdlib.h>\r
77 # include <string.h>\r
78 #else /* not STDC_HEADERS */\r
79 # if HAVE_STRING_H\r
80 #  include <string.h>\r
81 # else /* not HAVE_STRING_H */\r
82 #  include <strings.h>\r
83 # endif /* not HAVE_STRING_H */\r
84 #endif /* not STDC_HEADERS */\r
85 \r
86 #if HAVE_SYS_FCNTL_H\r
87 # include <sys/fcntl.h>\r
88 #else /* not HAVE_SYS_FCNTL_H */\r
89 # if HAVE_FCNTL_H\r
90 #  include <fcntl.h>\r
91 # endif /* HAVE_FCNTL_H */\r
92 #endif /* not HAVE_SYS_FCNTL_H */\r
93 \r
94 #if TIME_WITH_SYS_TIME\r
95 # include <sys/time.h>\r
96 # include <time.h>\r
97 #else\r
98 # if HAVE_SYS_TIME_H\r
99 #  include <sys/time.h>\r
100 # else\r
101 #  include <time.h>\r
102 # endif\r
103 #endif\r
104 \r
105 #if defined(_amigados) && !defined(__GNUC__)\r
106 struct timezone {\r
107     int tz_minuteswest;\r
108     int tz_dsttime;\r
109 };\r
110 extern int gettimeofday(struct timeval *, struct timezone *);\r
111 #endif\r
112 \r
113 #if HAVE_UNISTD_H\r
114 # include <unistd.h>\r
115 #endif\r
116 \r
117 #include "common.h"\r
118 #include "frontend.h"\r
119 #include "backend.h"\r
120 #include "parser.h"\r
121 #include "moves.h"\r
122 #if ZIPPY\r
123 # include "zippy.h"\r
124 #endif\r
125 #include "backendz.h"\r
126 #include "gettext.h" \r
127  \r
128 #ifdef ENABLE_NLS \r
129 # define _(s) gettext (s) \r
130 # define N_(s) gettext_noop (s) \r
131 #else \r
132 # define _(s) (s) \r
133 # define N_(s) s \r
134 #endif \r
135 \r
136 \r
137 /* A point in time */\r
138 typedef struct {\r
139     long sec;  /* Assuming this is >= 32 bits */\r
140     int ms;    /* Assuming this is >= 16 bits */\r
141 } TimeMark;\r
142 \r
143 int establish P((void));\r
144 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,\r
145                          char *buf, int count, int error));\r
146 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,\r
147                       char *buf, int count, int error));\r
148 void SendToICS P((char *s));\r
149 void SendToICSDelayed P((char *s, long msdelay));\r
150 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,\r
151                       int toX, int toY));\r
152 void InitPosition P((int redraw));\r
153 void HandleMachineMove P((char *message, ChessProgramState *cps));\r
154 int AutoPlayOneMove P((void));\r
155 int LoadGameOneMove P((ChessMove readAhead));\r
156 int LoadGameFromFile P((char *filename, int n, char *title, int useList));\r
157 int LoadPositionFromFile P((char *filename, int n, char *title));\r
158 int SavePositionToFile P((char *filename));\r
159 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,\r
160                   Board board));\r
161 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));\r
162 void ShowMove P((int fromX, int fromY, int toX, int toY));\r
163 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,\r
164                    /*char*/int promoChar));\r
165 void BackwardInner P((int target));\r
166 void ForwardInner P((int target));\r
167 void GameEnds P((ChessMove result, char *resultDetails, int whosays));\r
168 void EditPositionDone P((void));\r
169 void PrintOpponents P((FILE *fp));\r
170 void PrintPosition P((FILE *fp, int move));\r
171 void StartChessProgram P((ChessProgramState *cps));\r
172 void SendToProgram P((char *message, ChessProgramState *cps));\r
173 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));\r
174 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,\r
175                            char *buf, int count, int error));\r
176 void SendTimeControl P((ChessProgramState *cps,\r
177                         int mps, long tc, int inc, int sd, int st));\r
178 char *TimeControlTagValue P((void));\r
179 void Attention P((ChessProgramState *cps));\r
180 void FeedMovesToProgram P((ChessProgramState *cps, int upto));\r
181 void ResurrectChessProgram P((void));\r
182 void DisplayComment P((int moveNumber, char *text));\r
183 void DisplayMove P((int moveNumber));\r
184 void DisplayAnalysis P((void));\r
185 \r
186 void ParseGameHistory P((char *game));\r
187 void ParseBoard12 P((char *string));\r
188 void StartClocks P((void));\r
189 void SwitchClocks P((void));\r
190 void StopClocks P((void));\r
191 void ResetClocks P((void));\r
192 char *PGNDate P((void));\r
193 void SetGameInfo P((void));\r
194 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));\r
195 int RegisterMove P((void));\r
196 void MakeRegisteredMove P((void));\r
197 void TruncateGame P((void));\r
198 int looking_at P((char *, int *, char *));\r
199 void CopyPlayerNameIntoFileName P((char **, char *));\r
200 char *SavePart P((char *));\r
201 int SaveGameOldStyle P((FILE *));\r
202 int SaveGamePGN P((FILE *));\r
203 void GetTimeMark P((TimeMark *));\r
204 long SubtractTimeMarks P((TimeMark *, TimeMark *));\r
205 int CheckFlags P((void));\r
206 long NextTickLength P((long));\r
207 void CheckTimeControl P((void));\r
208 void show_bytes P((FILE *, char *, int));\r
209 int string_to_rating P((char *str));\r
210 void ParseFeatures P((char* args, ChessProgramState *cps));\r
211 void InitBackEnd3 P((void));\r
212 void FeatureDone P((ChessProgramState* cps, int val));\r
213 void InitChessProgram P((ChessProgramState *cps, int setup));\r
214 void OutputKibitz(int window, char *text);\r
215 int PerpetualChase(int first, int last);\r
216 int EngineOutputIsUp();\r
217 void InitDrawingSizes(int x, int y);\r
218 \r
219 #ifdef WIN32\r
220        extern void ConsoleCreate();\r
221 #endif\r
222 \r
223 ChessProgramState *WhitePlayer();\r
224 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c\r
225 int VerifyDisplayMode P(());\r
226 \r
227 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment\r
228 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c\r
229 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move\r
230 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book\r
231 extern char installDir[MSG_SIZ];\r
232 \r
233 extern int tinyLayout, smallLayout;\r
234 ChessProgramStats programStats;\r
235 static int exiting = 0; /* [HGM] moved to top */\r
236 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;\r
237 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */\r
238 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */\r
239 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */\r
240 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */\r
241 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */\r
242 int opponentKibitzes;\r
243 \r
244 /* States for ics_getting_history */\r
245 #define H_FALSE 0\r
246 #define H_REQUESTED 1\r
247 #define H_GOT_REQ_HEADER 2\r
248 #define H_GOT_UNREQ_HEADER 3\r
249 #define H_GETTING_MOVES 4\r
250 #define H_GOT_UNWANTED_HEADER 5\r
251 \r
252 /* whosays values for GameEnds */\r
253 #define GE_ICS 0\r
254 #define GE_ENGINE 1\r
255 #define GE_PLAYER 2\r
256 #define GE_FILE 3\r
257 #define GE_XBOARD 4\r
258 #define GE_ENGINE1 5\r
259 #define GE_ENGINE2 6\r
260 \r
261 /* Maximum number of games in a cmail message */\r
262 #define CMAIL_MAX_GAMES 20\r
263 \r
264 /* Different types of move when calling RegisterMove */\r
265 #define CMAIL_MOVE   0\r
266 #define CMAIL_RESIGN 1\r
267 #define CMAIL_DRAW   2\r
268 #define CMAIL_ACCEPT 3\r
269 \r
270 /* Different types of result to remember for each game */\r
271 #define CMAIL_NOT_RESULT 0\r
272 #define CMAIL_OLD_RESULT 1\r
273 #define CMAIL_NEW_RESULT 2\r
274 \r
275 /* Telnet protocol constants */\r
276 #define TN_WILL 0373\r
277 #define TN_WONT 0374\r
278 #define TN_DO   0375\r
279 #define TN_DONT 0376\r
280 #define TN_IAC  0377\r
281 #define TN_ECHO 0001\r
282 #define TN_SGA  0003\r
283 #define TN_PORT 23\r
284 \r
285 /* [AS] */\r
286 static char * safeStrCpy( char * dst, const char * src, size_t count )\r
287 {\r
288     assert( dst != NULL );\r
289     assert( src != NULL );\r
290     assert( count > 0 );\r
291 \r
292     strncpy( dst, src, count );\r
293     dst[ count-1 ] = '\0';\r
294     return dst;\r
295 }\r
296 \r
297 #if 0\r
298 //[HGM] for future use? Conditioned out for now to suppress warning.\r
299 static char * safeStrCat( char * dst, const char * src, size_t count )\r
300 {\r
301     size_t  dst_len;\r
302 \r
303     assert( dst != NULL );\r
304     assert( src != NULL );\r
305     assert( count > 0 );\r
306 \r
307     dst_len = strlen(dst);\r
308 \r
309     assert( count > dst_len ); /* Buffer size must be greater than current length */\r
310 \r
311     safeStrCpy( dst + dst_len, src, count - dst_len );\r
312 \r
313     return dst;\r
314 }\r
315 #endif\r
316 \r
317 /* Some compiler can't cast u64 to double\r
318  * This function do the job for us:\r
319 \r
320  * We use the highest bit for cast, this only\r
321  * works if the highest bit is not\r
322  * in use (This should not happen)\r
323  *\r
324  * We used this for all compiler\r
325  */\r
326 double\r
327 u64ToDouble(u64 value)\r
328 {\r
329   double r;\r
330   u64 tmp = value & u64Const(0x7fffffffffffffff);\r
331   r = (double)(s64)tmp;\r
332   if (value & u64Const(0x8000000000000000))\r
333        r +=  9.2233720368547758080e18; /* 2^63 */\r
334  return r;\r
335 }\r
336 \r
337 /* Fake up flags for now, as we aren't keeping track of castling\r
338    availability yet. [HGM] Change of logic: the flag now only\r
339    indicates the type of castlings allowed by the rule of the game.\r
340    The actual rights themselves are maintained in the array\r
341    castlingRights, as part of the game history, and are not probed\r
342    by this function.\r
343  */\r
344 int\r
345 PosFlags(index)\r
346 {\r
347   int flags = F_ALL_CASTLE_OK;\r
348   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;\r
349   switch (gameInfo.variant) {\r
350   case VariantSuicide:\r
351     flags &= ~F_ALL_CASTLE_OK;\r
352   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!\r
353     flags |= F_IGNORE_CHECK;\r
354     break;\r
355   case VariantAtomic:\r
356     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;\r
357     break;\r
358   case VariantKriegspiel:\r
359     flags |= F_KRIEGSPIEL_CAPTURE;\r
360     break;\r
361   case VariantCapaRandom: \r
362   case VariantFischeRandom:\r
363     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */\r
364   case VariantNoCastle:\r
365   case VariantShatranj:\r
366   case VariantCourier:\r
367     flags &= ~F_ALL_CASTLE_OK;\r
368     break;\r
369   default:\r
370     break;\r
371   }\r
372   return flags;\r
373 }\r
374 \r
375 FILE *gameFileFP, *debugFP;\r
376 \r
377 /* \r
378     [AS] Note: sometimes, the sscanf() function is used to parse the input\r
379     into a fixed-size buffer. Because of this, we must be prepared to\r
380     receive strings as long as the size of the input buffer, which is currently\r
381     set to 4K for Windows and 8K for the rest.\r
382     So, we must either allocate sufficiently large buffers here, or\r
383     reduce the size of the input buffer in the input reading part.\r
384 */\r
385 \r
386 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];\r
387 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];\r
388 char thinkOutput1[MSG_SIZ*10];\r
389 \r
390 ChessProgramState first, second;\r
391 \r
392 /* premove variables */\r
393 int premoveToX = 0;\r
394 int premoveToY = 0;\r
395 int premoveFromX = 0;\r
396 int premoveFromY = 0;\r
397 int premovePromoChar = 0;\r
398 int gotPremove = 0;\r
399 Boolean alarmSounded;\r
400 /* end premove variables */\r
401 \r
402 char *ics_prefix = "$";\r
403 int ics_type = ICS_GENERIC;\r
404 \r
405 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;\r
406 int pauseExamForwardMostMove = 0;\r
407 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;\r
408 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];\r
409 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;\r
410 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;\r
411 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;\r
412 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;\r
413 int whiteFlag = FALSE, blackFlag = FALSE;\r
414 int userOfferedDraw = FALSE;\r
415 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;\r
416 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;\r
417 int cmailMoveType[CMAIL_MAX_GAMES];\r
418 long ics_clock_paused = 0;\r
419 ProcRef icsPR = NoProc, cmailPR = NoProc;\r
420 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;\r
421 GameMode gameMode = BeginningOfGame;\r
422 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];\r
423 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];\r
424 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */\r
425 int hiddenThinkOutputState = 0; /* [AS] */\r
426 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */\r
427 int adjudicateLossPlies = 6;\r
428 char white_holding[64], black_holding[64];\r
429 TimeMark lastNodeCountTime;\r
430 long lastNodeCount=0;\r
431 int have_sent_ICS_logon = 0;\r
432 int movesPerSession;\r
433 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;\r
434 long timeControl_2; /* [AS] Allow separate time controls */\r
435 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */\r
436 long timeRemaining[2][MAX_MOVES];\r
437 int matchGame = 0;\r
438 TimeMark programStartTime;\r
439 char ics_handle[MSG_SIZ];\r
440 int have_set_title = 0;\r
441 \r
442 /* animateTraining preserves the state of appData.animate\r
443  * when Training mode is activated. This allows the\r
444  * response to be animated when appData.animate == TRUE and\r
445  * appData.animateDragging == TRUE.\r
446  */\r
447 Boolean animateTraining;\r
448 \r
449 GameInfo gameInfo;\r
450 \r
451 AppData appData;\r
452 \r
453 Board boards[MAX_MOVES];\r
454 /* [HGM] Following 7 needed for accurate legality tests: */\r
455 char  epStatus[MAX_MOVES];\r
456 char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1\r
457 char  castlingRank[BOARD_SIZE]; // and corresponding ranks\r
458 char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];\r
459 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status\r
460 int   initialRulePlies, FENrulePlies;\r
461 char  FENepStatus;\r
462 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)\r
463 int loadFlag = 0; \r
464 int shuffleOpenings;\r
465 \r
466 ChessSquare  FIDEArray[2][BOARD_SIZE] = {\r
467     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,\r
468         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },\r
469     { BlackRook, BlackKnight, BlackBishop, BlackQueen,\r
470         BlackKing, BlackBishop, BlackKnight, BlackRook }\r
471 };\r
472 \r
473 ChessSquare twoKingsArray[2][BOARD_SIZE] = {\r
474     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,\r
475         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },\r
476     { BlackRook, BlackKnight, BlackBishop, BlackQueen,\r
477         BlackKing, BlackKing, BlackKnight, BlackRook }\r
478 };\r
479 \r
480 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {\r
481     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,\r
482         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },\r
483     { BlackRook, BlackMan, BlackBishop, BlackQueen,\r
484         BlackUnicorn, BlackBishop, BlackMan, BlackRook }\r
485 };\r
486 \r
487 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */\r
488     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,\r
489         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },\r
490     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,\r
491         BlackKing, BlackBishop, BlackKnight, BlackRook }\r
492 };\r
493 \r
494 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */\r
495     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,\r
496         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },\r
497     { BlackRook, BlackKnight, BlackAlfil, BlackKing,\r
498         BlackFerz, BlackAlfil, BlackKnight, BlackRook }\r
499 };\r
500 \r
501 \r
502 #if (BOARD_SIZE>=10)\r
503 ChessSquare ShogiArray[2][BOARD_SIZE] = {\r
504     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,\r
505         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },\r
506     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,\r
507         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }\r
508 };\r
509 \r
510 ChessSquare XiangqiArray[2][BOARD_SIZE] = {\r
511     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,\r
512         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },\r
513     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,\r
514         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }\r
515 };\r
516 \r
517 ChessSquare CapablancaArray[2][BOARD_SIZE] = {\r
518     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, \r
519         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },\r
520     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, \r
521         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }\r
522 };\r
523 \r
524 ChessSquare GreatArray[2][BOARD_SIZE] = {\r
525     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, \r
526         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },\r
527     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, \r
528         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },\r
529 };\r
530 \r
531 ChessSquare JanusArray[2][BOARD_SIZE] = {\r
532     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, \r
533         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },\r
534     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, \r
535         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }\r
536 };\r
537 \r
538 #ifdef GOTHIC\r
539 ChessSquare GothicArray[2][BOARD_SIZE] = {\r
540     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, \r
541         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },\r
542     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, \r
543         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }\r
544 };\r
545 #else // !GOTHIC\r
546 #define GothicArray CapablancaArray\r
547 #endif // !GOTHIC\r
548 \r
549 #ifdef FALCON\r
550 ChessSquare FalconArray[2][BOARD_SIZE] = {\r
551     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, \r
552         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },\r
553     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, \r
554         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }\r
555 };\r
556 #else // !FALCON\r
557 #define FalconArray CapablancaArray\r
558 #endif // !FALCON\r
559 \r
560 #else // !(BOARD_SIZE>=10)\r
561 #define XiangqiPosition FIDEArray\r
562 #define CapablancaArray FIDEArray\r
563 #define GothicArray FIDEArray\r
564 #define GreatArray FIDEArray\r
565 #endif // !(BOARD_SIZE>=10)\r
566 \r
567 #if (BOARD_SIZE>=12)\r
568 ChessSquare CourierArray[2][BOARD_SIZE] = {\r
569     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,\r
570         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },\r
571     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,\r
572         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }\r
573 };\r
574 #else // !(BOARD_SIZE>=12)\r
575 #define CourierArray CapablancaArray\r
576 #endif // !(BOARD_SIZE>=12)\r
577 \r
578 \r
579 Board initialPosition;\r
580 \r
581 \r
582 /* Convert str to a rating. Checks for special cases of "----",\r
583 \r
584    "++++", etc. Also strips ()'s */\r
585 int\r
586 string_to_rating(str)\r
587   char *str;\r
588 {\r
589   while(*str && !isdigit(*str)) ++str;\r
590   if (!*str)\r
591     return 0;   /* One of the special "no rating" cases */\r
592   else\r
593     return atoi(str);\r
594 }\r
595 \r
596 void\r
597 ClearProgramStats()\r
598 {\r
599     /* Init programStats */\r
600     programStats.movelist[0] = 0;\r
601     programStats.depth = 0;\r
602     programStats.nr_moves = 0;\r
603     programStats.moves_left = 0;\r
604     programStats.nodes = 0;\r
605     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output\r
606     programStats.score = 0;\r
607     programStats.got_only_move = 0;\r
608     programStats.got_fail = 0;\r
609     programStats.line_is_book = 0;\r
610 }\r
611 \r
612 void\r
613 InitBackEnd1()\r
614 {\r
615     int matched, min, sec;\r
616 \r
617     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options\r
618 \r
619     GetTimeMark(&programStartTime);\r
620 \r
621     ClearProgramStats();\r
622     programStats.ok_to_send = 1;\r
623     programStats.seen_stat = 0;\r
624 \r
625     /*\r
626      * Initialize game list\r
627      */\r
628     ListNew(&gameList);\r
629 \r
630 \r
631     /*\r
632      * Internet chess server status\r
633      */\r
634     if (appData.icsActive) {\r
635         appData.matchMode = FALSE;\r
636         appData.matchGames = 0;\r
637 #if ZIPPY       \r
638         appData.noChessProgram = !appData.zippyPlay;\r
639 #else\r
640         appData.zippyPlay = FALSE;\r
641         appData.zippyTalk = FALSE;\r
642         appData.noChessProgram = TRUE;\r
643 #endif\r
644         if (*appData.icsHelper != NULLCHAR) {\r
645             appData.useTelnet = TRUE;\r
646             appData.telnetProgram = appData.icsHelper;\r
647         }\r
648     } else {\r
649         appData.zippyTalk = appData.zippyPlay = FALSE;\r
650     }\r
651 \r
652     /* [AS] Initialize pv info list [HGM] and game state */\r
653     {\r
654         int i, j;\r
655 \r
656         for( i=0; i<MAX_MOVES; i++ ) {\r
657             pvInfoList[i].depth = -1;\r
658             epStatus[i]=EP_NONE;\r
659             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
660         }\r
661     }\r
662 \r
663     /*\r
664      * Parse timeControl resource\r
665      */\r
666     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,\r
667                           appData.movesPerSession)) {\r
668         char buf[MSG_SIZ];\r
669         sprintf(buf, _("bad timeControl option %s"), appData.timeControl);\r
670         DisplayFatalError(buf, 0, 2);\r
671     }\r
672 \r
673     /*\r
674      * Parse searchTime resource\r
675      */\r
676     if (*appData.searchTime != NULLCHAR) {\r
677         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);\r
678         if (matched == 1) {\r
679             searchTime = min * 60;\r
680         } else if (matched == 2) {\r
681             searchTime = min * 60 + sec;\r
682         } else {\r
683             char buf[MSG_SIZ];\r
684             sprintf(buf, _("bad searchTime option %s"), appData.searchTime);\r
685             DisplayFatalError(buf, 0, 2);\r
686         }\r
687     }\r
688 \r
689     /* [AS] Adjudication threshold */\r
690     adjudicateLossThreshold = appData.adjudicateLossThreshold;\r
691     \r
692     first.which = "first";\r
693     second.which = "second";\r
694     first.maybeThinking = second.maybeThinking = FALSE;\r
695     first.pr = second.pr = NoProc;\r
696     first.isr = second.isr = NULL;\r
697     first.sendTime = second.sendTime = 2;\r
698     first.sendDrawOffers = 1;\r
699     if (appData.firstPlaysBlack) {\r
700         first.twoMachinesColor = "black\n";\r
701         second.twoMachinesColor = "white\n";\r
702     } else {\r
703         first.twoMachinesColor = "white\n";\r
704         second.twoMachinesColor = "black\n";\r
705     }\r
706     first.program = appData.firstChessProgram;\r
707     second.program = appData.secondChessProgram;\r
708     first.host = appData.firstHost;\r
709     second.host = appData.secondHost;\r
710     first.dir = appData.firstDirectory;\r
711     second.dir = appData.secondDirectory;\r
712     first.other = &second;\r
713     second.other = &first;\r
714     first.initString = appData.initString;\r
715     second.initString = appData.secondInitString;\r
716     first.computerString = appData.firstComputerString;\r
717     second.computerString = appData.secondComputerString;\r
718     first.useSigint = second.useSigint = TRUE;\r
719     first.useSigterm = second.useSigterm = TRUE;\r
720     first.reuse = appData.reuseFirst;\r
721     second.reuse = appData.reuseSecond;\r
722     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second\r
723     second.nps = appData.secondNPS;\r
724     first.useSetboard = second.useSetboard = FALSE;\r
725     first.useSAN = second.useSAN = FALSE;\r
726     first.usePing = second.usePing = FALSE;\r
727     first.lastPing = second.lastPing = 0;\r
728     first.lastPong = second.lastPong = 0;\r
729     first.usePlayother = second.usePlayother = FALSE;\r
730     first.useColors = second.useColors = TRUE;\r
731     first.useUsermove = second.useUsermove = FALSE;\r
732     first.sendICS = second.sendICS = FALSE;\r
733     first.sendName = second.sendName = appData.icsActive;\r
734     first.sdKludge = second.sdKludge = FALSE;\r
735     first.stKludge = second.stKludge = FALSE;\r
736     TidyProgramName(first.program, first.host, first.tidy);\r
737     TidyProgramName(second.program, second.host, second.tidy);\r
738     first.matchWins = second.matchWins = 0;\r
739     strcpy(first.variants, appData.variant);\r
740     strcpy(second.variants, appData.variant);\r
741     first.analysisSupport = second.analysisSupport = 2; /* detect */\r
742     first.analyzing = second.analyzing = FALSE;\r
743     first.initDone = second.initDone = FALSE;\r
744 \r
745     /* New features added by Tord: */\r
746     first.useFEN960 = FALSE; second.useFEN960 = FALSE;\r
747     first.useOOCastle = TRUE; second.useOOCastle = TRUE;\r
748     /* End of new features added by Tord. */\r
749 \r
750     /* [HGM] time odds: set factor for each machine */\r
751     first.timeOdds  = appData.firstTimeOdds;\r
752     second.timeOdds = appData.secondTimeOdds;\r
753     { int norm = 1;\r
754         if(appData.timeOddsMode) {\r
755             norm = first.timeOdds;\r
756             if(norm > second.timeOdds) norm = second.timeOdds;\r
757         }\r
758         first.timeOdds /= norm;\r
759         second.timeOdds /= norm;\r
760     }\r
761 \r
762     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/\r
763     first.accumulateTC = appData.firstAccumulateTC;\r
764     second.accumulateTC = appData.secondAccumulateTC;\r
765     first.maxNrOfSessions = second.maxNrOfSessions = 1;\r
766 \r
767     /* [HGM] debug */\r
768     first.debug = second.debug = FALSE;\r
769     first.supportsNPS = second.supportsNPS = UNKNOWN;\r
770 \r
771     /* [HGM] options */\r
772     first.optionSettings  = appData.firstOptions;\r
773     second.optionSettings = appData.secondOptions;\r
774 \r
775     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */\r
776     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */\r
777     first.isUCI = appData.firstIsUCI; /* [AS] */\r
778     second.isUCI = appData.secondIsUCI; /* [AS] */\r
779     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */\r
780     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */\r
781 \r
782     if (appData.firstProtocolVersion > PROTOVER ||\r
783         appData.firstProtocolVersion < 1) {\r
784       char buf[MSG_SIZ];\r
785       sprintf(buf, _("protocol version %d not supported"),\r
786               appData.firstProtocolVersion);\r
787       DisplayFatalError(buf, 0, 2);\r
788     } else {\r
789       first.protocolVersion = appData.firstProtocolVersion;\r
790     }\r
791 \r
792     if (appData.secondProtocolVersion > PROTOVER ||\r
793         appData.secondProtocolVersion < 1) {\r
794       char buf[MSG_SIZ];\r
795       sprintf(buf, _("protocol version %d not supported"),\r
796               appData.secondProtocolVersion);\r
797       DisplayFatalError(buf, 0, 2);\r
798     } else {\r
799       second.protocolVersion = appData.secondProtocolVersion;\r
800     }\r
801 \r
802     if (appData.icsActive) {\r
803         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */\r
804     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {\r
805         appData.clockMode = FALSE;\r
806         first.sendTime = second.sendTime = 0;\r
807     }\r
808     \r
809 #if ZIPPY\r
810     /* Override some settings from environment variables, for backward\r
811        compatibility.  Unfortunately it's not feasible to have the env\r
812        vars just set defaults, at least in xboard.  Ugh.\r
813     */\r
814     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {\r
815       ZippyInit();\r
816     }\r
817 #endif\r
818     \r
819     if (appData.noChessProgram) {\r
820         programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)\r
821                                         + strlen(PATCHLEVEL));\r
822         sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);\r
823     } else {\r
824 #if 0\r
825         char *p, *q;\r
826         q = first.program;\r
827         while (*q != ' ' && *q != NULLCHAR) q++;\r
828         p = q;\r
829         while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] bckslash added */\r
830         programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)\r
831                                         + strlen(PATCHLEVEL) + (q - p));\r
832         sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);\r
833         strncat(programVersion, p, q - p);\r
834 #else\r
835         /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */\r
836         programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)\r
837                                         + strlen(PATCHLEVEL) + strlen(first.tidy));\r
838         sprintf(programVersion, "%s %s.%s + %s", PRODUCT, VERSION, PATCHLEVEL, first.tidy);\r
839 #endif\r
840     }\r
841 \r
842     if (!appData.icsActive) {\r
843       char buf[MSG_SIZ];\r
844       /* Check for variants that are supported only in ICS mode,\r
845          or not at all.  Some that are accepted here nevertheless\r
846          have bugs; see comments below.\r
847       */\r
848       VariantClass variant = StringToVariant(appData.variant);\r
849       switch (variant) {\r
850       case VariantBughouse:     /* need four players and two boards */\r
851       case VariantKriegspiel:   /* need to hide pieces and move details */\r
852       /* case VariantFischeRandom: (Fabien: moved below) */\r
853         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);\r
854         DisplayFatalError(buf, 0, 2);\r
855         return;\r
856 \r
857       case VariantUnknown:\r
858       case VariantLoadable:\r
859       case Variant29:\r
860       case Variant30:\r
861       case Variant31:\r
862       case Variant32:\r
863       case Variant33:\r
864       case Variant34:\r
865       case Variant35:\r
866       case Variant36:\r
867       default:\r
868         sprintf(buf, _("Unknown variant name %s"), appData.variant);\r
869         DisplayFatalError(buf, 0, 2);\r
870         return;\r
871 \r
872       case VariantXiangqi:    /* [HGM] repetition rules not implemented */\r
873       case VariantFairy:      /* [HGM] TestLegality definitely off! */\r
874       case VariantGothic:     /* [HGM] should work */\r
875       case VariantCapablanca: /* [HGM] should work */\r
876       case VariantCourier:    /* [HGM] initial forced moves not implemented */\r
877       case VariantShogi:      /* [HGM] drops not tested for legality */\r
878       case VariantKnightmate: /* [HGM] should work */\r
879       case VariantCylinder:   /* [HGM] untested */\r
880       case VariantFalcon:     /* [HGM] untested */\r
881       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)\r
882                                  offboard interposition not understood */\r
883       case VariantNormal:     /* definitely works! */\r
884       case VariantWildCastle: /* pieces not automatically shuffled */\r
885       case VariantNoCastle:   /* pieces not automatically shuffled */\r
886       case VariantFischeRandom: /* [HGM] works and shuffles pieces */\r
887       case VariantLosers:     /* should work except for win condition,\r
888                                  and doesn't know captures are mandatory */\r
889       case VariantSuicide:    /* should work except for win condition,\r
890                                  and doesn't know captures are mandatory */\r
891       case VariantGiveaway:   /* should work except for win condition,\r
892                                  and doesn't know captures are mandatory */\r
893       case VariantTwoKings:   /* should work */\r
894       case VariantAtomic:     /* should work except for win condition */\r
895       case Variant3Check:     /* should work except for win condition */\r
896       case VariantShatranj:   /* should work except for all win conditions */\r
897       case VariantBerolina:   /* might work if TestLegality is off */\r
898       case VariantCapaRandom: /* should work */\r
899       case VariantJanus:      /* should work */\r
900       case VariantSuper:      /* experimental */\r
901       case VariantGreat:      /* experimental, requires legality testing to be off */\r
902         break;\r
903       }\r
904     }\r
905 \r
906     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard\r
907     InitEngineUCI( installDir, &second );\r
908 }\r
909 \r
910 int NextIntegerFromString( char ** str, long * value )\r
911 {\r
912     int result = -1;\r
913     char * s = *str;\r
914 \r
915     while( *s == ' ' || *s == '\t' ) {\r
916         s++;\r
917     }\r
918 \r
919     *value = 0;\r
920 \r
921     if( *s >= '0' && *s <= '9' ) {\r
922         while( *s >= '0' && *s <= '9' ) {\r
923             *value = *value * 10 + (*s - '0');\r
924             s++;\r
925         }\r
926 \r
927         result = 0;\r
928     }\r
929 \r
930     *str = s;\r
931 \r
932     return result;\r
933 }\r
934 \r
935 int NextTimeControlFromString( char ** str, long * value )\r
936 {\r
937     long temp;\r
938     int result = NextIntegerFromString( str, &temp );\r
939 \r
940     if( result == 0 ) {\r
941         *value = temp * 60; /* Minutes */\r
942         if( **str == ':' ) {\r
943             (*str)++;\r
944             result = NextIntegerFromString( str, &temp );\r
945             *value += temp; /* Seconds */\r
946         }\r
947     }\r
948 \r
949     return result;\r
950 }\r
951 \r
952 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)\r
953 {   /* [HGM] routine added to read '+moves/time' for secondary time control */\r
954     int result = -1; long temp, temp2;\r
955 \r
956     if(**str != '+') return -1; // old params remain in force!\r
957     (*str)++;\r
958     if( NextTimeControlFromString( str, &temp ) ) return -1;\r
959 \r
960     if(**str != '/') {\r
961         /* time only: incremental or sudden-death time control */\r
962         if(**str == '+') { /* increment follows; read it */\r
963             (*str)++;\r
964             if(result = NextIntegerFromString( str, &temp2)) return -1;\r
965             *inc = temp2 * 1000;\r
966         } else *inc = 0;\r
967         *moves = 0; *tc = temp * 1000; \r
968         return 0;\r
969     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */\r
970 \r
971     (*str)++; /* classical time control */\r
972     result = NextTimeControlFromString( str, &temp2);\r
973     if(result == 0) {\r
974         *moves = temp/60;\r
975         *tc    = temp2 * 1000;\r
976         *inc   = 0;\r
977     }\r
978     return result;\r
979 }\r
980 \r
981 int GetTimeQuota(int movenr)\r
982 {   /* [HGM] get time to add from the multi-session time-control string */\r
983     int moves=1; /* kludge to force reading of first session */\r
984     long time, increment;\r
985     char *s = fullTimeControlString;\r
986 \r
987     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);\r
988     do {\r
989         if(moves) NextSessionFromString(&s, &moves, &time, &increment);\r
990         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);\r
991         if(movenr == -1) return time;    /* last move before new session     */\r
992         if(!moves) return increment;     /* current session is incremental   */\r
993         if(movenr >= 0) movenr -= moves; /* we already finished this session */\r
994     } while(movenr >= -1);               /* try again for next session       */\r
995 \r
996     return 0; // no new time quota on this move\r
997 }\r
998 \r
999 int\r
1000 ParseTimeControl(tc, ti, mps)\r
1001      char *tc;\r
1002      int ti;\r
1003      int mps;\r
1004 {\r
1005 #if 0\r
1006     int matched, min, sec;\r
1007 \r
1008     matched = sscanf(tc, "%d:%d", &min, &sec);\r
1009     if (matched == 1) {\r
1010         timeControl = min * 60 * 1000;\r
1011     } else if (matched == 2) {\r
1012         timeControl = (min * 60 + sec) * 1000;\r
1013     } else {\r
1014         return FALSE;\r
1015     }\r
1016 #else\r
1017     long tc1;\r
1018     long tc2;\r
1019     char buf[MSG_SIZ];\r
1020 \r
1021     if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;\r
1022     if(ti > 0) {\r
1023         if(mps)\r
1024              sprintf(buf, "+%d/%s+%d", mps, tc, ti);\r
1025         else sprintf(buf, "+%s+%d", tc, ti);\r
1026     } else {\r
1027         if(mps)\r
1028              sprintf(buf, "+%d/%s", mps, tc);\r
1029         else sprintf(buf, "+%s", tc);\r
1030     }\r
1031     fullTimeControlString = StrSave(buf);\r
1032 \r
1033     if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {\r
1034         return FALSE;\r
1035     }\r
1036 \r
1037     if( *tc == '/' ) {\r
1038         /* Parse second time control */\r
1039         tc++;\r
1040 \r
1041         if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {\r
1042             return FALSE;\r
1043         }\r
1044 \r
1045         if( tc2 == 0 ) {\r
1046             return FALSE;\r
1047         }\r
1048 \r
1049         timeControl_2 = tc2 * 1000;\r
1050     }\r
1051     else {\r
1052         timeControl_2 = 0;\r
1053     }\r
1054 \r
1055     if( tc1 == 0 ) {\r
1056         return FALSE;\r
1057     }\r
1058 \r
1059     timeControl = tc1 * 1000;\r
1060 #endif\r
1061 \r
1062     if (ti >= 0) {\r
1063         timeIncrement = ti * 1000;  /* convert to ms */\r
1064         movesPerSession = 0;\r
1065     } else {\r
1066         timeIncrement = 0;\r
1067         movesPerSession = mps;\r
1068     }\r
1069     return TRUE;\r
1070 }\r
1071 \r
1072 void\r
1073 InitBackEnd2()\r
1074 {\r
1075     if (appData.debugMode) {\r
1076         fprintf(debugFP, "%s\n", programVersion);\r
1077     }\r
1078 \r
1079     if (appData.matchGames > 0) {\r
1080         appData.matchMode = TRUE;\r
1081     } else if (appData.matchMode) {\r
1082         appData.matchGames = 1;\r
1083     }\r
1084     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */\r
1085         appData.matchGames = appData.sameColorGames;\r
1086     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */\r
1087         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;\r
1088         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;\r
1089     }\r
1090     Reset(TRUE, FALSE);\r
1091     if (appData.noChessProgram || first.protocolVersion == 1) {\r
1092       InitBackEnd3();\r
1093     } else {\r
1094       /* kludge: allow timeout for initial "feature" commands */\r
1095       FreezeUI();\r
1096       DisplayMessage("", _("Starting chess program"));\r
1097       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);\r
1098     }\r
1099 }\r
1100 \r
1101 void\r
1102 InitBackEnd3 P((void))\r
1103 {\r
1104     GameMode initialMode;\r
1105     char buf[MSG_SIZ];\r
1106     int err;\r
1107 \r
1108     InitChessProgram(&first, startedFromSetupPosition);\r
1109 \r
1110 \r
1111     if (appData.icsActive) {\r
1112 #ifdef WIN32\r
1113         /* [DM] Make a console window if needed [HGM] merged ifs */\r
1114         ConsoleCreate(); \r
1115 #endif\r
1116         err = establish();\r
1117         if (err != 0) {\r
1118             if (*appData.icsCommPort != NULLCHAR) {\r
1119                 sprintf(buf, _("Could not open comm port %s"),  \r
1120                         appData.icsCommPort);\r
1121             } else {\r
1122                 sprintf(buf, _("Could not connect to host %s, port %s"),  \r
1123                         appData.icsHost, appData.icsPort);\r
1124             }\r
1125             DisplayFatalError(buf, err, 1);\r
1126             return;\r
1127         }\r
1128         SetICSMode();\r
1129         telnetISR =\r
1130           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);\r
1131         fromUserISR =\r
1132           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);\r
1133     } else if (appData.noChessProgram) {\r
1134         SetNCPMode();\r
1135     } else {\r
1136         SetGNUMode();\r
1137     }\r
1138 \r
1139     if (*appData.cmailGameName != NULLCHAR) {\r
1140         SetCmailMode();\r
1141         OpenLoopback(&cmailPR);\r
1142         cmailISR =\r
1143           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);\r
1144     }\r
1145     \r
1146     ThawUI();\r
1147     DisplayMessage("", "");\r
1148     if (StrCaseCmp(appData.initialMode, "") == 0) {\r
1149       initialMode = BeginningOfGame;\r
1150     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {\r
1151       initialMode = TwoMachinesPlay;\r
1152     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {\r
1153       initialMode = AnalyzeFile; \r
1154     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {\r
1155       initialMode = AnalyzeMode;\r
1156     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {\r
1157       initialMode = MachinePlaysWhite;\r
1158     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {\r
1159       initialMode = MachinePlaysBlack;\r
1160     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {\r
1161       initialMode = EditGame;\r
1162     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {\r
1163       initialMode = EditPosition;\r
1164     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {\r
1165       initialMode = Training;\r
1166     } else {\r
1167       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);\r
1168       DisplayFatalError(buf, 0, 2);\r
1169       return;\r
1170     }\r
1171 \r
1172     if (appData.matchMode) {\r
1173         /* Set up machine vs. machine match */\r
1174         if (appData.noChessProgram) {\r
1175             DisplayFatalError(_("Can't have a match with no chess programs"),\r
1176                               0, 2);\r
1177             return;\r
1178         }\r
1179         matchMode = TRUE;\r
1180         matchGame = 1;\r
1181         if (*appData.loadGameFile != NULLCHAR) {\r
1182             int index = appData.loadGameIndex; // [HGM] autoinc\r
1183             if(index<0) lastIndex = index = 1;\r
1184             if (!LoadGameFromFile(appData.loadGameFile,\r
1185                                   index,\r
1186                                   appData.loadGameFile, FALSE)) {\r
1187                 DisplayFatalError(_("Bad game file"), 0, 1);\r
1188                 return;\r
1189             }\r
1190         } else if (*appData.loadPositionFile != NULLCHAR) {\r
1191             int index = appData.loadPositionIndex; // [HGM] autoinc\r
1192             if(index<0) lastIndex = index = 1;\r
1193             if (!LoadPositionFromFile(appData.loadPositionFile,\r
1194                                       index,\r
1195                                       appData.loadPositionFile)) {\r
1196                 DisplayFatalError(_("Bad position file"), 0, 1);\r
1197                 return;\r
1198             }\r
1199         }\r
1200         TwoMachinesEvent();\r
1201     } else if (*appData.cmailGameName != NULLCHAR) {\r
1202         /* Set up cmail mode */\r
1203         ReloadCmailMsgEvent(TRUE);\r
1204     } else {\r
1205         /* Set up other modes */\r
1206         if (initialMode == AnalyzeFile) {\r
1207           if (*appData.loadGameFile == NULLCHAR) {\r
1208             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);\r
1209             return;\r
1210           }\r
1211         }\r
1212         if (*appData.loadGameFile != NULLCHAR) {\r
1213             (void) LoadGameFromFile(appData.loadGameFile,\r
1214                                     appData.loadGameIndex,\r
1215                                     appData.loadGameFile, TRUE);\r
1216         } else if (*appData.loadPositionFile != NULLCHAR) {\r
1217             (void) LoadPositionFromFile(appData.loadPositionFile,\r
1218                                         appData.loadPositionIndex,\r
1219                                         appData.loadPositionFile);\r
1220             /* [HGM] try to make self-starting even after FEN load */\r
1221             /* to allow automatic setup of fairy variants with wtm */\r
1222             if(initialMode == BeginningOfGame && !blackPlaysFirst) {\r
1223                 gameMode = BeginningOfGame;\r
1224                 setboardSpoiledMachineBlack = 1;\r
1225             }\r
1226             /* [HGM] loadPos: make that every new game uses the setup */\r
1227             /* from file as long as we do not switch variant          */\r
1228             if(!blackPlaysFirst) { int i;\r
1229                 startedFromPositionFile = TRUE;\r
1230                 CopyBoard(filePosition, boards[0]);\r
1231                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];\r
1232             }\r
1233         }\r
1234         if (initialMode == AnalyzeMode) {\r
1235           if (appData.noChessProgram) {\r
1236             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);\r
1237             return;\r
1238           }\r
1239           if (appData.icsActive) {\r
1240             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);\r
1241             return;\r
1242           }\r
1243           AnalyzeModeEvent();\r
1244         } else if (initialMode == AnalyzeFile) {\r
1245           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent\r
1246           ShowThinkingEvent();\r
1247           AnalyzeFileEvent();\r
1248           AnalysisPeriodicEvent(1);\r
1249         } else if (initialMode == MachinePlaysWhite) {\r
1250           if (appData.noChessProgram) {\r
1251             DisplayFatalError(_("MachineWhite mode requires a chess engine"),\r
1252                               0, 2);\r
1253             return;\r
1254           }\r
1255           if (appData.icsActive) {\r
1256             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),\r
1257                               0, 2);\r
1258             return;\r
1259           }\r
1260           MachineWhiteEvent();\r
1261         } else if (initialMode == MachinePlaysBlack) {\r
1262           if (appData.noChessProgram) {\r
1263             DisplayFatalError(_("MachineBlack mode requires a chess engine"),\r
1264                               0, 2);\r
1265             return;\r
1266           }\r
1267           if (appData.icsActive) {\r
1268             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),\r
1269                               0, 2);\r
1270             return;\r
1271           }\r
1272           MachineBlackEvent();\r
1273         } else if (initialMode == TwoMachinesPlay) {\r
1274           if (appData.noChessProgram) {\r
1275             DisplayFatalError(_("TwoMachines mode requires a chess engine"),\r
1276                               0, 2);\r
1277             return;\r
1278           }\r
1279           if (appData.icsActive) {\r
1280             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),\r
1281                               0, 2);\r
1282             return;\r
1283           }\r
1284           TwoMachinesEvent();\r
1285         } else if (initialMode == EditGame) {\r
1286           EditGameEvent();\r
1287         } else if (initialMode == EditPosition) {\r
1288           EditPositionEvent();\r
1289         } else if (initialMode == Training) {\r
1290           if (*appData.loadGameFile == NULLCHAR) {\r
1291             DisplayFatalError(_("Training mode requires a game file"), 0, 2);\r
1292             return;\r
1293           }\r
1294           TrainingEvent();\r
1295         }\r
1296     }\r
1297 }\r
1298 \r
1299 /*\r
1300  * Establish will establish a contact to a remote host.port.\r
1301  * Sets icsPR to a ProcRef for a process (or pseudo-process)\r
1302  *  used to talk to the host.\r
1303  * Returns 0 if okay, error code if not.\r
1304  */\r
1305 int\r
1306 establish()\r
1307 {\r
1308     char buf[MSG_SIZ];\r
1309 \r
1310     if (*appData.icsCommPort != NULLCHAR) {\r
1311         /* Talk to the host through a serial comm port */\r
1312         return OpenCommPort(appData.icsCommPort, &icsPR);\r
1313 \r
1314     } else if (*appData.gateway != NULLCHAR) {\r
1315         if (*appData.remoteShell == NULLCHAR) {\r
1316             /* Use the rcmd protocol to run telnet program on a gateway host */\r
1317             sprintf(buf, "%s %s %s",\r
1318                     appData.telnetProgram, appData.icsHost, appData.icsPort);\r
1319             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);\r
1320 \r
1321         } else {\r
1322             /* Use the rsh program to run telnet program on a gateway host */\r
1323             if (*appData.remoteUser == NULLCHAR) {\r
1324                 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,\r
1325                         appData.gateway, appData.telnetProgram,\r
1326                         appData.icsHost, appData.icsPort);\r
1327             } else {\r
1328                 sprintf(buf, "%s %s -l %s %s %s %s",\r
1329                         appData.remoteShell, appData.gateway, \r
1330                         appData.remoteUser, appData.telnetProgram,\r
1331                         appData.icsHost, appData.icsPort);\r
1332             }\r
1333             return StartChildProcess(buf, "", &icsPR);\r
1334 \r
1335         }\r
1336     } else if (appData.useTelnet) {\r
1337         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);\r
1338 \r
1339     } else {\r
1340         /* TCP socket interface differs somewhat between\r
1341            Unix and NT; handle details in the front end.\r
1342            */\r
1343         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);\r
1344     }\r
1345 }\r
1346 \r
1347 void\r
1348 show_bytes(fp, buf, count)\r
1349      FILE *fp;\r
1350      char *buf;\r
1351      int count;\r
1352 {\r
1353     while (count--) {\r
1354         if (*buf < 040 || *(unsigned char *) buf > 0177) {\r
1355             fprintf(fp, "\\%03o", *buf & 0xff);\r
1356         } else {\r
1357             putc(*buf, fp);\r
1358         }\r
1359         buf++;\r
1360     }\r
1361     fflush(fp);\r
1362 }\r
1363 \r
1364 /* Returns an errno value */\r
1365 int\r
1366 OutputMaybeTelnet(pr, message, count, outError)\r
1367      ProcRef pr;\r
1368      char *message;\r
1369      int count;\r
1370      int *outError;\r
1371 {\r
1372     char buf[8192], *p, *q, *buflim;\r
1373     int left, newcount, outcount;\r
1374 \r
1375     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||\r
1376         *appData.gateway != NULLCHAR) {\r
1377         if (appData.debugMode) {\r
1378             fprintf(debugFP, ">ICS: ");\r
1379             show_bytes(debugFP, message, count);\r
1380             fprintf(debugFP, "\n");\r
1381         }\r
1382         return OutputToProcess(pr, message, count, outError);\r
1383     }\r
1384 \r
1385     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */\r
1386     p = message;\r
1387     q = buf;\r
1388     left = count;\r
1389     newcount = 0;\r
1390     while (left) {\r
1391         if (q >= buflim) {\r
1392             if (appData.debugMode) {\r
1393                 fprintf(debugFP, ">ICS: ");\r
1394                 show_bytes(debugFP, buf, newcount);\r
1395                 fprintf(debugFP, "\n");\r
1396             }\r
1397             outcount = OutputToProcess(pr, buf, newcount, outError);\r
1398             if (outcount < newcount) return -1; /* to be sure */\r
1399             q = buf;\r
1400             newcount = 0;\r
1401         }\r
1402         if (*p == '\n') {\r
1403             *q++ = '\r';\r
1404             newcount++;\r
1405         } else if (((unsigned char) *p) == TN_IAC) {\r
1406             *q++ = (char) TN_IAC;\r
1407             newcount ++;\r
1408         }\r
1409         *q++ = *p++;\r
1410         newcount++;\r
1411         left--;\r
1412     }\r
1413     if (appData.debugMode) {\r
1414         fprintf(debugFP, ">ICS: ");\r
1415         show_bytes(debugFP, buf, newcount);\r
1416         fprintf(debugFP, "\n");\r
1417     }\r
1418     outcount = OutputToProcess(pr, buf, newcount, outError);\r
1419     if (outcount < newcount) return -1; /* to be sure */\r
1420     return count;\r
1421 }\r
1422 \r
1423 void\r
1424 read_from_player(isr, closure, message, count, error)\r
1425      InputSourceRef isr;\r
1426      VOIDSTAR closure;\r
1427      char *message;\r
1428      int count;\r
1429      int error;\r
1430 {\r
1431     int outError, outCount;\r
1432     static int gotEof = 0;\r
1433 \r
1434     /* Pass data read from player on to ICS */\r
1435     if (count > 0) {\r
1436         gotEof = 0;\r
1437         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);\r
1438         if (outCount < count) {\r
1439             DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1440         }\r
1441     } else if (count < 0) {\r
1442         RemoveInputSource(isr);\r
1443         DisplayFatalError(_("Error reading from keyboard"), error, 1);\r
1444     } else if (gotEof++ > 0) {\r
1445         RemoveInputSource(isr);\r
1446         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);\r
1447     }\r
1448 }\r
1449 \r
1450 void\r
1451 SendToICS(s)\r
1452      char *s;\r
1453 {\r
1454     int count, outCount, outError;\r
1455 \r
1456     if (icsPR == NULL) return;\r
1457 \r
1458     count = strlen(s);\r
1459     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);\r
1460     if (outCount < count) {\r
1461         DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1462     }\r
1463 }\r
1464 \r
1465 /* This is used for sending logon scripts to the ICS. Sending\r
1466    without a delay causes problems when using timestamp on ICC\r
1467    (at least on my machine). */\r
1468 void\r
1469 SendToICSDelayed(s,msdelay)\r
1470      char *s;\r
1471      long msdelay;\r
1472 {\r
1473     int count, outCount, outError;\r
1474 \r
1475     if (icsPR == NULL) return;\r
1476 \r
1477     count = strlen(s);\r
1478     if (appData.debugMode) {\r
1479         fprintf(debugFP, ">ICS: ");\r
1480         show_bytes(debugFP, s, count);\r
1481         fprintf(debugFP, "\n");\r
1482     }\r
1483     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,\r
1484                                       msdelay);\r
1485     if (outCount < count) {\r
1486         DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1487     }\r
1488 }\r
1489 \r
1490 \r
1491 /* Remove all highlighting escape sequences in s\r
1492    Also deletes any suffix starting with '(' \r
1493    */\r
1494 char *\r
1495 StripHighlightAndTitle(s)\r
1496      char *s;\r
1497 {\r
1498     static char retbuf[MSG_SIZ];\r
1499     char *p = retbuf;\r
1500 \r
1501     while (*s != NULLCHAR) {\r
1502         while (*s == '\033') {\r
1503             while (*s != NULLCHAR && !isalpha(*s)) s++;\r
1504             if (*s != NULLCHAR) s++;\r
1505         }\r
1506         while (*s != NULLCHAR && *s != '\033') {\r
1507             if (*s == '(' || *s == '[') {\r
1508                 *p = NULLCHAR;\r
1509                 return retbuf;\r
1510             }\r
1511             *p++ = *s++;\r
1512         }\r
1513     }\r
1514     *p = NULLCHAR;\r
1515     return retbuf;\r
1516 }\r
1517 \r
1518 /* Remove all highlighting escape sequences in s */\r
1519 char *\r
1520 StripHighlight(s)\r
1521      char *s;\r
1522 {\r
1523     static char retbuf[MSG_SIZ];\r
1524     char *p = retbuf;\r
1525 \r
1526     while (*s != NULLCHAR) {\r
1527         while (*s == '\033') {\r
1528             while (*s != NULLCHAR && !isalpha(*s)) s++;\r
1529             if (*s != NULLCHAR) s++;\r
1530         }\r
1531         while (*s != NULLCHAR && *s != '\033') {\r
1532             *p++ = *s++;\r
1533         }\r
1534     }\r
1535     *p = NULLCHAR;\r
1536     return retbuf;\r
1537 }\r
1538 \r
1539 char *variantNames[] = VARIANT_NAMES;\r
1540 char *\r
1541 VariantName(v)\r
1542      VariantClass v;\r
1543 {\r
1544     return variantNames[v];\r
1545 }\r
1546 \r
1547 \r
1548 /* Identify a variant from the strings the chess servers use or the\r
1549    PGN Variant tag names we use. */\r
1550 VariantClass\r
1551 StringToVariant(e)\r
1552      char *e;\r
1553 {\r
1554     char *p;\r
1555     int wnum = -1;\r
1556     VariantClass v = VariantNormal;\r
1557     int i, found = FALSE;\r
1558     char buf[MSG_SIZ];\r
1559 \r
1560     if (!e) return v;\r
1561 \r
1562     /* [HGM] skip over optional board-size prefixes */\r
1563     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||\r
1564         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {\r
1565         while( *e++ != '_');\r
1566     }\r
1567 \r
1568     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {\r
1569       if (StrCaseStr(e, variantNames[i])) {\r
1570         v = (VariantClass) i;\r
1571         found = TRUE;\r
1572         break;\r
1573       }\r
1574     }\r
1575 \r
1576     if (!found) {\r
1577       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))\r
1578           || StrCaseStr(e, "wild/fr") \r
1579           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {\r
1580         v = VariantFischeRandom;\r
1581       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||\r
1582                  (i = 1, p = StrCaseStr(e, "w"))) {\r
1583         p += i;\r
1584         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;\r
1585         if (isdigit(*p)) {\r
1586           wnum = atoi(p);\r
1587         } else {\r
1588           wnum = -1;\r
1589         }\r
1590         switch (wnum) {\r
1591         case 0: /* FICS only, actually */\r
1592         case 1:\r
1593           /* Castling legal even if K starts on d-file */\r
1594           v = VariantWildCastle;\r
1595           break;\r
1596         case 2:\r
1597         case 3:\r
1598         case 4:\r
1599           /* Castling illegal even if K & R happen to start in\r
1600              normal positions. */\r
1601           v = VariantNoCastle;\r
1602           break;\r
1603         case 5:\r
1604         case 7:\r
1605         case 8:\r
1606         case 10:\r
1607         case 11:\r
1608         case 12:\r
1609         case 13:\r
1610         case 14:\r
1611         case 15:\r
1612         case 18:\r
1613         case 19:\r
1614           /* Castling legal iff K & R start in normal positions */\r
1615           v = VariantNormal;\r
1616           break;\r
1617         case 6:\r
1618         case 20:\r
1619         case 21:\r
1620           /* Special wilds for position setup; unclear what to do here */\r
1621           v = VariantLoadable;\r
1622           break;\r
1623         case 9:\r
1624           /* Bizarre ICC game */\r
1625           v = VariantTwoKings;\r
1626           break;\r
1627         case 16:\r
1628           v = VariantKriegspiel;\r
1629           break;\r
1630         case 17:\r
1631           v = VariantLosers;\r
1632           break;\r
1633         case 22:\r
1634           v = VariantFischeRandom;\r
1635           break;\r
1636         case 23:\r
1637           v = VariantCrazyhouse;\r
1638           break;\r
1639         case 24:\r
1640           v = VariantBughouse;\r
1641           break;\r
1642         case 25:\r
1643           v = Variant3Check;\r
1644           break;\r
1645         case 26:\r
1646           /* Not quite the same as FICS suicide! */\r
1647           v = VariantGiveaway;\r
1648           break;\r
1649         case 27:\r
1650           v = VariantAtomic;\r
1651           break;\r
1652         case 28:\r
1653           v = VariantShatranj;\r
1654           break;\r
1655 \r
1656         /* Temporary names for future ICC types.  The name *will* change in \r
1657            the next xboard/WinBoard release after ICC defines it. */\r
1658         case 29:\r
1659           v = Variant29;\r
1660           break;\r
1661         case 30:\r
1662           v = Variant30;\r
1663           break;\r
1664         case 31:\r
1665           v = Variant31;\r
1666           break;\r
1667         case 32:\r
1668           v = Variant32;\r
1669           break;\r
1670         case 33:\r
1671           v = Variant33;\r
1672           break;\r
1673         case 34:\r
1674           v = Variant34;\r
1675           break;\r
1676         case 35:\r
1677           v = Variant35;\r
1678           break;\r
1679         case 36:\r
1680           v = Variant36;\r
1681           break;\r
1682         case 37:\r
1683           v = VariantShogi;\r
1684           break;\r
1685         case 38:\r
1686           v = VariantXiangqi;\r
1687           break;\r
1688         case 39:\r
1689           v = VariantCourier;\r
1690           break;\r
1691         case 40:\r
1692           v = VariantGothic;\r
1693           break;\r
1694         case 41:\r
1695           v = VariantCapablanca;\r
1696           break;\r
1697         case 42:\r
1698           v = VariantKnightmate;\r
1699           break;\r
1700         case 43:\r
1701           v = VariantFairy;\r
1702           break;\r
1703         case 44:\r
1704           v = VariantCylinder;\r
1705           break;\r
1706         case 45:\r
1707           v = VariantFalcon;\r
1708           break;\r
1709         case 46:\r
1710           v = VariantCapaRandom;\r
1711           break;\r
1712         case 47:\r
1713           v = VariantBerolina;\r
1714           break;\r
1715         case 48:\r
1716           v = VariantJanus;\r
1717           break;\r
1718         case 49:\r
1719           v = VariantSuper;\r
1720           break;\r
1721         case 50:\r
1722           v = VariantGreat;\r
1723           break;\r
1724         case -1:\r
1725           /* Found "wild" or "w" in the string but no number;\r
1726              must assume it's normal chess. */\r
1727           v = VariantNormal;\r
1728           break;\r
1729         default:\r
1730           sprintf(buf, _("Unknown wild type %d"), wnum);\r
1731           DisplayError(buf, 0);\r
1732           v = VariantUnknown;\r
1733           break;\r
1734         }\r
1735       }\r
1736     }\r
1737     if (appData.debugMode) {\r
1738       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),\r
1739               e, wnum, VariantName(v));\r
1740     }\r
1741     return v;\r
1742 }\r
1743 \r
1744 static int leftover_start = 0, leftover_len = 0;\r
1745 char star_match[STAR_MATCH_N][MSG_SIZ];\r
1746 \r
1747 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,\r
1748    advance *index beyond it, and set leftover_start to the new value of\r
1749    *index; else return FALSE.  If pattern contains the character '*', it\r
1750    matches any sequence of characters not containing '\r', '\n', or the\r
1751    character following the '*' (if any), and the matched sequence(s) are\r
1752    copied into star_match.\r
1753    */\r
1754 int\r
1755 looking_at(buf, index, pattern)\r
1756      char *buf;\r
1757      int *index;\r
1758      char *pattern;\r
1759 {\r
1760     char *bufp = &buf[*index], *patternp = pattern;\r
1761     int star_count = 0;\r
1762     char *matchp = star_match[0];\r
1763     \r
1764     for (;;) {\r
1765         if (*patternp == NULLCHAR) {\r
1766             *index = leftover_start = bufp - buf;\r
1767             *matchp = NULLCHAR;\r
1768             return TRUE;\r
1769         }\r
1770         if (*bufp == NULLCHAR) return FALSE;\r
1771         if (*patternp == '*') {\r
1772             if (*bufp == *(patternp + 1)) {\r
1773                 *matchp = NULLCHAR;\r
1774                 matchp = star_match[++star_count];\r
1775                 patternp += 2;\r
1776                 bufp++;\r
1777                 continue;\r
1778             } else if (*bufp == '\n' || *bufp == '\r') {\r
1779                 patternp++;\r
1780                 if (*patternp == NULLCHAR)\r
1781                   continue;\r
1782                 else\r
1783                   return FALSE;\r
1784             } else {\r
1785                 *matchp++ = *bufp++;\r
1786                 continue;\r
1787             }\r
1788         }\r
1789         if (*patternp != *bufp) return FALSE;\r
1790         patternp++;\r
1791         bufp++;\r
1792     }\r
1793 }\r
1794 \r
1795 void\r
1796 SendToPlayer(data, length)\r
1797      char *data;\r
1798      int length;\r
1799 {\r
1800     int error, outCount;\r
1801     outCount = OutputToProcess(NoProc, data, length, &error);\r
1802     if (outCount < length) {\r
1803         DisplayFatalError(_("Error writing to display"), error, 1);\r
1804     }\r
1805 }\r
1806 \r
1807 void\r
1808 PackHolding(packed, holding)\r
1809      char packed[];\r
1810      char *holding;\r
1811 {\r
1812     char *p = holding;\r
1813     char *q = packed;\r
1814     int runlength = 0;\r
1815     int curr = 9999;\r
1816     do {\r
1817         if (*p == curr) {\r
1818             runlength++;\r
1819         } else {\r
1820             switch (runlength) {\r
1821               case 0:\r
1822                 break;\r
1823               case 1:\r
1824                 *q++ = curr;\r
1825                 break;\r
1826               case 2:\r
1827                 *q++ = curr;\r
1828                 *q++ = curr;\r
1829                 break;\r
1830               default:\r
1831                 sprintf(q, "%d", runlength);\r
1832                 while (*q) q++;\r
1833                 *q++ = curr;\r
1834                 break;\r
1835             }\r
1836             runlength = 1;\r
1837             curr = *p;\r
1838         }\r
1839     } while (*p++);\r
1840     *q = NULLCHAR;\r
1841 }\r
1842 \r
1843 /* Telnet protocol requests from the front end */\r
1844 void\r
1845 TelnetRequest(ddww, option)\r
1846      unsigned char ddww, option;\r
1847 {\r
1848     unsigned char msg[3];\r
1849     int outCount, outError;\r
1850 \r
1851     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;\r
1852 \r
1853     if (appData.debugMode) {\r
1854         char buf1[8], buf2[8], *ddwwStr, *optionStr;\r
1855         switch (ddww) {\r
1856           case TN_DO:\r
1857             ddwwStr = "DO";\r
1858             break;\r
1859           case TN_DONT:\r
1860             ddwwStr = "DONT";\r
1861             break;\r
1862           case TN_WILL:\r
1863             ddwwStr = "WILL";\r
1864             break;\r
1865           case TN_WONT:\r
1866             ddwwStr = "WONT";\r
1867             break;\r
1868           default:\r
1869             ddwwStr = buf1;\r
1870             sprintf(buf1, "%d", ddww);\r
1871             break;\r
1872         }\r
1873         switch (option) {\r
1874           case TN_ECHO:\r
1875             optionStr = "ECHO";\r
1876             break;\r
1877           default:\r
1878             optionStr = buf2;\r
1879             sprintf(buf2, "%d", option);\r
1880             break;\r
1881         }\r
1882         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);\r
1883     }\r
1884     msg[0] = TN_IAC;\r
1885     msg[1] = ddww;\r
1886     msg[2] = option;\r
1887     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);\r
1888     if (outCount < 3) {\r
1889         DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1890     }\r
1891 }\r
1892 \r
1893 void\r
1894 DoEcho()\r
1895 {\r
1896     if (!appData.icsActive) return;\r
1897     TelnetRequest(TN_DO, TN_ECHO);\r
1898 }\r
1899 \r
1900 void\r
1901 DontEcho()\r
1902 {\r
1903     if (!appData.icsActive) return;\r
1904     TelnetRequest(TN_DONT, TN_ECHO);\r
1905 }\r
1906 \r
1907 void\r
1908 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)\r
1909 {\r
1910     /* put the holdings sent to us by the server on the board holdings area */\r
1911     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;\r
1912     char p;\r
1913     ChessSquare piece;\r
1914 \r
1915     if(gameInfo.holdingsWidth < 2)  return;\r
1916 \r
1917     if( (int)lowestPiece >= BlackPawn ) {\r
1918         holdingsColumn = 0;\r
1919         countsColumn = 1;\r
1920         holdingsStartRow = BOARD_HEIGHT-1;\r
1921         direction = -1;\r
1922     } else {\r
1923         holdingsColumn = BOARD_WIDTH-1;\r
1924         countsColumn = BOARD_WIDTH-2;\r
1925         holdingsStartRow = 0;\r
1926         direction = 1;\r
1927     }\r
1928 \r
1929     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */\r
1930         board[i][holdingsColumn] = EmptySquare;\r
1931         board[i][countsColumn]   = (ChessSquare) 0;\r
1932     }\r
1933     while( (p=*holdings++) != NULLCHAR ) {\r
1934         piece = CharToPiece( ToUpper(p) );\r
1935         if(piece == EmptySquare) continue;\r
1936         /*j = (int) piece - (int) WhitePawn;*/\r
1937         j = PieceToNumber(piece);\r
1938         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */\r
1939         if(j < 0) continue;               /* should not happen */\r
1940         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );\r
1941         board[holdingsStartRow+j*direction][holdingsColumn] = piece;\r
1942         board[holdingsStartRow+j*direction][countsColumn]++;\r
1943     }\r
1944 \r
1945 }\r
1946 \r
1947 \r
1948 void\r
1949 VariantSwitch(Board board, VariantClass newVariant)\r
1950 {\r
1951    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;\r
1952    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;\r
1953 //   Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;\r
1954 \r
1955    startedFromPositionFile = FALSE;\r
1956    if(gameInfo.variant == newVariant) return;\r
1957 \r
1958    /* [HGM] This routine is called each time an assignment is made to\r
1959     * gameInfo.variant during a game, to make sure the board sizes\r
1960     * are set to match the new variant. If that means adding or deleting\r
1961     * holdings, we shift the playing board accordingly\r
1962     * This kludge is needed because in ICS observe mode, we get boards\r
1963     * of an ongoing game without knowing the variant, and learn about the\r
1964     * latter only later. This can be because of the move list we requested,\r
1965     * in which case the game history is refilled from the beginning anyway,\r
1966     * but also when receiving holdings of a crazyhouse game. In the latter\r
1967     * case we want to add those holdings to the already received position.\r
1968     */\r
1969 \r
1970 \r
1971   if (appData.debugMode) {\r
1972     fprintf(debugFP, "Switch board from %s to %s\n",\r
1973                VariantName(gameInfo.variant), VariantName(newVariant));\r
1974     setbuf(debugFP, NULL);\r
1975   }\r
1976     shuffleOpenings = 0;       /* [HGM] shuffle */\r
1977     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */\r
1978     switch(newVariant) {\r
1979             case VariantShogi:\r
1980               newWidth = 9;  newHeight = 9;\r
1981               gameInfo.holdingsSize = 7;\r
1982             case VariantBughouse:\r
1983             case VariantCrazyhouse:\r
1984               newHoldingsWidth = 2; break;\r
1985             default:\r
1986               newHoldingsWidth = gameInfo.holdingsSize = 0;\r
1987     }\r
1988 \r
1989     if(newWidth  != gameInfo.boardWidth  ||\r
1990        newHeight != gameInfo.boardHeight ||\r
1991        newHoldingsWidth != gameInfo.holdingsWidth ) {\r
1992 \r
1993         /* shift position to new playing area, if needed */\r
1994         if(newHoldingsWidth > gameInfo.holdingsWidth) {\r
1995            for(i=0; i<BOARD_HEIGHT; i++) \r
1996                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)\r
1997                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =\r
1998                                                      board[i][j];\r
1999            for(i=0; i<newHeight; i++) {\r
2000                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;\r
2001                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;\r
2002            }\r
2003         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {\r
2004            for(i=0; i<BOARD_HEIGHT; i++)\r
2005                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
2006                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =\r
2007                                                  board[i][j];\r
2008         }\r
2009 \r
2010         gameInfo.boardWidth  = newWidth;\r
2011         gameInfo.boardHeight = newHeight;\r
2012         gameInfo.holdingsWidth = newHoldingsWidth;\r
2013         gameInfo.variant = newVariant;\r
2014         InitDrawingSizes(-2, 0);\r
2015 \r
2016         /* [HGM] The following should definitely be solved in a better way */\r
2017 #if 0\r
2018         CopyBoard(board, tempBoard); /* save position in case it is board[0] */\r
2019         for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];\r
2020         saveEP = epStatus[0];\r
2021 #endif\r
2022         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */\r
2023 #if 0\r
2024         epStatus[0] = saveEP;\r
2025         for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];\r
2026         CopyBoard(tempBoard, board); /* restore position received from ICS   */\r
2027 #endif\r
2028     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }\r
2029 \r
2030     forwardMostMove = oldForwardMostMove;\r
2031     backwardMostMove = oldBackwardMostMove;\r
2032     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */\r
2033 }\r
2034 \r
2035 static int loggedOn = FALSE;\r
2036 \r
2037 /*-- Game start info cache: --*/\r
2038 int gs_gamenum;\r
2039 char gs_kind[MSG_SIZ];\r
2040 static char player1Name[128] = "";\r
2041 static char player2Name[128] = "";\r
2042 static int player1Rating = -1;\r
2043 static int player2Rating = -1;\r
2044 /*----------------------------*/\r
2045 \r
2046 ColorClass curColor = ColorNormal;\r
2047 int suppressKibitz = 0;\r
2048 \r
2049 void\r
2050 read_from_ics(isr, closure, data, count, error)\r
2051      InputSourceRef isr;\r
2052      VOIDSTAR closure;\r
2053      char *data;\r
2054      int count;\r
2055      int error;\r
2056 {\r
2057 #define BUF_SIZE 8192\r
2058 #define STARTED_NONE 0\r
2059 #define STARTED_MOVES 1\r
2060 #define STARTED_BOARD 2\r
2061 #define STARTED_OBSERVE 3\r
2062 #define STARTED_HOLDINGS 4\r
2063 #define STARTED_CHATTER 5\r
2064 #define STARTED_COMMENT 6\r
2065 #define STARTED_MOVES_NOHIDE 7\r
2066     \r
2067     static int started = STARTED_NONE;\r
2068     static char parse[20000];\r
2069     static int parse_pos = 0;\r
2070     static char buf[BUF_SIZE + 1];\r
2071     static int firstTime = TRUE, intfSet = FALSE;\r
2072     static ColorClass prevColor = ColorNormal;\r
2073     static int savingComment = FALSE;\r
2074     char str[500];\r
2075     int i, oldi;\r
2076     int buf_len;\r
2077     int next_out;\r
2078     int tkind;\r
2079     int backup;    /* [DM] For zippy color lines */\r
2080     char *p;\r
2081 \r
2082     if (appData.debugMode) {\r
2083       if (!error) {\r
2084         fprintf(debugFP, "<ICS: ");\r
2085         show_bytes(debugFP, data, count);\r
2086         fprintf(debugFP, "\n");\r
2087       }\r
2088     }\r
2089 \r
2090     if (appData.debugMode) { int f = forwardMostMove;\r
2091         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,\r
2092                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
2093     }\r
2094     if (count > 0) {\r
2095         /* If last read ended with a partial line that we couldn't parse,\r
2096            prepend it to the new read and try again. */\r
2097         if (leftover_len > 0) {\r
2098             for (i=0; i<leftover_len; i++)\r
2099               buf[i] = buf[leftover_start + i];\r
2100         }\r
2101 \r
2102         /* Copy in new characters, removing nulls and \r's */\r
2103         buf_len = leftover_len;\r
2104         for (i = 0; i < count; i++) {\r
2105             if (data[i] != NULLCHAR && data[i] != '\r')\r
2106               buf[buf_len++] = data[i];\r
2107             if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && \r
2108                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') \r
2109                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous\r
2110         }\r
2111 \r
2112         buf[buf_len] = NULLCHAR;\r
2113         next_out = leftover_len;\r
2114         leftover_start = 0;\r
2115         \r
2116         i = 0;\r
2117         while (i < buf_len) {\r
2118             /* Deal with part of the TELNET option negotiation\r
2119                protocol.  We refuse to do anything beyond the\r
2120                defaults, except that we allow the WILL ECHO option,\r
2121                which ICS uses to turn off password echoing when we are\r
2122                directly connected to it.  We reject this option\r
2123                if localLineEditing mode is on (always on in xboard)\r
2124                and we are talking to port 23, which might be a real\r
2125                telnet server that will try to keep WILL ECHO on permanently.\r
2126              */\r
2127             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {\r
2128                 static int remoteEchoOption = FALSE; /* telnet ECHO option */\r
2129                 unsigned char option;\r
2130                 oldi = i;\r
2131                 switch ((unsigned char) buf[++i]) {\r
2132                   case TN_WILL:\r
2133                     if (appData.debugMode)\r
2134                       fprintf(debugFP, "\n<WILL ");\r
2135                     switch (option = (unsigned char) buf[++i]) {\r
2136                       case TN_ECHO:\r
2137                         if (appData.debugMode)\r
2138                           fprintf(debugFP, "ECHO ");\r
2139                         /* Reply only if this is a change, according\r
2140                            to the protocol rules. */\r
2141                         if (remoteEchoOption) break;\r
2142                         if (appData.localLineEditing &&\r
2143                             atoi(appData.icsPort) == TN_PORT) {\r
2144                             TelnetRequest(TN_DONT, TN_ECHO);\r
2145                         } else {\r
2146                             EchoOff();\r
2147                             TelnetRequest(TN_DO, TN_ECHO);\r
2148                             remoteEchoOption = TRUE;\r
2149                         }\r
2150                         break;\r
2151                       default:\r
2152                         if (appData.debugMode)\r
2153                           fprintf(debugFP, "%d ", option);\r
2154                         /* Whatever this is, we don't want it. */\r
2155                         TelnetRequest(TN_DONT, option);\r
2156                         break;\r
2157                     }\r
2158                     break;\r
2159                   case TN_WONT:\r
2160                     if (appData.debugMode)\r
2161                       fprintf(debugFP, "\n<WONT ");\r
2162                     switch (option = (unsigned char) buf[++i]) {\r
2163                       case TN_ECHO:\r
2164                         if (appData.debugMode)\r
2165                           fprintf(debugFP, "ECHO ");\r
2166                         /* Reply only if this is a change, according\r
2167                            to the protocol rules. */\r
2168                         if (!remoteEchoOption) break;\r
2169                         EchoOn();\r
2170                         TelnetRequest(TN_DONT, TN_ECHO);\r
2171                         remoteEchoOption = FALSE;\r
2172                         break;\r
2173                       default:\r
2174                         if (appData.debugMode)\r
2175                           fprintf(debugFP, "%d ", (unsigned char) option);\r
2176                         /* Whatever this is, it must already be turned\r
2177                            off, because we never agree to turn on\r
2178                            anything non-default, so according to the\r
2179                            protocol rules, we don't reply. */\r
2180                         break;\r
2181                     }\r
2182                     break;\r
2183                   case TN_DO:\r
2184                     if (appData.debugMode)\r
2185                       fprintf(debugFP, "\n<DO ");\r
2186                     switch (option = (unsigned char) buf[++i]) {\r
2187                       default:\r
2188                         /* Whatever this is, we refuse to do it. */\r
2189                         if (appData.debugMode)\r
2190                           fprintf(debugFP, "%d ", option);\r
2191                         TelnetRequest(TN_WONT, option);\r
2192                         break;\r
2193                     }\r
2194                     break;\r
2195                   case TN_DONT:\r
2196                     if (appData.debugMode)\r
2197                       fprintf(debugFP, "\n<DONT ");\r
2198                     switch (option = (unsigned char) buf[++i]) {\r
2199                       default:\r
2200                         if (appData.debugMode)\r
2201                           fprintf(debugFP, "%d ", option);\r
2202                         /* Whatever this is, we are already not doing\r
2203                            it, because we never agree to do anything\r
2204                            non-default, so according to the protocol\r
2205                            rules, we don't reply. */\r
2206                         break;\r
2207                     }\r
2208                     break;\r
2209                   case TN_IAC:\r
2210                     if (appData.debugMode)\r
2211                       fprintf(debugFP, "\n<IAC ");\r
2212                     /* Doubled IAC; pass it through */\r
2213                     i--;\r
2214                     break;\r
2215                   default:\r
2216                     if (appData.debugMode)\r
2217                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);\r
2218                     /* Drop all other telnet commands on the floor */\r
2219                     break;\r
2220                 }\r
2221                 if (oldi > next_out)\r
2222                   SendToPlayer(&buf[next_out], oldi - next_out);\r
2223                 if (++i > next_out)\r
2224                   next_out = i;\r
2225                 continue;\r
2226             }\r
2227                 \r
2228             /* OK, this at least will *usually* work */\r
2229             if (!loggedOn && looking_at(buf, &i, "ics%")) {\r
2230                 loggedOn = TRUE;\r
2231             }\r
2232             \r
2233             if (loggedOn && !intfSet) {\r
2234                 if (ics_type == ICS_ICC) {\r
2235                   sprintf(str,\r
2236                           "/set-quietly interface %s\n/set-quietly style 12\n",\r
2237                           programVersion);\r
2238 \r
2239                 } else if (ics_type == ICS_CHESSNET) {\r
2240                   sprintf(str, "/style 12\n");\r
2241                 } else {\r
2242                   strcpy(str, "alias $ @\n$set interface ");\r
2243                   strcat(str, programVersion);\r
2244                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");\r
2245 #ifdef WIN32\r
2246                   strcat(str, "$iset nohighlight 1\n");\r
2247 #endif\r
2248                   strcat(str, "$iset lock 1\n$style 12\n");\r
2249                 }\r
2250                 SendToICS(str);\r
2251                 intfSet = TRUE;\r
2252             }\r
2253 \r
2254             if (started == STARTED_COMMENT) {\r
2255                 /* Accumulate characters in comment */\r
2256                 parse[parse_pos++] = buf[i];\r
2257                 if (buf[i] == '\n') {\r
2258                     parse[parse_pos] = NULLCHAR;\r
2259                     if(!suppressKibitz) // [HGM] kibitz\r
2260                         AppendComment(forwardMostMove, StripHighlight(parse));\r
2261                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window\r
2262                         int nrDigit = 0, nrAlph = 0, i;\r
2263                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input\r
2264                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }\r
2265                         parse[parse_pos] = NULLCHAR;\r
2266                         // try to be smart: if it does not look like search info, it should go to\r
2267                         // ICS interaction window after all, not to engine-output window.\r
2268                         for(i=0; i<parse_pos; i++) { // count letters and digits\r
2269                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');\r
2270                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');\r
2271                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');\r
2272                         }\r
2273                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info\r
2274                             OutputKibitz(suppressKibitz, parse);\r
2275                         } else {\r
2276                             char tmp[MSG_SIZ];\r
2277                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);\r
2278                             SendToPlayer(tmp, strlen(tmp));\r
2279                         }\r
2280                     }\r
2281                     started = STARTED_NONE;\r
2282                 } else {\r
2283                     /* Don't match patterns against characters in chatter */\r
2284                     i++;\r
2285                     continue;\r
2286                 }\r
2287             }\r
2288             if (started == STARTED_CHATTER) {\r
2289                 if (buf[i] != '\n') {\r
2290                     /* Don't match patterns against characters in chatter */\r
2291                     i++;\r
2292                     continue;\r
2293                 }\r
2294                 started = STARTED_NONE;\r
2295             }\r
2296 \r
2297             /* Kludge to deal with rcmd protocol */\r
2298             if (firstTime && looking_at(buf, &i, "\001*")) {\r
2299                 DisplayFatalError(&buf[1], 0, 1);\r
2300                 continue;\r
2301             } else {\r
2302                 firstTime = FALSE;\r
2303             }\r
2304 \r
2305             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {\r
2306                 ics_type = ICS_ICC;\r
2307                 ics_prefix = "/";\r
2308                 if (appData.debugMode)\r
2309                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2310                 continue;\r
2311             }\r
2312             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {\r
2313                 ics_type = ICS_FICS;\r
2314                 ics_prefix = "$";\r
2315                 if (appData.debugMode)\r
2316                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2317                 continue;\r
2318             }\r
2319             if (!loggedOn && looking_at(buf, &i, "chess.net")) {\r
2320                 ics_type = ICS_CHESSNET;\r
2321                 ics_prefix = "/";\r
2322                 if (appData.debugMode)\r
2323                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2324                 continue;\r
2325             }\r
2326 \r
2327             if (!loggedOn &&\r
2328                 (looking_at(buf, &i, "\"*\" is *a registered name") ||\r
2329                  looking_at(buf, &i, "Logging you in as \"*\"") ||\r
2330                  looking_at(buf, &i, "will be \"*\""))) {\r
2331               strcpy(ics_handle, star_match[0]);\r
2332               continue;\r
2333             }\r
2334 \r
2335             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {\r
2336               char buf[MSG_SIZ];\r
2337               sprintf(buf, "%s@%s", ics_handle, appData.icsHost);\r
2338               DisplayIcsInteractionTitle(buf);\r
2339               have_set_title = TRUE;\r
2340             }\r
2341 \r
2342             /* skip finger notes */\r
2343             if (started == STARTED_NONE &&\r
2344                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||\r
2345                  (buf[i] == '1' && buf[i+1] == '0')) &&\r
2346                 buf[i+2] == ':' && buf[i+3] == ' ') {\r
2347               started = STARTED_CHATTER;\r
2348               i += 3;\r
2349               continue;\r
2350             }\r
2351 \r
2352             /* skip formula vars */\r
2353             if (started == STARTED_NONE &&\r
2354                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {\r
2355               started = STARTED_CHATTER;\r
2356               i += 3;\r
2357               continue;\r
2358             }\r
2359 \r
2360             oldi = i;\r
2361             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window\r
2362             if (appData.autoKibitz && started == STARTED_NONE && \r
2363                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze\r
2364                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {\r
2365                 if(looking_at(buf, &i, "* kibitzes: ") &&\r
2366                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || \r
2367                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent\r
2368                         suppressKibitz = TRUE;\r
2369                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]\r
2370                                 && (gameMode == IcsPlayingWhite)) ||\r
2371                            (StrStr(star_match[0], gameInfo.black) == star_match[0]\r
2372                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz\r
2373                             started = STARTED_CHATTER; // own kibitz we simply discard\r
2374                         else {\r
2375                             started = STARTED_COMMENT; // make sure it will be collected in parse[]\r
2376                             parse_pos = 0; parse[0] = NULLCHAR;\r
2377                             savingComment = TRUE;\r
2378                             suppressKibitz = gameMode != IcsObserving ? 2 :\r
2379                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;\r
2380                         } \r
2381                         continue;\r
2382                 } else\r
2383                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz\r
2384                     started = STARTED_CHATTER;\r
2385                     suppressKibitz = TRUE;\r
2386                 }\r
2387             } // [HGM] kibitz: end of patch\r
2388 \r
2389             if (appData.zippyTalk || appData.zippyPlay) {\r
2390                 /* [DM] Backup address for color zippy lines */\r
2391                 backup = i;\r
2392 #if ZIPPY\r
2393        #ifdef WIN32\r
2394                if (loggedOn == TRUE)\r
2395                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||\r
2396                           (appData.zippyPlay && ZippyMatch(buf, &backup)));\r
2397        #else\r
2398                 if (ZippyControl(buf, &i) ||\r
2399                     ZippyConverse(buf, &i) ||\r
2400                     (appData.zippyPlay && ZippyMatch(buf, &i))) {\r
2401                       loggedOn = TRUE;\r
2402                       if (!appData.colorize) continue;\r
2403                 }\r
2404        #endif\r
2405 #endif\r
2406             } // [DM] 'else { ' deleted\r
2407                 if (/* Don't color "message" or "messages" output */\r
2408                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||\r
2409                     looking_at(buf, &i, "*. * at *:*: ") ||\r
2410                     looking_at(buf, &i, "--* (*:*): ") ||\r
2411                     /* Regular tells and says */\r
2412                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||\r
2413                     looking_at(buf, &i, "* (your partner) tells you: ") ||\r
2414                     looking_at(buf, &i, "* says: ") ||\r
2415                     /* Message notifications (same color as tells) */\r
2416                     looking_at(buf, &i, "* has left a message ") ||\r
2417                     looking_at(buf, &i, "* just sent you a message:\n") ||\r
2418                     /* Whispers and kibitzes */\r
2419                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||\r
2420                     looking_at(buf, &i, "* kibitzes: ") ||\r
2421                     /* Channel tells */\r
2422                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {\r
2423 \r
2424                   if (tkind == 1 && strchr(star_match[0], ':')) {\r
2425                       /* Avoid "tells you:" spoofs in channels */\r
2426                      tkind = 3;\r
2427                   }\r
2428                   if (star_match[0][0] == NULLCHAR ||\r
2429                       strchr(star_match[0], ' ') ||\r
2430                       (tkind == 3 && strchr(star_match[1], ' '))) {\r
2431                     /* Reject bogus matches */\r
2432                     i = oldi;\r
2433                   } else {\r
2434                     if (appData.colorize) {\r
2435                       if (oldi > next_out) {\r
2436                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2437                         next_out = oldi;\r
2438                       }\r
2439                       switch (tkind) {\r
2440                       case 1:\r
2441                         Colorize(ColorTell, FALSE);\r
2442                         curColor = ColorTell;\r
2443                         break;\r
2444                       case 2:\r
2445                         Colorize(ColorKibitz, FALSE);\r
2446                         curColor = ColorKibitz;\r
2447                         break;\r
2448                       case 3:\r
2449                         p = strrchr(star_match[1], '(');\r
2450                         if (p == NULL) {\r
2451                           p = star_match[1];\r
2452                         } else {\r
2453                           p++;\r
2454                         }\r
2455                         if (atoi(p) == 1) {\r
2456                           Colorize(ColorChannel1, FALSE);\r
2457                           curColor = ColorChannel1;\r
2458                         } else {\r
2459                           Colorize(ColorChannel, FALSE);\r
2460                           curColor = ColorChannel;\r
2461                         }\r
2462                         break;\r
2463                       case 5:\r
2464                         curColor = ColorNormal;\r
2465                         break;\r
2466                       }\r
2467                     }\r
2468                     if (started == STARTED_NONE && appData.autoComment &&\r
2469                         (gameMode == IcsObserving ||\r
2470                          gameMode == IcsPlayingWhite ||\r
2471                          gameMode == IcsPlayingBlack)) {\r
2472                       parse_pos = i - oldi;\r
2473                       memcpy(parse, &buf[oldi], parse_pos);\r
2474                       parse[parse_pos] = NULLCHAR;\r
2475                       started = STARTED_COMMENT;\r
2476                       savingComment = TRUE;\r
2477                     } else {\r
2478                       started = STARTED_CHATTER;\r
2479                       savingComment = FALSE;\r
2480                     }\r
2481                     loggedOn = TRUE;\r
2482                     continue;\r
2483                   }\r
2484                 }\r
2485 \r
2486                 if (looking_at(buf, &i, "* s-shouts: ") ||\r
2487                     looking_at(buf, &i, "* c-shouts: ")) {\r
2488                     if (appData.colorize) {\r
2489                         if (oldi > next_out) {\r
2490                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2491                             next_out = oldi;\r
2492                         }\r
2493                         Colorize(ColorSShout, FALSE);\r
2494                         curColor = ColorSShout;\r
2495                     }\r
2496                     loggedOn = TRUE;\r
2497                     started = STARTED_CHATTER;\r
2498                     continue;\r
2499                 }\r
2500 \r
2501                 if (looking_at(buf, &i, "--->")) {\r
2502                     loggedOn = TRUE;\r
2503                     continue;\r
2504                 }\r
2505 \r
2506                 if (looking_at(buf, &i, "* shouts: ") ||\r
2507                     looking_at(buf, &i, "--> ")) {\r
2508                     if (appData.colorize) {\r
2509                         if (oldi > next_out) {\r
2510                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2511                             next_out = oldi;\r
2512                         }\r
2513                         Colorize(ColorShout, FALSE);\r
2514                         curColor = ColorShout;\r
2515                     }\r
2516                     loggedOn = TRUE;\r
2517                     started = STARTED_CHATTER;\r
2518                     continue;\r
2519                 }\r
2520 \r
2521                 if (looking_at( buf, &i, "Challenge:")) {\r
2522                     if (appData.colorize) {\r
2523                         if (oldi > next_out) {\r
2524                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2525                             next_out = oldi;\r
2526                         }\r
2527                         Colorize(ColorChallenge, FALSE);\r
2528                         curColor = ColorChallenge;\r
2529                     }\r
2530                     loggedOn = TRUE;\r
2531                     continue;\r
2532                 }\r
2533 \r
2534                 if (looking_at(buf, &i, "* offers you") ||\r
2535                     looking_at(buf, &i, "* offers to be") ||\r
2536                     looking_at(buf, &i, "* would like to") ||\r
2537                     looking_at(buf, &i, "* requests to") ||\r
2538                     looking_at(buf, &i, "Your opponent offers") ||\r
2539                     looking_at(buf, &i, "Your opponent requests")) {\r
2540 \r
2541                     if (appData.colorize) {\r
2542                         if (oldi > next_out) {\r
2543                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2544                             next_out = oldi;\r
2545                         }\r
2546                         Colorize(ColorRequest, FALSE);\r
2547                         curColor = ColorRequest;\r
2548                     }\r
2549                     continue;\r
2550                 }\r
2551 \r
2552                 if (looking_at(buf, &i, "* (*) seeking")) {\r
2553                     if (appData.colorize) {\r
2554                         if (oldi > next_out) {\r
2555                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2556                             next_out = oldi;\r
2557                         }\r
2558                         Colorize(ColorSeek, FALSE);\r
2559                         curColor = ColorSeek;\r
2560                     }\r
2561                     continue;\r
2562             }\r
2563 \r
2564             if (looking_at(buf, &i, "\\   ")) {\r
2565                 if (prevColor != ColorNormal) {\r
2566                     if (oldi > next_out) {\r
2567                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2568                         next_out = oldi;\r
2569                     }\r
2570                     Colorize(prevColor, TRUE);\r
2571                     curColor = prevColor;\r
2572                 }\r
2573                 if (savingComment) {\r
2574                     parse_pos = i - oldi;\r
2575                     memcpy(parse, &buf[oldi], parse_pos);\r
2576                     parse[parse_pos] = NULLCHAR;\r
2577                     started = STARTED_COMMENT;\r
2578                 } else {\r
2579                     started = STARTED_CHATTER;\r
2580                 }\r
2581                 continue;\r
2582             }\r
2583 \r
2584             if (looking_at(buf, &i, "Black Strength :") ||\r
2585                 looking_at(buf, &i, "<<< style 10 board >>>") ||\r
2586                 looking_at(buf, &i, "<10>") ||\r
2587                 looking_at(buf, &i, "#@#")) {\r
2588                 /* Wrong board style */\r
2589                 loggedOn = TRUE;\r
2590                 SendToICS(ics_prefix);\r
2591                 SendToICS("set style 12\n");\r
2592                 SendToICS(ics_prefix);\r
2593                 SendToICS("refresh\n");\r
2594                 continue;\r
2595             }\r
2596             \r
2597             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {\r
2598                 ICSInitScript();\r
2599                 have_sent_ICS_logon = 1;\r
2600                 continue;\r
2601             }\r
2602               \r
2603             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && \r
2604                 (looking_at(buf, &i, "\n<12> ") ||\r
2605                  looking_at(buf, &i, "<12> "))) {\r
2606                 loggedOn = TRUE;\r
2607                 if (oldi > next_out) {\r
2608                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2609                 }\r
2610                 next_out = i;\r
2611                 started = STARTED_BOARD;\r
2612                 parse_pos = 0;\r
2613                 continue;\r
2614             }\r
2615 \r
2616             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||\r
2617                 looking_at(buf, &i, "<b1> ")) {\r
2618                 if (oldi > next_out) {\r
2619                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2620                 }\r
2621                 next_out = i;\r
2622                 started = STARTED_HOLDINGS;\r
2623                 parse_pos = 0;\r
2624                 continue;\r
2625             }\r
2626 \r
2627             if (looking_at(buf, &i, "* *vs. * *--- *")) {\r
2628                 loggedOn = TRUE;\r
2629                 /* Header for a move list -- first line */\r
2630 \r
2631                 switch (ics_getting_history) {\r
2632                   case H_FALSE:\r
2633                     switch (gameMode) {\r
2634                       case IcsIdle:\r
2635                       case BeginningOfGame:\r
2636                         /* User typed "moves" or "oldmoves" while we\r
2637                            were idle.  Pretend we asked for these\r
2638                            moves and soak them up so user can step\r
2639                            through them and/or save them.\r
2640                            */\r
2641                         Reset(FALSE, TRUE);\r
2642                         gameMode = IcsObserving;\r
2643                         ModeHighlight();\r
2644                         ics_gamenum = -1;\r
2645                         ics_getting_history = H_GOT_UNREQ_HEADER;\r
2646                         break;\r
2647                       case EditGame: /*?*/\r
2648                       case EditPosition: /*?*/\r
2649                         /* Should above feature work in these modes too? */\r
2650                         /* For now it doesn't */\r
2651                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2652                         break;\r
2653                       default:\r
2654                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2655                         break;\r
2656                     }\r
2657                     break;\r
2658                   case H_REQUESTED:\r
2659                     /* Is this the right one? */\r
2660                     if (gameInfo.white && gameInfo.black &&\r
2661                         strcmp(gameInfo.white, star_match[0]) == 0 &&\r
2662                         strcmp(gameInfo.black, star_match[2]) == 0) {\r
2663                         /* All is well */\r
2664                         ics_getting_history = H_GOT_REQ_HEADER;\r
2665                     }\r
2666                     break;\r
2667                   case H_GOT_REQ_HEADER:\r
2668                   case H_GOT_UNREQ_HEADER:\r
2669                   case H_GOT_UNWANTED_HEADER:\r
2670                   case H_GETTING_MOVES:\r
2671                     /* Should not happen */\r
2672                     DisplayError(_("Error gathering move list: two headers"), 0);\r
2673                     ics_getting_history = H_FALSE;\r
2674                     break;\r
2675                 }\r
2676 \r
2677                 /* Save player ratings into gameInfo if needed */\r
2678                 if ((ics_getting_history == H_GOT_REQ_HEADER ||\r
2679                      ics_getting_history == H_GOT_UNREQ_HEADER) &&\r
2680                     (gameInfo.whiteRating == -1 ||\r
2681                      gameInfo.blackRating == -1)) {\r
2682 \r
2683                     gameInfo.whiteRating = string_to_rating(star_match[1]);\r
2684                     gameInfo.blackRating = string_to_rating(star_match[3]);\r
2685                     if (appData.debugMode)\r
2686                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), \r
2687                               gameInfo.whiteRating, gameInfo.blackRating);\r
2688                 }\r
2689                 continue;\r
2690             }\r
2691 \r
2692             if (looking_at(buf, &i,\r
2693               "* * match, initial time: * minute*, increment: * second")) {\r
2694                 /* Header for a move list -- second line */\r
2695                 /* Initial board will follow if this is a wild game */\r
2696                 if (gameInfo.event != NULL) free(gameInfo.event);\r
2697                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);\r
2698                 gameInfo.event = StrSave(str);\r
2699                 /* [HGM] we switched variant. Translate boards if needed. */\r
2700                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));\r
2701                 continue;\r
2702             }\r
2703 \r
2704             if (looking_at(buf, &i, "Move  ")) {\r
2705                 /* Beginning of a move list */\r
2706                 switch (ics_getting_history) {\r
2707                   case H_FALSE:\r
2708                     /* Normally should not happen */\r
2709                     /* Maybe user hit reset while we were parsing */\r
2710                     break;\r
2711                   case H_REQUESTED:\r
2712                     /* Happens if we are ignoring a move list that is not\r
2713                      * the one we just requested.  Common if the user\r
2714                      * tries to observe two games without turning off\r
2715                      * getMoveList */\r
2716                     break;\r
2717                   case H_GETTING_MOVES:\r
2718                     /* Should not happen */\r
2719                     DisplayError(_("Error gathering move list: nested"), 0);\r
2720                     ics_getting_history = H_FALSE;\r
2721                     break;\r
2722                   case H_GOT_REQ_HEADER:\r
2723                     ics_getting_history = H_GETTING_MOVES;\r
2724                     started = STARTED_MOVES;\r
2725                     parse_pos = 0;\r
2726                     if (oldi > next_out) {\r
2727                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2728                     }\r
2729                     break;\r
2730                   case H_GOT_UNREQ_HEADER:\r
2731                     ics_getting_history = H_GETTING_MOVES;\r
2732                     started = STARTED_MOVES_NOHIDE;\r
2733                     parse_pos = 0;\r
2734                     break;\r
2735                   case H_GOT_UNWANTED_HEADER:\r
2736                     ics_getting_history = H_FALSE;\r
2737                     break;\r
2738                 }\r
2739                 continue;\r
2740             }                           \r
2741             \r
2742             if (looking_at(buf, &i, "% ") ||\r
2743                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)\r
2744                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book\r
2745                 savingComment = FALSE;\r
2746                 switch (started) {\r
2747                   case STARTED_MOVES:\r
2748                   case STARTED_MOVES_NOHIDE:\r
2749                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);\r
2750                     parse[parse_pos + i - oldi] = NULLCHAR;\r
2751                     ParseGameHistory(parse);\r
2752 #if ZIPPY\r
2753                     if (appData.zippyPlay && first.initDone) {\r
2754                         FeedMovesToProgram(&first, forwardMostMove);\r
2755                         if (gameMode == IcsPlayingWhite) {\r
2756                             if (WhiteOnMove(forwardMostMove)) {\r
2757                                 if (first.sendTime) {\r
2758                                   if (first.useColors) {\r
2759                                     SendToProgram("black\n", &first); \r
2760                                   }\r
2761                                   SendTimeRemaining(&first, TRUE);\r
2762                                 }\r
2763 #if 0\r
2764                                 if (first.useColors) {\r
2765                                   SendToProgram("white\ngo\n", &first);\r
2766                                 } else {\r
2767                                   SendToProgram("go\n", &first);\r
2768                                 }\r
2769 #else\r
2770                                 if (first.useColors) {\r
2771                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent\r
2772                                 }\r
2773                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos\r
2774 #endif\r
2775                                 first.maybeThinking = TRUE;\r
2776                             } else {\r
2777                                 if (first.usePlayother) {\r
2778                                   if (first.sendTime) {\r
2779                                     SendTimeRemaining(&first, TRUE);\r
2780                                   }\r
2781                                   SendToProgram("playother\n", &first);\r
2782                                   firstMove = FALSE;\r
2783                                 } else {\r
2784                                   firstMove = TRUE;\r
2785                                 }\r
2786                             }\r
2787                         } else if (gameMode == IcsPlayingBlack) {\r
2788                             if (!WhiteOnMove(forwardMostMove)) {\r
2789                                 if (first.sendTime) {\r
2790                                   if (first.useColors) {\r
2791                                     SendToProgram("white\n", &first);\r
2792                                   }\r
2793                                   SendTimeRemaining(&first, FALSE);\r
2794                                 }\r
2795 #if 0\r
2796                                 if (first.useColors) {\r
2797                                   SendToProgram("black\ngo\n", &first);\r
2798                                 } else {\r
2799                                   SendToProgram("go\n", &first);\r
2800                                 }\r
2801 #else\r
2802                                 if (first.useColors) {\r
2803                                   SendToProgram("black\n", &first);\r
2804                                 }\r
2805                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);\r
2806 #endif\r
2807                                 first.maybeThinking = TRUE;\r
2808                             } else {\r
2809                                 if (first.usePlayother) {\r
2810                                   if (first.sendTime) {\r
2811                                     SendTimeRemaining(&first, FALSE);\r
2812                                   }\r
2813                                   SendToProgram("playother\n", &first);\r
2814                                   firstMove = FALSE;\r
2815                                 } else {\r
2816                                   firstMove = TRUE;\r
2817                                 }\r
2818                             }\r
2819                         }                       \r
2820                     }\r
2821 #endif\r
2822                     if (gameMode == IcsObserving && ics_gamenum == -1) {\r
2823                         /* Moves came from oldmoves or moves command\r
2824                            while we weren't doing anything else.\r
2825                            */\r
2826                         currentMove = forwardMostMove;\r
2827                         ClearHighlights();/*!!could figure this out*/\r
2828                         flipView = appData.flipView;\r
2829                         DrawPosition(FALSE, boards[currentMove]);\r
2830                         DisplayBothClocks();\r
2831                         sprintf(str, "%s vs. %s",\r
2832                                 gameInfo.white, gameInfo.black);\r
2833                         DisplayTitle(str);\r
2834                         gameMode = IcsIdle;\r
2835                     } else {\r
2836                         /* Moves were history of an active game */\r
2837                         if (gameInfo.resultDetails != NULL) {\r
2838                             free(gameInfo.resultDetails);\r
2839                             gameInfo.resultDetails = NULL;\r
2840                         }\r
2841                     }\r
2842                     HistorySet(parseList, backwardMostMove,\r
2843                                forwardMostMove, currentMove-1);\r
2844                     DisplayMove(currentMove - 1);\r
2845                     if (started == STARTED_MOVES) next_out = i;\r
2846                     started = STARTED_NONE;\r
2847                     ics_getting_history = H_FALSE;\r
2848                     break;\r
2849 \r
2850                   case STARTED_OBSERVE:\r
2851                     started = STARTED_NONE;\r
2852                     SendToICS(ics_prefix);\r
2853                     SendToICS("refresh\n");\r
2854                     break;\r
2855 \r
2856                   default:\r
2857                     break;\r
2858                 }\r
2859                 if(bookHit) { // [HGM] book: simulate book reply\r
2860                     static char bookMove[MSG_SIZ]; // a bit generous?\r
2861 \r
2862                     programStats.nodes = programStats.depth = programStats.time = \r
2863                     programStats.score = programStats.got_only_move = 0;\r
2864                     sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
2865 \r
2866                     strcpy(bookMove, "move ");\r
2867                     strcat(bookMove, bookHit);\r
2868                     HandleMachineMove(bookMove, &first);\r
2869                 }\r
2870                 continue;\r
2871             }\r
2872             \r
2873             if ((started == STARTED_MOVES || started == STARTED_BOARD ||\r
2874                  started == STARTED_HOLDINGS ||\r
2875                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {\r
2876                 /* Accumulate characters in move list or board */\r
2877                 parse[parse_pos++] = buf[i];\r
2878             }\r
2879             \r
2880             /* Start of game messages.  Mostly we detect start of game\r
2881                when the first board image arrives.  On some versions\r
2882                of the ICS, though, we need to do a "refresh" after starting\r
2883                to observe in order to get the current board right away. */\r
2884             if (looking_at(buf, &i, "Adding game * to observation list")) {\r
2885                 started = STARTED_OBSERVE;\r
2886                 continue;\r
2887             }\r
2888 \r
2889             /* Handle auto-observe */\r
2890             if (appData.autoObserve &&\r
2891                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&\r
2892                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {\r
2893                 char *player;\r
2894                 /* Choose the player that was highlighted, if any. */\r
2895                 if (star_match[0][0] == '\033' ||\r
2896                     star_match[1][0] != '\033') {\r
2897                     player = star_match[0];\r
2898                 } else {\r
2899                     player = star_match[2];\r
2900                 }\r
2901                 sprintf(str, "%sobserve %s\n",\r
2902                         ics_prefix, StripHighlightAndTitle(player));\r
2903                 SendToICS(str);\r
2904 \r
2905                 /* Save ratings from notify string */\r
2906                 strcpy(player1Name, star_match[0]);\r
2907                 player1Rating = string_to_rating(star_match[1]);\r
2908                 strcpy(player2Name, star_match[2]);\r
2909                 player2Rating = string_to_rating(star_match[3]);\r
2910 \r
2911                 if (appData.debugMode)\r
2912                   fprintf(debugFP, \r
2913                           "Ratings from 'Game notification:' %s %d, %s %d\n",\r
2914                           player1Name, player1Rating,\r
2915                           player2Name, player2Rating);\r
2916 \r
2917                 continue;\r
2918             }\r
2919 \r
2920             /* Deal with automatic examine mode after a game,\r
2921                and with IcsObserving -> IcsExamining transition */\r
2922             if (looking_at(buf, &i, "Entering examine mode for game *") ||\r
2923                 looking_at(buf, &i, "has made you an examiner of game *")) {\r
2924 \r
2925                 int gamenum = atoi(star_match[0]);\r
2926                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&\r
2927                     gamenum == ics_gamenum) {\r
2928                     /* We were already playing or observing this game;\r
2929                        no need to refetch history */\r
2930                     gameMode = IcsExamining;\r
2931                     if (pausing) {\r
2932                         pauseExamForwardMostMove = forwardMostMove;\r
2933                     } else if (currentMove < forwardMostMove) {\r
2934                         ForwardInner(forwardMostMove);\r
2935                     }\r
2936                 } else {\r
2937                     /* I don't think this case really can happen */\r
2938                     SendToICS(ics_prefix);\r
2939                     SendToICS("refresh\n");\r
2940                 }\r
2941                 continue;\r
2942             }    \r
2943             \r
2944             /* Error messages */\r
2945             if (ics_user_moved) {\r
2946                 if (looking_at(buf, &i, "Illegal move") ||\r
2947                     looking_at(buf, &i, "Not a legal move") ||\r
2948                     looking_at(buf, &i, "Your king is in check") ||\r
2949                     looking_at(buf, &i, "It isn't your turn") ||\r
2950                     looking_at(buf, &i, "It is not your move")) {\r
2951                     /* Illegal move */\r
2952                     ics_user_moved = 0;\r
2953                     if (forwardMostMove > backwardMostMove) {\r
2954                         currentMove = --forwardMostMove;\r
2955                         DisplayMove(currentMove - 1); /* before DMError */\r
2956                         DisplayMoveError(_("Illegal move (rejected by ICS)"));\r
2957                         DrawPosition(FALSE, boards[currentMove]);\r
2958                         SwitchClocks();\r
2959                         DisplayBothClocks();\r
2960                     }\r
2961                     continue;\r
2962                 }\r
2963             }\r
2964 \r
2965             if (looking_at(buf, &i, "still have time") ||\r
2966                 looking_at(buf, &i, "not out of time") ||\r
2967                 looking_at(buf, &i, "either player is out of time") ||\r
2968                 looking_at(buf, &i, "has timeseal; checking")) {\r
2969                 /* We must have called his flag a little too soon */\r
2970                 whiteFlag = blackFlag = FALSE;\r
2971                 continue;\r
2972             }\r
2973 \r
2974             if (looking_at(buf, &i, "added * seconds to") ||\r
2975                 looking_at(buf, &i, "seconds were added to")) {\r
2976                 /* Update the clocks */\r
2977                 SendToICS(ics_prefix);\r
2978                 SendToICS("refresh\n");\r
2979                 continue;\r
2980             }\r
2981 \r
2982             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {\r
2983                 ics_clock_paused = TRUE;\r
2984                 StopClocks();\r
2985                 continue;\r
2986             }\r
2987 \r
2988             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {\r
2989                 ics_clock_paused = FALSE;\r
2990                 StartClocks();\r
2991                 continue;\r
2992             }\r
2993 \r
2994             /* Grab player ratings from the Creating: message.\r
2995                Note we have to check for the special case when\r
2996                the ICS inserts things like [white] or [black]. */\r
2997             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||\r
2998                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {\r
2999                 /* star_matches:\r
3000                    0    player 1 name (not necessarily white)\r
3001                    1    player 1 rating\r
3002                    2    empty, white, or black (IGNORED)\r
3003                    3    player 2 name (not necessarily black)\r
3004                    4    player 2 rating\r
3005                    \r
3006                    The names/ratings are sorted out when the game\r
3007                    actually starts (below).\r
3008                 */\r
3009                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));\r
3010                 player1Rating = string_to_rating(star_match[1]);\r
3011                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));\r
3012                 player2Rating = string_to_rating(star_match[4]);\r
3013 \r
3014                 if (appData.debugMode)\r
3015                   fprintf(debugFP, \r
3016                           "Ratings from 'Creating:' %s %d, %s %d\n",\r
3017                           player1Name, player1Rating,\r
3018                           player2Name, player2Rating);\r
3019 \r
3020                 continue;\r
3021             }\r
3022             \r
3023             /* Improved generic start/end-of-game messages */\r
3024             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||\r
3025                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){\r
3026                 /* If tkind == 0: */\r
3027                 /* star_match[0] is the game number */\r
3028                 /*           [1] is the white player's name */\r
3029                 /*           [2] is the black player's name */\r
3030                 /* For end-of-game: */\r
3031                 /*           [3] is the reason for the game end */\r
3032                 /*           [4] is a PGN end game-token, preceded by " " */\r
3033                 /* For start-of-game: */\r
3034                 /*           [3] begins with "Creating" or "Continuing" */\r
3035                 /*           [4] is " *" or empty (don't care). */\r
3036                 int gamenum = atoi(star_match[0]);\r
3037                 char *whitename, *blackname, *why, *endtoken;\r
3038                 ChessMove endtype = (ChessMove) 0;\r
3039 \r
3040                 if (tkind == 0) {\r
3041                   whitename = star_match[1];\r
3042                   blackname = star_match[2];\r
3043                   why = star_match[3];\r
3044                   endtoken = star_match[4];\r
3045                 } else {\r
3046                   whitename = star_match[1];\r
3047                   blackname = star_match[3];\r
3048                   why = star_match[5];\r
3049                   endtoken = star_match[6];\r
3050                 }\r
3051 \r
3052                 /* Game start messages */\r
3053                 if (strncmp(why, "Creating ", 9) == 0 ||\r
3054                     strncmp(why, "Continuing ", 11) == 0) {\r
3055                     gs_gamenum = gamenum;\r
3056                     strcpy(gs_kind, strchr(why, ' ') + 1);\r
3057 #if ZIPPY\r
3058                     if (appData.zippyPlay) {\r
3059                         ZippyGameStart(whitename, blackname);\r
3060                     }\r
3061 #endif /*ZIPPY*/\r
3062                     continue;\r
3063                 }\r
3064 \r
3065                 /* Game end messages */\r
3066                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||\r
3067                     ics_gamenum != gamenum) {\r
3068                     continue;\r
3069                 }\r
3070                 while (endtoken[0] == ' ') endtoken++;\r
3071                 switch (endtoken[0]) {\r
3072                   case '*':\r
3073                   default:\r
3074                     endtype = GameUnfinished;\r
3075                     break;\r
3076                   case '0':\r
3077                     endtype = BlackWins;\r
3078                     break;\r
3079                   case '1':\r
3080                     if (endtoken[1] == '/')\r
3081                       endtype = GameIsDrawn;\r
3082                     else\r
3083                       endtype = WhiteWins;\r
3084                     break;\r
3085                 }\r
3086                 GameEnds(endtype, why, GE_ICS);\r
3087 #if ZIPPY\r
3088                 if (appData.zippyPlay && first.initDone) {\r
3089                     ZippyGameEnd(endtype, why);\r
3090                     if (first.pr == NULL) {\r
3091                       /* Start the next process early so that we'll\r
3092                          be ready for the next challenge */\r
3093                       StartChessProgram(&first);\r
3094                     }\r
3095                     /* Send "new" early, in case this command takes\r
3096                        a long time to finish, so that we'll be ready\r
3097                        for the next challenge. */\r
3098                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'\r
3099                     Reset(TRUE, TRUE);\r
3100                 }\r
3101 #endif /*ZIPPY*/\r
3102                 continue;\r
3103             }\r
3104 \r
3105             if (looking_at(buf, &i, "Removing game * from observation") ||\r
3106                 looking_at(buf, &i, "no longer observing game *") ||\r
3107                 looking_at(buf, &i, "Game * (*) has no examiners")) {\r
3108                 if (gameMode == IcsObserving &&\r
3109                     atoi(star_match[0]) == ics_gamenum)\r
3110                   {\r
3111                       /* icsEngineAnalyze */\r
3112                       if (appData.icsEngineAnalyze) {\r
3113                             ExitAnalyzeMode();\r
3114                             ModeHighlight();\r
3115                       }\r
3116                       StopClocks();\r
3117                       gameMode = IcsIdle;\r
3118                       ics_gamenum = -1;\r
3119                       ics_user_moved = FALSE;\r
3120                   }\r
3121                 continue;\r
3122             }\r
3123 \r
3124             if (looking_at(buf, &i, "no longer examining game *")) {\r
3125                 if (gameMode == IcsExamining &&\r
3126                     atoi(star_match[0]) == ics_gamenum)\r
3127                   {\r
3128                       gameMode = IcsIdle;\r
3129                       ics_gamenum = -1;\r
3130                       ics_user_moved = FALSE;\r
3131                   }\r
3132                 continue;\r
3133             }\r
3134 \r
3135             /* Advance leftover_start past any newlines we find,\r
3136                so only partial lines can get reparsed */\r
3137             if (looking_at(buf, &i, "\n")) {\r
3138                 prevColor = curColor;\r
3139                 if (curColor != ColorNormal) {\r
3140                     if (oldi > next_out) {\r
3141                         SendToPlayer(&buf[next_out], oldi - next_out);\r
3142                         next_out = oldi;\r
3143                     }\r
3144                     Colorize(ColorNormal, FALSE);\r
3145                     curColor = ColorNormal;\r
3146                 }\r
3147                 if (started == STARTED_BOARD) {\r
3148                     started = STARTED_NONE;\r
3149                     parse[parse_pos] = NULLCHAR;\r
3150                     ParseBoard12(parse);\r
3151                     ics_user_moved = 0;\r
3152 \r
3153                     /* Send premove here */\r
3154                     if (appData.premove) {\r
3155                       char str[MSG_SIZ];\r
3156                       if (currentMove == 0 &&\r
3157                           gameMode == IcsPlayingWhite &&\r
3158                           appData.premoveWhite) {\r
3159                         sprintf(str, "%s%s\n", ics_prefix,\r
3160                                 appData.premoveWhiteText);\r
3161                         if (appData.debugMode)\r
3162                           fprintf(debugFP, "Sending premove:\n");\r
3163                         SendToICS(str);\r
3164                       } else if (currentMove == 1 &&\r
3165                                  gameMode == IcsPlayingBlack &&\r
3166                                  appData.premoveBlack) {\r
3167                         sprintf(str, "%s%s\n", ics_prefix,\r
3168                                 appData.premoveBlackText);\r
3169                         if (appData.debugMode)\r
3170                           fprintf(debugFP, "Sending premove:\n");\r
3171                         SendToICS(str);\r
3172                       } else if (gotPremove) {\r
3173                         gotPremove = 0;\r
3174                         ClearPremoveHighlights();\r
3175                         if (appData.debugMode)\r
3176                           fprintf(debugFP, "Sending premove:\n");\r
3177                           UserMoveEvent(premoveFromX, premoveFromY, \r
3178                                         premoveToX, premoveToY, \r
3179                                         premovePromoChar);\r
3180                       }\r
3181                     }\r
3182 \r
3183                     /* Usually suppress following prompt */\r
3184                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {\r
3185                         if (looking_at(buf, &i, "*% ")) {\r
3186                             savingComment = FALSE;\r
3187                         }\r
3188                     }\r
3189                     next_out = i;\r
3190                 } else if (started == STARTED_HOLDINGS) {\r
3191                     int gamenum;\r
3192                     char new_piece[MSG_SIZ];\r
3193                     started = STARTED_NONE;\r
3194                     parse[parse_pos] = NULLCHAR;\r
3195                     if (appData.debugMode)\r
3196                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",\r
3197                                                         parse, currentMove);\r
3198                     if (sscanf(parse, " game %d", &gamenum) == 1 &&\r
3199                         gamenum == ics_gamenum) {\r
3200                         if (gameInfo.variant == VariantNormal) {\r
3201                           /* [HGM] We seem to switch variant during a game!\r
3202                            * Presumably no holdings were displayed, so we have\r
3203                            * to move the position two files to the right to\r
3204                            * create room for them!\r
3205                            */\r
3206                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */\r
3207                           /* Get a move list just to see the header, which\r
3208                              will tell us whether this is really bug or zh */\r
3209                           if (ics_getting_history == H_FALSE) {\r
3210                             ics_getting_history = H_REQUESTED;\r
3211                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3212                             SendToICS(str);\r
3213                           }\r
3214                         }\r
3215                         new_piece[0] = NULLCHAR;\r
3216                         sscanf(parse, "game %d white [%s black [%s <- %s",\r
3217                                &gamenum, white_holding, black_holding,\r
3218                                new_piece);\r
3219                         white_holding[strlen(white_holding)-1] = NULLCHAR;\r
3220                         black_holding[strlen(black_holding)-1] = NULLCHAR;\r
3221                         /* [HGM] copy holdings to board holdings area */\r
3222                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);\r
3223                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);\r
3224 #if ZIPPY\r
3225                         if (appData.zippyPlay && first.initDone) {\r
3226                             ZippyHoldings(white_holding, black_holding,\r
3227                                           new_piece);\r
3228                         }\r
3229 #endif /*ZIPPY*/\r
3230                         if (tinyLayout || smallLayout) {\r
3231                             char wh[16], bh[16];\r
3232                             PackHolding(wh, white_holding);\r
3233                             PackHolding(bh, black_holding);\r
3234                             sprintf(str, "[%s-%s] %s-%s", wh, bh,\r
3235                                     gameInfo.white, gameInfo.black);\r
3236                         } else {\r
3237                             sprintf(str, "%s [%s] vs. %s [%s]",\r
3238                                     gameInfo.white, white_holding,\r
3239                                     gameInfo.black, black_holding);\r
3240                         }\r
3241 \r
3242                         DrawPosition(FALSE, boards[currentMove]);\r
3243                         DisplayTitle(str);\r
3244                     }\r
3245                     /* Suppress following prompt */\r
3246                     if (looking_at(buf, &i, "*% ")) {\r
3247                         savingComment = FALSE;\r
3248                     }\r
3249                     next_out = i;\r
3250                 }\r
3251                 continue;\r
3252             }\r
3253 \r
3254             i++;                /* skip unparsed character and loop back */\r
3255         }\r
3256         \r
3257         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window\r
3258             started != STARTED_HOLDINGS && i > next_out) {\r
3259             SendToPlayer(&buf[next_out], i - next_out);\r
3260             next_out = i;\r
3261         }\r
3262         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above\r
3263         \r
3264         leftover_len = buf_len - leftover_start;\r
3265         /* if buffer ends with something we couldn't parse,\r
3266            reparse it after appending the next read */\r
3267         \r
3268     } else if (count == 0) {\r
3269         RemoveInputSource(isr);\r
3270         DisplayFatalError(_("Connection closed by ICS"), 0, 0);\r
3271     } else {\r
3272         DisplayFatalError(_("Error reading from ICS"), error, 1);\r
3273     }\r
3274 }\r
3275 \r
3276 \r
3277 /* Board style 12 looks like this:\r
3278    \r
3279    <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
3280    \r
3281  * The "<12> " is stripped before it gets to this routine.  The two\r
3282  * trailing 0's (flip state and clock ticking) are later addition, and\r
3283  * some chess servers may not have them, or may have only the first.\r
3284  * Additional trailing fields may be added in the future.  \r
3285  */\r
3286 \r
3287 #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
3288 \r
3289 #define RELATION_OBSERVING_PLAYED    0\r
3290 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */\r
3291 #define RELATION_PLAYING_MYMOVE      1\r
3292 #define RELATION_PLAYING_NOTMYMOVE  -1\r
3293 #define RELATION_EXAMINING           2\r
3294 #define RELATION_ISOLATED_BOARD     -3\r
3295 #define RELATION_STARTING_POSITION  -4   /* FICS only */\r
3296 \r
3297 void\r
3298 ParseBoard12(string)\r
3299      char *string;\r
3300\r
3301     GameMode newGameMode;\r
3302     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;\r
3303     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;\r
3304     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;\r
3305     char to_play, board_chars[200];\r
3306     char move_str[500], str[500], elapsed_time[500];\r
3307     char black[32], white[32];\r
3308     Board board;\r
3309     int prevMove = currentMove;\r
3310     int ticking = 2;\r
3311     ChessMove moveType;\r
3312     int fromX, fromY, toX, toY;\r
3313     char promoChar;\r
3314     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */\r
3315     char *bookHit = NULL; // [HGM] book\r
3316 \r
3317     fromX = fromY = toX = toY = -1;\r
3318     \r
3319     newGame = FALSE;\r
3320 \r
3321     if (appData.debugMode)\r
3322       fprintf(debugFP, _("Parsing board: %s\n"), string);\r
3323 \r
3324     move_str[0] = NULLCHAR;\r
3325     elapsed_time[0] = NULLCHAR;\r
3326     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */\r
3327         int  i = 0, j;\r
3328         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {\r
3329             if(string[i] == ' ') { ranks++; files = 0; }\r
3330             else files++;\r
3331             i++;\r
3332         }\r
3333         for(j = 0; j <i; j++) board_chars[j] = string[j];\r
3334         board_chars[i] = '\0';\r
3335         string += i + 1;\r
3336     }\r
3337     n = sscanf(string, PATTERN, &to_play, &double_push,\r
3338                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,\r
3339                &gamenum, white, black, &relation, &basetime, &increment,\r
3340                &white_stren, &black_stren, &white_time, &black_time,\r
3341                &moveNum, str, elapsed_time, move_str, &ics_flip,\r
3342                &ticking);\r
3343 \r
3344     if (n < 21) {\r
3345         sprintf(str, _("Failed to parse board string:\n\"%s\""), string);\r
3346         DisplayError(str, 0);\r
3347         return;\r
3348     }\r
3349 \r
3350     /* Convert the move number to internal form */\r
3351     moveNum = (moveNum - 1) * 2;\r
3352     if (to_play == 'B') moveNum++;\r
3353     if (moveNum >= MAX_MOVES) {\r
3354       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),\r
3355                         0, 1);\r
3356       return;\r
3357     }\r
3358     \r
3359     switch (relation) {\r
3360       case RELATION_OBSERVING_PLAYED:\r
3361       case RELATION_OBSERVING_STATIC:\r
3362         if (gamenum == -1) {\r
3363             /* Old ICC buglet */\r
3364             relation = RELATION_OBSERVING_STATIC;\r
3365         }\r
3366         newGameMode = IcsObserving;\r
3367         break;\r
3368       case RELATION_PLAYING_MYMOVE:\r
3369       case RELATION_PLAYING_NOTMYMOVE:\r
3370         newGameMode =\r
3371           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?\r
3372             IcsPlayingWhite : IcsPlayingBlack;\r
3373         break;\r
3374       case RELATION_EXAMINING:\r
3375         newGameMode = IcsExamining;\r
3376         break;\r
3377       case RELATION_ISOLATED_BOARD:\r
3378       default:\r
3379         /* Just display this board.  If user was doing something else,\r
3380            we will forget about it until the next board comes. */ \r
3381         newGameMode = IcsIdle;\r
3382         break;\r
3383       case RELATION_STARTING_POSITION:\r
3384         newGameMode = gameMode;\r
3385         break;\r
3386     }\r
3387     \r
3388     /* Modify behavior for initial board display on move listing\r
3389        of wild games.\r
3390        */\r
3391     switch (ics_getting_history) {\r
3392       case H_FALSE:\r
3393       case H_REQUESTED:\r
3394         break;\r
3395       case H_GOT_REQ_HEADER:\r
3396       case H_GOT_UNREQ_HEADER:\r
3397         /* This is the initial position of the current game */\r
3398         gamenum = ics_gamenum;\r
3399         moveNum = 0;            /* old ICS bug workaround */\r
3400         if (to_play == 'B') {\r
3401           startedFromSetupPosition = TRUE;\r
3402           blackPlaysFirst = TRUE;\r
3403           moveNum = 1;\r
3404           if (forwardMostMove == 0) forwardMostMove = 1;\r
3405           if (backwardMostMove == 0) backwardMostMove = 1;\r
3406           if (currentMove == 0) currentMove = 1;\r
3407         }\r
3408         newGameMode = gameMode;\r
3409         relation = RELATION_STARTING_POSITION; /* ICC needs this */\r
3410         break;\r
3411       case H_GOT_UNWANTED_HEADER:\r
3412         /* This is an initial board that we don't want */\r
3413         return;\r
3414       case H_GETTING_MOVES:\r
3415         /* Should not happen */\r
3416         DisplayError(_("Error gathering move list: extra board"), 0);\r
3417         ics_getting_history = H_FALSE;\r
3418         return;\r
3419     }\r
3420     \r
3421     /* Take action if this is the first board of a new game, or of a\r
3422        different game than is currently being displayed.  */\r
3423     if (gamenum != ics_gamenum || newGameMode != gameMode ||\r
3424         relation == RELATION_ISOLATED_BOARD) {\r
3425         \r
3426         /* Forget the old game and get the history (if any) of the new one */\r
3427         if (gameMode != BeginningOfGame) {\r
3428           Reset(FALSE, TRUE);\r
3429         }\r
3430         newGame = TRUE;\r
3431         if (appData.autoRaiseBoard) BoardToTop();\r
3432         prevMove = -3;\r
3433         if (gamenum == -1) {\r
3434             newGameMode = IcsIdle;\r
3435         } else if (moveNum > 0 && newGameMode != IcsIdle &&\r
3436                    appData.getMoveList) {\r
3437             /* Need to get game history */\r
3438             ics_getting_history = H_REQUESTED;\r
3439             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3440             SendToICS(str);\r
3441         }\r
3442         \r
3443         /* Initially flip the board to have black on the bottom if playing\r
3444            black or if the ICS flip flag is set, but let the user change\r
3445            it with the Flip View button. */\r
3446         flipView = appData.autoFlipView ? \r
3447           (newGameMode == IcsPlayingBlack) || ics_flip :\r
3448           appData.flipView;\r
3449         \r
3450         /* Done with values from previous mode; copy in new ones */\r
3451         gameMode = newGameMode;\r
3452         ModeHighlight();\r
3453         ics_gamenum = gamenum;\r
3454         if (gamenum == gs_gamenum) {\r
3455             int klen = strlen(gs_kind);\r
3456             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;\r
3457             sprintf(str, "ICS %s", gs_kind);\r
3458             gameInfo.event = StrSave(str);\r
3459         } else {\r
3460             gameInfo.event = StrSave("ICS game");\r
3461         }\r
3462         gameInfo.site = StrSave(appData.icsHost);\r
3463         gameInfo.date = PGNDate();\r
3464         gameInfo.round = StrSave("-");\r
3465         gameInfo.white = StrSave(white);\r
3466         gameInfo.black = StrSave(black);\r
3467         timeControl = basetime * 60 * 1000;\r
3468         timeControl_2 = 0;\r
3469         timeIncrement = increment * 1000;\r
3470         movesPerSession = 0;\r
3471         gameInfo.timeControl = TimeControlTagValue();\r
3472         VariantSwitch(board, StringToVariant(gameInfo.event) );\r
3473   if (appData.debugMode) {\r
3474     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);\r
3475     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));\r
3476     setbuf(debugFP, NULL);\r
3477   }\r
3478 \r
3479         gameInfo.outOfBook = NULL;\r
3480         \r
3481         /* Do we have the ratings? */\r
3482         if (strcmp(player1Name, white) == 0 &&\r
3483             strcmp(player2Name, black) == 0) {\r
3484             if (appData.debugMode)\r
3485               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3486                       player1Rating, player2Rating);\r
3487             gameInfo.whiteRating = player1Rating;\r
3488             gameInfo.blackRating = player2Rating;\r
3489         } else if (strcmp(player2Name, white) == 0 &&\r
3490                    strcmp(player1Name, black) == 0) {\r
3491             if (appData.debugMode)\r
3492               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3493                       player2Rating, player1Rating);\r
3494             gameInfo.whiteRating = player2Rating;\r
3495             gameInfo.blackRating = player1Rating;\r
3496         }\r
3497         player1Name[0] = player2Name[0] = NULLCHAR;\r
3498 \r
3499         /* Silence shouts if requested */\r
3500         if (appData.quietPlay &&\r
3501             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {\r
3502             SendToICS(ics_prefix);\r
3503             SendToICS("set shout 0\n");\r
3504         }\r
3505     }\r
3506     \r
3507     /* Deal with midgame name changes */\r
3508     if (!newGame) {\r
3509         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {\r
3510             if (gameInfo.white) free(gameInfo.white);\r
3511             gameInfo.white = StrSave(white);\r
3512         }\r
3513         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {\r
3514             if (gameInfo.black) free(gameInfo.black);\r
3515             gameInfo.black = StrSave(black);\r
3516         }\r
3517     }\r
3518     \r
3519     /* Throw away game result if anything actually changes in examine mode */\r
3520     if (gameMode == IcsExamining && !newGame) {\r
3521         gameInfo.result = GameUnfinished;\r
3522         if (gameInfo.resultDetails != NULL) {\r
3523             free(gameInfo.resultDetails);\r
3524             gameInfo.resultDetails = NULL;\r
3525         }\r
3526     }\r
3527     \r
3528     /* In pausing && IcsExamining mode, we ignore boards coming\r
3529        in if they are in a different variation than we are. */\r
3530     if (pauseExamInvalid) return;\r
3531     if (pausing && gameMode == IcsExamining) {\r
3532         if (moveNum <= pauseExamForwardMostMove) {\r
3533             pauseExamInvalid = TRUE;\r
3534             forwardMostMove = pauseExamForwardMostMove;\r
3535             return;\r
3536         }\r
3537     }\r
3538     \r
3539   if (appData.debugMode) {\r
3540     fprintf(debugFP, "load %dx%d board\n", files, ranks);\r
3541   }\r
3542     /* Parse the board */\r
3543     for (k = 0; k < ranks; k++) {\r
3544       for (j = 0; j < files; j++)\r
3545         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);\r
3546       if(gameInfo.holdingsWidth > 1) {\r
3547            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;\r
3548            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;\r
3549       }\r
3550     }\r
3551     CopyBoard(boards[moveNum], board);\r
3552     if (moveNum == 0) {\r
3553         startedFromSetupPosition =\r
3554           !CompareBoards(board, initialPosition);\r
3555         if(startedFromSetupPosition)\r
3556             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */\r
3557     }\r
3558 \r
3559     /* [HGM] Set castling rights. Take the outermost Rooks,\r
3560        to make it also work for FRC opening positions. Note that board12\r
3561        is really defective for later FRC positions, as it has no way to\r
3562        indicate which Rook can castle if they are on the same side of King.\r
3563        For the initial position we grant rights to the outermost Rooks,\r
3564        and remember thos rights, and we then copy them on positions\r
3565        later in an FRC game. This means WB might not recognize castlings with\r
3566        Rooks that have moved back to their original position as illegal,\r
3567        but in ICS mode that is not its job anyway.\r
3568     */\r
3569     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)\r
3570     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;\r
3571 \r
3572         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)\r
3573             if(board[0][i] == WhiteRook) j = i;\r
3574         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3575         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)\r
3576             if(board[0][i] == WhiteRook) j = i;\r
3577         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3578         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)\r
3579             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;\r
3580         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3581         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)\r
3582             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;\r
3583         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3584 \r
3585         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }\r
3586         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)\r
3587             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;\r
3588         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)\r
3589             if(board[BOARD_HEIGHT-1][k] == bKing)\r
3590                 initialRights[5] = castlingRights[moveNum][5] = k;\r
3591     } else { int r;\r
3592         r = castlingRights[moveNum][0] = initialRights[0];\r
3593         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;\r
3594         r = castlingRights[moveNum][1] = initialRights[1];\r
3595         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;\r
3596         r = castlingRights[moveNum][3] = initialRights[3];\r
3597         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;\r
3598         r = castlingRights[moveNum][4] = initialRights[4];\r
3599         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;\r
3600         /* wildcastle kludge: always assume King has rights */\r
3601         r = castlingRights[moveNum][2] = initialRights[2];\r
3602         r = castlingRights[moveNum][5] = initialRights[5];\r
3603     }\r
3604     /* [HGM] e.p. rights. Assume that ICS sends file number here? */\r
3605     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;\r
3606 \r
3607     \r
3608     if (ics_getting_history == H_GOT_REQ_HEADER ||\r
3609         ics_getting_history == H_GOT_UNREQ_HEADER) {\r
3610         /* This was an initial position from a move list, not\r
3611            the current position */\r
3612         return;\r
3613     }\r
3614     \r
3615     /* Update currentMove and known move number limits */\r
3616     newMove = newGame || moveNum > forwardMostMove;\r
3617 \r
3618     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */\r
3619     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {\r
3620         takeback = forwardMostMove - moveNum;\r
3621         for (i = 0; i < takeback; i++) {\r
3622              if (appData.debugMode) fprintf(debugFP, "take back move\n");\r
3623              SendToProgram("undo\n", &first);\r
3624         }\r
3625     }\r
3626 \r
3627     if (newGame) {\r
3628         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3629         if (gameMode == IcsExamining && moveNum == 0) {\r
3630           /* Workaround for ICS limitation: we are not told the wild\r
3631              type when starting to examine a game.  But if we ask for\r
3632              the move list, the move list header will tell us */\r
3633             ics_getting_history = H_REQUESTED;\r
3634             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3635             SendToICS(str);\r
3636         }\r
3637     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove\r
3638                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {\r
3639         forwardMostMove = moveNum;\r
3640         if (!pausing || currentMove > forwardMostMove)\r
3641           currentMove = forwardMostMove;\r
3642     } else {\r
3643         /* New part of history that is not contiguous with old part */ \r
3644         if (pausing && gameMode == IcsExamining) {\r
3645             pauseExamInvalid = TRUE;\r
3646             forwardMostMove = pauseExamForwardMostMove;\r
3647             return;\r
3648         }\r
3649         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3650         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {\r
3651             ics_getting_history = H_REQUESTED;\r
3652             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3653             SendToICS(str);\r
3654         }\r
3655     }\r
3656     \r
3657     /* Update the clocks */\r
3658     if (strchr(elapsed_time, '.')) {\r
3659       /* Time is in ms */\r
3660       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;\r
3661       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;\r
3662     } else {\r
3663       /* Time is in seconds */\r
3664       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;\r
3665       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;\r
3666     }\r
3667       \r
3668 \r
3669 #if ZIPPY\r
3670     if (appData.zippyPlay && newGame &&\r
3671         gameMode != IcsObserving && gameMode != IcsIdle &&\r
3672         gameMode != IcsExamining)\r
3673       ZippyFirstBoard(moveNum, basetime, increment);\r
3674 #endif\r
3675     \r
3676     /* Put the move on the move list, first converting\r
3677        to canonical algebraic form. */\r
3678     if (moveNum > 0) {\r
3679   if (appData.debugMode) {\r
3680     if (appData.debugMode) { int f = forwardMostMove;\r
3681         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,\r
3682                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
3683     }\r
3684     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);\r
3685     fprintf(debugFP, "moveNum = %d\n", moveNum);\r
3686     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);\r
3687     setbuf(debugFP, NULL);\r
3688   }\r
3689         if (moveNum <= backwardMostMove) {\r
3690             /* We don't know what the board looked like before\r
3691                this move.  Punt. */\r
3692             strcpy(parseList[moveNum - 1], move_str);\r
3693             strcat(parseList[moveNum - 1], " ");\r
3694             strcat(parseList[moveNum - 1], elapsed_time);\r
3695             moveList[moveNum - 1][0] = NULLCHAR;\r
3696         } else if (strcmp(move_str, "none") == 0) {\r
3697             // [HGM] long SAN: swapped order; test for 'none' before parsing move\r
3698             /* Again, we don't know what the board looked like;\r
3699                this is really the start of the game. */\r
3700             parseList[moveNum - 1][0] = NULLCHAR;\r
3701             moveList[moveNum - 1][0] = NULLCHAR;\r
3702             backwardMostMove = moveNum;\r
3703             startedFromSetupPosition = TRUE;\r
3704             fromX = fromY = toX = toY = -1;\r
3705         } else {\r
3706           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. \r
3707           //                 So we parse the long-algebraic move string in stead of the SAN move\r
3708           int valid; char buf[MSG_SIZ], *prom;\r
3709 \r
3710           // str looks something like "Q/a1-a2"; kill the slash\r
3711           if(str[1] == '/') \r
3712                 sprintf(buf, "%c%s", str[0], str+2);\r
3713           else  strcpy(buf, str); // might be castling\r
3714           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) \r
3715                 strcat(buf, prom); // long move lacks promo specification!\r
3716           if(!appData.testLegality) {\r
3717                 if(appData.debugMode) \r
3718                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);\r
3719                 strcpy(move_str, buf);\r
3720           }\r
3721           valid = ParseOneMove(move_str, moveNum - 1, &moveType,\r
3722                                 &fromX, &fromY, &toX, &toY, &promoChar)\r
3723                || ParseOneMove(buf, moveNum - 1, &moveType,\r
3724                                 &fromX, &fromY, &toX, &toY, &promoChar);\r
3725           // end of long SAN patch\r
3726           if (valid) {\r
3727             (void) CoordsToAlgebraic(boards[moveNum - 1],\r
3728                                      PosFlags(moveNum - 1), EP_UNKNOWN,\r
3729                                      fromY, fromX, toY, toX, promoChar,\r
3730                                      parseList[moveNum-1]);\r
3731             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,\r
3732                              castlingRights[moveNum]) ) {\r
3733               case MT_NONE:\r
3734               case MT_STALEMATE:\r
3735               default:\r
3736                 break;\r
3737               case MT_CHECK:\r
3738                 if(gameInfo.variant != VariantShogi)\r
3739                     strcat(parseList[moveNum - 1], "+");\r
3740                 break;\r
3741               case MT_CHECKMATE:\r
3742                 strcat(parseList[moveNum - 1], "#");\r
3743                 break;\r
3744             }\r
3745             strcat(parseList[moveNum - 1], " ");\r
3746             strcat(parseList[moveNum - 1], elapsed_time);\r
3747             /* currentMoveString is set as a side-effect of ParseOneMove */\r
3748             strcpy(moveList[moveNum - 1], currentMoveString);\r
3749             strcat(moveList[moveNum - 1], "\n");\r
3750           } else {\r
3751             /* Move from ICS was illegal!?  Punt. */\r
3752   if (appData.debugMode) {\r
3753     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);\r
3754     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
3755   }\r
3756 #if 0\r
3757             if (appData.testLegality && appData.debugMode) {\r
3758                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);\r
3759                 DisplayError(str, 0);\r
3760             }\r
3761 #endif\r
3762             strcpy(parseList[moveNum - 1], move_str);\r
3763             strcat(parseList[moveNum - 1], " ");\r
3764             strcat(parseList[moveNum - 1], elapsed_time);\r
3765             moveList[moveNum - 1][0] = NULLCHAR;\r
3766             fromX = fromY = toX = toY = -1;\r
3767           }\r
3768         }\r
3769   if (appData.debugMode) {\r
3770     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);\r
3771     setbuf(debugFP, NULL);\r
3772   }\r
3773 \r
3774 #if ZIPPY\r
3775         /* Send move to chess program (BEFORE animating it). */\r
3776         if (appData.zippyPlay && !newGame && newMove && \r
3777            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {\r
3778 \r
3779             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||\r
3780                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {\r
3781                 if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3782                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),\r
3783                             move_str);\r
3784                     DisplayError(str, 0);\r
3785                 } else {\r
3786                     if (first.sendTime) {\r
3787                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);\r
3788                     }\r
3789                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book\r
3790                     if (firstMove && !bookHit) {\r
3791                         firstMove = FALSE;\r
3792                         if (first.useColors) {\r
3793                           SendToProgram(gameMode == IcsPlayingWhite ?\r
3794                                         "white\ngo\n" :\r
3795                                         "black\ngo\n", &first);\r
3796                         } else {\r
3797                           SendToProgram("go\n", &first);\r
3798                         }\r
3799                         first.maybeThinking = TRUE;\r
3800                     }\r
3801                 }\r
3802             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {\r
3803               if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3804                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);\r
3805                 DisplayError(str, 0);\r
3806               } else {\r
3807                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!\r
3808                 SendMoveToProgram(moveNum - 1, &first);\r
3809               }\r
3810             }\r
3811         }\r
3812 #endif\r
3813     }\r
3814 \r
3815     if (moveNum > 0 && !gotPremove) {\r
3816         /* If move comes from a remote source, animate it.  If it\r
3817            isn't remote, it will have already been animated. */\r
3818         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {\r
3819             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);\r
3820         }\r
3821         if (!pausing && appData.highlightLastMove) {\r
3822             SetHighlights(fromX, fromY, toX, toY);\r
3823         }\r
3824     }\r
3825     \r
3826     /* Start the clocks */\r
3827     whiteFlag = blackFlag = FALSE;\r
3828     appData.clockMode = !(basetime == 0 && increment == 0);\r
3829     if (ticking == 0) {\r
3830       ics_clock_paused = TRUE;\r
3831       StopClocks();\r
3832     } else if (ticking == 1) {\r
3833       ics_clock_paused = FALSE;\r
3834     }\r
3835     if (gameMode == IcsIdle ||\r
3836         relation == RELATION_OBSERVING_STATIC ||\r
3837         relation == RELATION_EXAMINING ||\r
3838         ics_clock_paused)\r
3839       DisplayBothClocks();\r
3840     else\r
3841       StartClocks();\r
3842     \r
3843     /* Display opponents and material strengths */\r
3844     if (gameInfo.variant != VariantBughouse &&\r
3845         gameInfo.variant != VariantCrazyhouse) {\r
3846         if (tinyLayout || smallLayout) {\r
3847             if(gameInfo.variant == VariantNormal)\r
3848                 sprintf(str, "%s(%d) %s(%d) {%d %d}", \r
3849                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3850                     basetime, increment);\r
3851             else\r
3852                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", \r
3853                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3854                     basetime, increment, (int) gameInfo.variant);\r
3855         } else {\r
3856             if(gameInfo.variant == VariantNormal)\r
3857                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", \r
3858                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3859                     basetime, increment);\r
3860             else\r
3861                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", \r
3862                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3863                     basetime, increment, VariantName(gameInfo.variant));\r
3864         }\r
3865         DisplayTitle(str);\r
3866   if (appData.debugMode) {\r
3867     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);\r
3868   }\r
3869     }\r
3870 \r
3871    \r
3872     /* Display the board */\r
3873     if (!pausing) {\r
3874       \r
3875       if (appData.premove)\r
3876           if (!gotPremove || \r
3877              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||\r
3878              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))\r
3879               ClearPremoveHighlights();\r
3880 \r
3881       DrawPosition(FALSE, boards[currentMove]);\r
3882       DisplayMove(moveNum - 1);\r
3883       if (appData.ringBellAfterMoves && !ics_user_moved)\r
3884         RingBell();\r
3885     }\r
3886 \r
3887     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
3888 #if ZIPPY\r
3889     if(bookHit) { // [HGM] book: simulate book reply\r
3890         static char bookMove[MSG_SIZ]; // a bit generous?\r
3891 \r
3892         programStats.nodes = programStats.depth = programStats.time = \r
3893         programStats.score = programStats.got_only_move = 0;\r
3894         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
3895 \r
3896         strcpy(bookMove, "move ");\r
3897         strcat(bookMove, bookHit);\r
3898         HandleMachineMove(bookMove, &first);\r
3899     }\r
3900 #endif\r
3901 }\r
3902 \r
3903 void\r
3904 GetMoveListEvent()\r
3905 {\r
3906     char buf[MSG_SIZ];\r
3907     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {\r
3908         ics_getting_history = H_REQUESTED;\r
3909         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);\r
3910         SendToICS(buf);\r
3911     }\r
3912 }\r
3913 \r
3914 void\r
3915 AnalysisPeriodicEvent(force)\r
3916      int force;\r
3917 {\r
3918     if (((programStats.ok_to_send == 0 || programStats.line_is_book)\r
3919          && !force) || !appData.periodicUpdates)\r
3920       return;\r
3921 \r
3922     /* Send . command to Crafty to collect stats */\r
3923     SendToProgram(".\n", &first);\r
3924 \r
3925     /* Don't send another until we get a response (this makes\r
3926        us stop sending to old Crafty's which don't understand\r
3927        the "." command (sending illegal cmds resets node count & time,\r
3928        which looks bad)) */\r
3929     programStats.ok_to_send = 0;\r
3930 }\r
3931 \r
3932 void\r
3933 SendMoveToProgram(moveNum, cps)\r
3934      int moveNum;\r
3935      ChessProgramState *cps;\r
3936 {\r
3937     char buf[MSG_SIZ];\r
3938 \r
3939     if (cps->useUsermove) {\r
3940       SendToProgram("usermove ", cps);\r
3941     }\r
3942     if (cps->useSAN) {\r
3943       char *space;\r
3944       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {\r
3945         int len = space - parseList[moveNum];\r
3946         memcpy(buf, parseList[moveNum], len);\r
3947         buf[len++] = '\n';\r
3948         buf[len] = NULLCHAR;\r
3949       } else {\r
3950         sprintf(buf, "%s\n", parseList[moveNum]);\r
3951       }\r
3952       SendToProgram(buf, cps);\r
3953     } else {\r
3954       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */\r
3955         AlphaRank(moveList[moveNum], 4);\r
3956         SendToProgram(moveList[moveNum], cps);\r
3957         AlphaRank(moveList[moveNum], 4); // and back\r
3958       } else\r
3959       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by\r
3960        * the engine. It would be nice to have a better way to identify castle \r
3961        * moves here. */\r
3962       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)\r
3963                                                                          && cps->useOOCastle) {\r
3964         int fromX = moveList[moveNum][0] - AAA; \r
3965         int fromY = moveList[moveNum][1] - ONE;\r
3966         int toX = moveList[moveNum][2] - AAA; \r
3967         int toY = moveList[moveNum][3] - ONE;\r
3968         if((boards[moveNum][fromY][fromX] == WhiteKing \r
3969             && boards[moveNum][toY][toX] == WhiteRook)\r
3970            || (boards[moveNum][fromY][fromX] == BlackKing \r
3971                && boards[moveNum][toY][toX] == BlackRook)) {\r
3972           if(toX > fromX) SendToProgram("O-O\n", cps);\r
3973           else SendToProgram("O-O-O\n", cps);\r
3974         }\r
3975         else SendToProgram(moveList[moveNum], cps);\r
3976       }\r
3977       else SendToProgram(moveList[moveNum], cps);\r
3978       /* End of additions by Tord */\r
3979     }\r
3980 \r
3981     /* [HGM] setting up the opening has brought engine in force mode! */\r
3982     /*       Send 'go' if we are in a mode where machine should play. */\r
3983     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&\r
3984         (gameMode == TwoMachinesPlay   ||\r
3985 #ifdef ZIPPY\r
3986          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||\r
3987 #endif\r
3988          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {\r
3989         SendToProgram("go\n", cps);\r
3990   if (appData.debugMode) {\r
3991     fprintf(debugFP, "(extra)\n");\r
3992   }\r
3993     }\r
3994     setboardSpoiledMachineBlack = 0;\r
3995 }\r
3996 \r
3997 void\r
3998 SendMoveToICS(moveType, fromX, fromY, toX, toY)\r
3999      ChessMove moveType;\r
4000      int fromX, fromY, toX, toY;\r
4001 {\r
4002     char user_move[MSG_SIZ];\r
4003 \r
4004     switch (moveType) {\r
4005       default:\r
4006         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),\r
4007                 (int)moveType, fromX, fromY, toX, toY);\r
4008         DisplayError(user_move + strlen("say "), 0);\r
4009         break;\r
4010       case WhiteKingSideCastle:\r
4011       case BlackKingSideCastle:\r
4012       case WhiteQueenSideCastleWild:\r
4013       case BlackQueenSideCastleWild:\r
4014       /* PUSH Fabien */\r
4015       case WhiteHSideCastleFR:\r
4016       case BlackHSideCastleFR:\r
4017       /* POP Fabien */\r
4018         sprintf(user_move, "o-o\n");\r
4019         break;\r
4020       case WhiteQueenSideCastle:\r
4021       case BlackQueenSideCastle:\r
4022       case WhiteKingSideCastleWild:\r
4023       case BlackKingSideCastleWild:\r
4024       /* PUSH Fabien */\r
4025       case WhiteASideCastleFR:\r
4026       case BlackASideCastleFR:\r
4027       /* POP Fabien */\r
4028         sprintf(user_move, "o-o-o\n");\r
4029         break;\r
4030       case WhitePromotionQueen:\r
4031       case BlackPromotionQueen:\r
4032       case WhitePromotionRook:\r
4033       case BlackPromotionRook:\r
4034       case WhitePromotionBishop:\r
4035       case BlackPromotionBishop:\r
4036       case WhitePromotionKnight:\r
4037       case BlackPromotionKnight:\r
4038       case WhitePromotionKing:\r
4039       case BlackPromotionKing:\r
4040       case WhitePromotionChancellor:\r
4041       case BlackPromotionChancellor:\r
4042       case WhitePromotionArchbishop:\r
4043       case BlackPromotionArchbishop:\r
4044         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)\r
4045             sprintf(user_move, "%c%c%c%c=%c\n",\r
4046                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4047                 PieceToChar(WhiteFerz));\r
4048         else if(gameInfo.variant == VariantGreat)\r
4049             sprintf(user_move, "%c%c%c%c=%c\n",\r
4050                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4051                 PieceToChar(WhiteMan));\r
4052         else\r
4053             sprintf(user_move, "%c%c%c%c=%c\n",\r
4054                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4055                 PieceToChar(PromoPiece(moveType)));\r
4056         break;\r
4057       case WhiteDrop:\r
4058       case BlackDrop:\r
4059         sprintf(user_move, "%c@%c%c\n",\r
4060                 ToUpper(PieceToChar((ChessSquare) fromX)),\r
4061                 AAA + toX, ONE + toY);\r
4062         break;\r
4063       case NormalMove:\r
4064       case WhiteCapturesEnPassant:\r
4065       case BlackCapturesEnPassant:\r
4066       case IllegalMove:  /* could be a variant we don't quite understand */\r
4067         sprintf(user_move, "%c%c%c%c\n",\r
4068                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);\r
4069         break;\r
4070     }\r
4071     SendToICS(user_move);\r
4072 }\r
4073 \r
4074 void\r
4075 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)\r
4076      int rf, ff, rt, ft;\r
4077      char promoChar;\r
4078      char move[7];\r
4079 {\r
4080     if (rf == DROP_RANK) {\r
4081         sprintf(move, "%c@%c%c\n",\r
4082                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);\r
4083     } else {\r
4084         if (promoChar == 'x' || promoChar == NULLCHAR) {\r
4085             sprintf(move, "%c%c%c%c\n",\r
4086                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);\r
4087         } else {\r
4088             sprintf(move, "%c%c%c%c%c\n",\r
4089                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);\r
4090         }\r
4091     }\r
4092 }\r
4093 \r
4094 void\r
4095 ProcessICSInitScript(f)\r
4096      FILE *f;\r
4097 {\r
4098     char buf[MSG_SIZ];\r
4099 \r
4100     while (fgets(buf, MSG_SIZ, f)) {\r
4101         SendToICSDelayed(buf,(long)appData.msLoginDelay);\r
4102     }\r
4103 \r
4104     fclose(f);\r
4105 }\r
4106 \r
4107 \r
4108 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */\r
4109 void\r
4110 AlphaRank(char *move, int n)\r
4111 {\r
4112 //    char *p = move, c; int x, y;\r
4113 \r
4114     if (appData.debugMode) {\r
4115         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);\r
4116     }\r
4117 \r
4118     if(move[1]=='*' && \r
4119        move[2]>='0' && move[2]<='9' &&\r
4120        move[3]>='a' && move[3]<='x'    ) {\r
4121         move[1] = '@';\r
4122         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;\r
4123         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;\r
4124     } else\r
4125     if(move[0]>='0' && move[0]<='9' &&\r
4126        move[1]>='a' && move[1]<='x' &&\r
4127        move[2]>='0' && move[2]<='9' &&\r
4128        move[3]>='a' && move[3]<='x'    ) {\r
4129         /* input move, Shogi -> normal */\r
4130         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;\r
4131         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;\r
4132         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;\r
4133         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;\r
4134     } else\r
4135     if(move[1]=='@' &&\r
4136        move[3]>='0' && move[3]<='9' &&\r
4137        move[2]>='a' && move[2]<='x'    ) {\r
4138         move[1] = '*';\r
4139         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';\r
4140         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';\r
4141     } else\r
4142     if(\r
4143        move[0]>='a' && move[0]<='x' &&\r
4144        move[3]>='0' && move[3]<='9' &&\r
4145        move[2]>='a' && move[2]<='x'    ) {\r
4146          /* output move, normal -> Shogi */\r
4147         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';\r
4148         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';\r
4149         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';\r
4150         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';\r
4151         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';\r
4152     }\r
4153     if (appData.debugMode) {\r
4154         fprintf(debugFP, "   out = '%s'\n", move);\r
4155     }\r
4156 }\r
4157 \r
4158 /* Parser for moves from gnuchess, ICS, or user typein box */\r
4159 Boolean\r
4160 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)\r
4161      char *move;\r
4162      int moveNum;\r
4163      ChessMove *moveType;\r
4164      int *fromX, *fromY, *toX, *toY;\r
4165      char *promoChar;\r
4166 {       \r
4167     if (appData.debugMode) {\r
4168         fprintf(debugFP, "move to parse: %s\n", move);\r
4169     }\r
4170     *moveType = yylexstr(moveNum, move);\r
4171 \r
4172     switch (*moveType) {\r
4173       case WhitePromotionChancellor:\r
4174       case BlackPromotionChancellor:\r
4175       case WhitePromotionArchbishop:\r
4176       case BlackPromotionArchbishop:\r
4177       case WhitePromotionQueen:\r
4178       case BlackPromotionQueen:\r
4179       case WhitePromotionRook:\r
4180       case BlackPromotionRook:\r
4181       case WhitePromotionBishop:\r
4182       case BlackPromotionBishop:\r
4183       case WhitePromotionKnight:\r
4184       case BlackPromotionKnight:\r
4185       case WhitePromotionKing:\r
4186       case BlackPromotionKing:\r
4187       case NormalMove:\r
4188       case WhiteCapturesEnPassant:\r
4189       case BlackCapturesEnPassant:\r
4190       case WhiteKingSideCastle:\r
4191       case WhiteQueenSideCastle:\r
4192       case BlackKingSideCastle:\r
4193       case BlackQueenSideCastle:\r
4194       case WhiteKingSideCastleWild:\r
4195       case WhiteQueenSideCastleWild:\r
4196       case BlackKingSideCastleWild:\r
4197       case BlackQueenSideCastleWild:\r
4198       /* Code added by Tord: */\r
4199       case WhiteHSideCastleFR:\r
4200       case WhiteASideCastleFR:\r
4201       case BlackHSideCastleFR:\r
4202       case BlackASideCastleFR:\r
4203       /* End of code added by Tord */\r
4204       case IllegalMove:         /* bug or odd chess variant */\r
4205         *fromX = currentMoveString[0] - AAA;\r
4206         *fromY = currentMoveString[1] - ONE;\r
4207         *toX = currentMoveString[2] - AAA;\r
4208         *toY = currentMoveString[3] - ONE;\r
4209         *promoChar = currentMoveString[4];\r
4210         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||\r
4211             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {\r
4212     if (appData.debugMode) {\r
4213         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);\r
4214     }\r
4215             *fromX = *fromY = *toX = *toY = 0;\r
4216             return FALSE;\r
4217         }\r
4218         if (appData.testLegality) {\r
4219           return (*moveType != IllegalMove);\r
4220         } else {\r
4221           return !(fromX == fromY && toX == toY);\r
4222         }\r
4223 \r
4224       case WhiteDrop:\r
4225       case BlackDrop:\r
4226         *fromX = *moveType == WhiteDrop ?\r
4227           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
4228           (int) CharToPiece(ToLower(currentMoveString[0]));\r
4229         *fromY = DROP_RANK;\r
4230         *toX = currentMoveString[2] - AAA;\r
4231         *toY = currentMoveString[3] - ONE;\r
4232         *promoChar = NULLCHAR;\r
4233         return TRUE;\r
4234 \r
4235       case AmbiguousMove:\r
4236       case ImpossibleMove:\r
4237       case (ChessMove) 0:       /* end of file */\r
4238       case ElapsedTime:\r
4239       case Comment:\r
4240       case PGNTag:\r
4241       case NAG:\r
4242       case WhiteWins:\r
4243       case BlackWins:\r
4244       case GameIsDrawn:\r
4245       default:\r
4246     if (appData.debugMode) {\r
4247         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);\r
4248     }\r
4249         /* bug? */\r
4250         *fromX = *fromY = *toX = *toY = 0;\r
4251         *promoChar = NULLCHAR;\r
4252         return FALSE;\r
4253     }\r
4254 }\r
4255 \r
4256 #if 0\r
4257 /* [AS] FRC game initialization */\r
4258 static int FindEmptySquare( Board board, int n )\r
4259 {\r
4260     int i = 0;\r
4261 \r
4262     while( 1 ) {\r
4263         while( board[0][i] != EmptySquare ) i++;\r
4264         if( n == 0 )\r
4265             break;\r
4266         n--;\r
4267         i++;\r
4268     }\r
4269 \r
4270     return i;\r
4271 }\r
4272 \r
4273 static void ShuffleFRC( Board board )\r
4274 {\r
4275     int i;\r
4276 \r
4277     srand( time(0) );\r
4278     \r
4279     for( i=0; i<8; i++ ) {\r
4280         board[0][i] = EmptySquare;\r
4281     }\r
4282 \r
4283     board[0][(rand() % 4)*2  ] = WhiteBishop; /* On dark square */\r
4284     board[0][(rand() % 4)*2+1] = WhiteBishop; /* On lite square */\r
4285     board[0][FindEmptySquare(board, rand() % 6)] = WhiteQueen;\r
4286     board[0][FindEmptySquare(board, rand() % 5)] = WhiteKnight;\r
4287     board[0][FindEmptySquare(board, rand() % 4)] = WhiteKnight;\r
4288     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4289     initialRights[1]  = initialRights[4]  =\r
4290     castlingRights[0][1] = castlingRights[0][4] = i;\r
4291     board[0][ i=FindEmptySquare(board, 0) ] = WhiteKing;\r
4292     initialRights[2]  = initialRights[5]  =\r
4293     castlingRights[0][2] = castlingRights[0][5] = i;\r
4294     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4295     initialRights[0]  = initialRights[3]  =\r
4296     castlingRights[0][0] = castlingRights[0][3] = i;\r
4297 \r
4298     for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {\r
4299         board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;\r
4300     }\r
4301 }\r
4302 \r
4303 static unsigned char FRC_KnightTable[10] = {\r
4304     0x00, 0x01, 0x02, 0x03, 0x11, 0x12, 0x13, 0x22, 0x23, 0x33\r
4305 };\r
4306 \r
4307 static void SetupFRC( Board board, int pos_index )\r
4308 {\r
4309     int i;\r
4310     unsigned char knights;\r
4311 \r
4312     /* Bring the position index into a safe range (just in case...) */\r
4313     if( pos_index < 0 ) pos_index = 0;\r
4314 \r
4315     pos_index %= 960;\r
4316 \r
4317     /* Clear the board */\r
4318     for( i=0; i<8; i++ ) {\r
4319         board[0][i] = EmptySquare;\r
4320     }\r
4321 \r
4322     /* Place bishops and queen */\r
4323     board[0][ (pos_index % 4)*2 + 1 ] = WhiteBishop; /* On lite square */\r
4324     pos_index /= 4;\r
4325     \r
4326     board[0][ (pos_index % 4)*2     ] = WhiteBishop; /* On dark square */\r
4327     pos_index /= 4;\r
4328 \r
4329     board[0][ FindEmptySquare(board, pos_index % 6) ] = WhiteQueen;\r
4330     pos_index /= 6;\r
4331 \r
4332     /* Place knigths */\r
4333     knights = FRC_KnightTable[ pos_index ];\r
4334 \r
4335     board[0][ FindEmptySquare(board, knights / 16) ] = WhiteKnight;\r
4336     board[0][ FindEmptySquare(board, knights % 16) ] = WhiteKnight;\r
4337 \r
4338     /* Place rooks and king */\r
4339     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4340     initialRights[1]  = initialRights[4]  =\r
4341     castlingRights[0][1] = castlingRights[0][4] = i;\r
4342     board[0][ i=FindEmptySquare(board, 0) ] = WhiteKing;\r
4343     initialRights[2]  = initialRights[5]  =\r
4344     castlingRights[0][2] = castlingRights[0][5] = i;\r
4345     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4346     initialRights[0]  = initialRights[3]  =\r
4347     castlingRights[0][0] = castlingRights[0][3] = i;\r
4348 \r
4349     /* Mirror piece placement for black */\r
4350     for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {\r
4351         board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;\r
4352     }\r
4353 }\r
4354 #else\r
4355 // [HGM] shuffle: a more general way to suffle opening setups, applicable to arbitrry variants.\r
4356 // All positions will have equal probability, but the current method will not provide a unique\r
4357 // numbering scheme for arrays that contain 3 or more pieces of the same kind.\r
4358 #define DARK 1\r
4359 #define LITE 2\r
4360 #define ANY 3\r
4361 \r
4362 int squaresLeft[4];\r
4363 int piecesLeft[(int)BlackPawn];\r
4364 u64 seed, nrOfShuffles;\r
4365 \r
4366 void GetPositionNumber()\r
4367 {       // sets global variable seed\r
4368         int i;\r
4369 \r
4370         seed = appData.defaultFrcPosition;\r
4371         if(seed < 0) { // randomize based on time for negative FRC position numbers\r
4372                 srandom(time(0)); \r
4373                 for(i=0; i<50; i++) seed += random();\r
4374                 seed = random() ^ random() >> 8 ^ random() << 8;\r
4375                 if(seed<0) seed = -seed;\r
4376         }\r
4377 }\r
4378 \r
4379 int put(Board board, int pieceType, int rank, int n, int shade)\r
4380 // put the piece on the (n-1)-th empty squares of the given shade\r
4381 {\r
4382         int i;\r
4383 \r
4384         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {\r
4385                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {\r
4386                         board[rank][i] = (ChessSquare) pieceType;\r
4387                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;\r
4388                         squaresLeft[ANY]--;\r
4389                         piecesLeft[pieceType]--; \r
4390                         return i;\r
4391                 }\r
4392         }\r
4393         return -1;\r
4394 }\r
4395 \r
4396 \r
4397 void AddOnePiece(Board board, int pieceType, int rank, int shade)\r
4398 // calculate where the next piece goes, (any empty square), and put it there\r
4399 {\r
4400         int i;\r
4401 \r
4402         i = seed % squaresLeft[shade];\r
4403         nrOfShuffles *= squaresLeft[shade];\r
4404         seed /= squaresLeft[shade];\r
4405         put(board, pieceType, rank, i, shade);\r
4406 }\r
4407 \r
4408 void AddTwoPieces(Board board, int pieceType, int rank)\r
4409 // calculate where the next 2 identical pieces go, (any empty square), and put it there\r
4410 {\r
4411         int i, n=squaresLeft[ANY], j=n-1, k;\r
4412 \r
4413         k = n*(n-1)/2; // nr of possibilities, not counting permutations\r
4414         i = seed % k;  // pick one\r
4415         nrOfShuffles *= k;\r
4416         seed /= k;\r
4417         while(i >= j) i -= j--;\r
4418         j = n - 1 - j; i += j;\r
4419         put(board, pieceType, rank, j, ANY);\r
4420         put(board, pieceType, rank, i, ANY);\r
4421 }\r
4422 \r
4423 void SetUpShuffle(Board board, int number)\r
4424 {\r
4425         int i, p, first=1;\r
4426 \r
4427         GetPositionNumber(); nrOfShuffles = 1;\r
4428 \r
4429         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;\r
4430         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;\r
4431         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];\r
4432 \r
4433         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;\r
4434 \r
4435         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board\r
4436             p = (int) board[0][i];\r
4437             if(p < (int) BlackPawn) piecesLeft[p] ++;\r
4438             board[0][i] = EmptySquare;\r
4439         }\r
4440 \r
4441         if(PosFlags(0) & F_ALL_CASTLE_OK) {\r
4442             // shuffles restricted to allow normal castling put KRR first\r
4443             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle\r
4444                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);\r
4445             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles\r
4446                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);\r
4447             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling\r
4448                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);\r
4449             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling\r
4450                 put(board, WhiteRook, 0, 0, ANY);\r
4451             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle\r
4452         }\r
4453 \r
4454         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)\r
4455             // only for even boards make effort to put pairs of colorbound pieces on opposite colors\r
4456             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {\r
4457                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;\r
4458                 while(piecesLeft[p] >= 2) {\r
4459                     AddOnePiece(board, p, 0, LITE);\r
4460                     AddOnePiece(board, p, 0, DARK);\r
4461                 }\r
4462                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)\r
4463             }\r
4464 \r
4465         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {\r
4466             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere\r
4467             // but we leave King and Rooks for last, to possibly obey FRC restriction\r
4468             if(p == (int)WhiteRook) continue;\r
4469             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations\r
4470             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece\r
4471         }\r
4472 \r
4473         // now everything is placed, except perhaps King (Unicorn) and Rooks\r
4474 \r
4475         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {\r
4476             // Last King gets castling rights\r
4477             while(piecesLeft[(int)WhiteUnicorn]) {\r
4478                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);\r
4479                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;\r
4480             }\r
4481 \r
4482             while(piecesLeft[(int)WhiteKing]) {\r
4483                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);\r
4484                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;\r
4485             }\r
4486 \r
4487 \r
4488         } else {\r
4489             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);\r
4490             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);\r
4491         }\r
4492 \r
4493         // Only Rooks can be left; simply place them all\r
4494         while(piecesLeft[(int)WhiteRook]) {\r
4495                 i = put(board, WhiteRook, 0, 0, ANY);\r
4496                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights\r
4497                         if(first) {\r
4498                                 first=0;\r
4499                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;\r
4500                         }\r
4501                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;\r
4502                 }\r
4503         }\r
4504         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white\r
4505             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;\r
4506         }\r
4507 \r
4508         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize\r
4509 }\r
4510 \r
4511 #endif\r
4512 \r
4513 int SetCharTable( char *table, const char * map )\r
4514 /* [HGM] moved here from winboard.c because of its general usefulness */\r
4515 /*       Basically a safe strcpy that uses the last character as King */\r
4516 {\r
4517     int result = FALSE; int NrPieces;\r
4518 \r
4519     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare \r
4520                     && NrPieces >= 12 && !(NrPieces&1)) {\r
4521         int i; /* [HGM] Accept even length from 12 to 34 */\r
4522 \r
4523         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';\r
4524         for( i=0; i<NrPieces/2-1; i++ ) {\r
4525             table[i] = map[i];\r
4526             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];\r
4527         }\r
4528         table[(int) WhiteKing]  = map[NrPieces/2-1];\r
4529         table[(int) BlackKing]  = map[NrPieces-1];\r
4530 \r
4531         result = TRUE;\r
4532     }\r
4533 \r
4534     return result;\r
4535 }\r
4536 \r
4537 void Prelude(Board board)\r
4538 {       // [HGM] superchess: random selection of exo-pieces\r
4539         int i, j, k; ChessSquare p; \r
4540         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };\r
4541 \r
4542         GetPositionNumber(); // use FRC position number\r
4543 \r
4544         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table\r
4545             SetCharTable(pieceToChar, appData.pieceToCharTable);\r
4546             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) \r
4547                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;\r
4548         }\r
4549 \r
4550         j = seed%4;                 seed /= 4; \r
4551         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);\r
4552         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4553         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4554         j = seed%3 + (seed%3 >= j); seed /= 3; \r
4555         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);\r
4556         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4557         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4558         j = seed%3;                 seed /= 3; \r
4559         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);\r
4560         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4561         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4562         j = seed%2 + (seed%2 >= j); seed /= 2; \r
4563         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);\r
4564         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4565         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4566         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);\r
4567         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);\r
4568         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);\r
4569         put(board, exoPieces[0],    0, 0, ANY);\r
4570         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];\r
4571 }\r
4572 \r
4573 void\r
4574 InitPosition(redraw)\r
4575      int redraw;\r
4576 {\r
4577     ChessSquare (* pieces)[BOARD_SIZE];\r
4578     int i, j, pawnRow, overrule,\r
4579     oldx = gameInfo.boardWidth,\r
4580     oldy = gameInfo.boardHeight,\r
4581     oldh = gameInfo.holdingsWidth,\r
4582     oldv = gameInfo.variant;\r
4583 \r
4584     currentMove = forwardMostMove = backwardMostMove = 0;\r
4585     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request\r
4586 \r
4587     /* [AS] Initialize pv info list [HGM] and game status */\r
4588     {\r
4589         for( i=0; i<MAX_MOVES; i++ ) {\r
4590             pvInfoList[i].depth = 0;\r
4591             epStatus[i]=EP_NONE;\r
4592             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
4593         }\r
4594 \r
4595         initialRulePlies = 0; /* 50-move counter start */\r
4596 \r
4597         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;\r
4598         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;\r
4599     }\r
4600 \r
4601     \r
4602     /* [HGM] logic here is completely changed. In stead of full positions */\r
4603     /* the initialized data only consist of the two backranks. The switch */\r
4604     /* selects which one we will use, which is than copied to the Board   */\r
4605     /* initialPosition, which for the rest is initialized by Pawns and    */\r
4606     /* empty squares. This initial position is then copied to boards[0],  */\r
4607     /* possibly after shuffling, so that it remains available.            */\r
4608 \r
4609     gameInfo.holdingsWidth = 0; /* default board sizes */\r
4610     gameInfo.boardWidth    = 8;\r
4611     gameInfo.boardHeight   = 8;\r
4612     gameInfo.holdingsSize  = 0;\r
4613     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */\r
4614     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */\r
4615     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); \r
4616 \r
4617     switch (gameInfo.variant) {\r
4618     case VariantFischeRandom:\r
4619       shuffleOpenings = TRUE;\r
4620     default:\r
4621       pieces = FIDEArray;\r
4622       break;\r
4623     case VariantShatranj:\r
4624       pieces = ShatranjArray;\r
4625       nrCastlingRights = 0;\r
4626       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); \r
4627       break;\r
4628     case VariantTwoKings:\r
4629       pieces = twoKingsArray;\r
4630       nrCastlingRights = 8;                 /* add rights for second King */\r
4631       castlingRights[0][6] = initialRights[2] = 5;\r
4632       castlingRights[0][7] = initialRights[5] = 5;\r
4633       castlingRank[6] = 0;\r
4634       castlingRank[7] = BOARD_HEIGHT-1;\r
4635       break;\r
4636     case VariantCapaRandom:\r
4637       shuffleOpenings = TRUE;\r
4638     case VariantCapablanca:\r
4639       pieces = CapablancaArray;\r
4640       gameInfo.boardWidth = 10;\r
4641       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
4642       break;\r
4643     case VariantGothic:\r
4644       pieces = GothicArray;\r
4645       gameInfo.boardWidth = 10;\r
4646       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
4647       break;\r
4648     case VariantJanus:\r
4649       pieces = JanusArray;\r
4650       gameInfo.boardWidth = 10;\r
4651       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); \r
4652       nrCastlingRights = 6;\r
4653         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
4654         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
4655         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;\r
4656         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
4657         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4658         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;\r
4659       break;\r
4660     case VariantFalcon:\r
4661       pieces = FalconArray;\r
4662       gameInfo.boardWidth = 10;\r
4663       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); \r
4664       break;\r
4665     case VariantXiangqi:\r
4666       pieces = XiangqiArray;\r
4667       gameInfo.boardWidth  = 9;\r
4668       gameInfo.boardHeight = 10;\r
4669       nrCastlingRights = 0;\r
4670       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); \r
4671       break;\r
4672     case VariantShogi:\r
4673       pieces = ShogiArray;\r
4674       gameInfo.boardWidth  = 9;\r
4675       gameInfo.boardHeight = 9;\r
4676       gameInfo.holdingsSize = 7;\r
4677       nrCastlingRights = 0;\r
4678       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); \r
4679       break;\r
4680     case VariantCourier:\r
4681       pieces = CourierArray;\r
4682       gameInfo.boardWidth  = 12;\r
4683       nrCastlingRights = 0;\r
4684       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); \r
4685       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
4686       break;\r
4687     case VariantKnightmate:\r
4688       pieces = KnightmateArray;\r
4689       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); \r
4690       break;\r
4691     case VariantFairy:\r
4692       pieces = fairyArray;\r
4693       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); \r
4694       break;\r
4695     case VariantGreat:\r
4696       pieces = GreatArray;\r
4697       gameInfo.boardWidth = 10;\r
4698       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");\r
4699       gameInfo.holdingsSize = 8;\r
4700       break;\r
4701     case VariantSuper:\r
4702       pieces = FIDEArray;\r
4703       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");\r
4704       gameInfo.holdingsSize = 8;\r
4705       startedFromSetupPosition = TRUE;\r
4706       break;\r
4707     case VariantCrazyhouse:\r
4708     case VariantBughouse:\r
4709       pieces = FIDEArray;\r
4710       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); \r
4711       gameInfo.holdingsSize = 5;\r
4712       break;\r
4713     case VariantWildCastle:\r
4714       pieces = FIDEArray;\r
4715       /* !!?shuffle with kings guaranteed to be on d or e file */\r
4716       shuffleOpenings = 1;\r
4717       break;\r
4718     case VariantNoCastle:\r
4719       pieces = FIDEArray;\r
4720       nrCastlingRights = 0;\r
4721       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
4722       /* !!?unconstrained back-rank shuffle */\r
4723       shuffleOpenings = 1;\r
4724       break;\r
4725     }\r
4726 \r
4727     overrule = 0;\r
4728     if(appData.NrFiles >= 0) {\r
4729         if(gameInfo.boardWidth != appData.NrFiles) overrule++;\r
4730         gameInfo.boardWidth = appData.NrFiles;\r
4731     }\r
4732     if(appData.NrRanks >= 0) {\r
4733         gameInfo.boardHeight = appData.NrRanks;\r
4734     }\r
4735     if(appData.holdingsSize >= 0) {\r
4736         i = appData.holdingsSize;\r
4737         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;\r
4738         gameInfo.holdingsSize = i;\r
4739     }\r
4740     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;\r
4741     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)\r
4742         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);\r
4743 \r
4744     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */\r
4745     if(pawnRow < 1) pawnRow = 1;\r
4746 \r
4747     /* User pieceToChar list overrules defaults */\r
4748     if(appData.pieceToCharTable != NULL)\r
4749         SetCharTable(pieceToChar, appData.pieceToCharTable);\r
4750 \r
4751     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;\r
4752 \r
4753         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)\r
4754             s = (ChessSquare) 0; /* account holding counts in guard band */\r
4755         for( i=0; i<BOARD_HEIGHT; i++ )\r
4756             initialPosition[i][j] = s;\r
4757 \r
4758         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;\r
4759         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];\r
4760         initialPosition[pawnRow][j] = WhitePawn;\r
4761         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;\r
4762         if(gameInfo.variant == VariantXiangqi) {\r
4763             if(j&1) {\r
4764                 initialPosition[pawnRow][j] = \r
4765                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;\r
4766                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {\r
4767                    initialPosition[2][j] = WhiteCannon;\r
4768                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;\r
4769                 }\r
4770             }\r
4771         }\r
4772         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];\r
4773     }\r
4774     if( (gameInfo.variant == VariantShogi) && !overrule ) {\r
4775 \r
4776             j=BOARD_LEFT+1;\r
4777             initialPosition[1][j] = WhiteBishop;\r
4778             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;\r
4779             j=BOARD_RGHT-2;\r
4780             initialPosition[1][j] = WhiteRook;\r
4781             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;\r
4782     }\r
4783 \r
4784     if( nrCastlingRights == -1) {\r
4785         /* [HGM] Build normal castling rights (must be done after board sizing!) */\r
4786         /*       This sets default castling rights from none to normal corners   */\r
4787         /* Variants with other castling rights must set them themselves above    */\r
4788         nrCastlingRights = 6;\r
4789        \r
4790         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
4791         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
4792         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;\r
4793         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
4794         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4795         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;\r
4796      }\r
4797 \r
4798      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);\r
4799      if(gameInfo.variant == VariantGreat) { // promotion commoners\r
4800         initialPosition[PieceToNumber(WhiteMan)][BOARD_RGHT-1] = WhiteMan;\r
4801         initialPosition[PieceToNumber(WhiteMan)][BOARD_RGHT-2] = 9;\r
4802         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;\r
4803         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;\r
4804      }\r
4805 #if 0\r
4806     if(gameInfo.variant == VariantFischeRandom) {\r
4807       if( appData.defaultFrcPosition < 0 ) {\r
4808         ShuffleFRC( initialPosition );\r
4809       }\r
4810       else {\r
4811         SetupFRC( initialPosition, appData.defaultFrcPosition );\r
4812       }\r
4813       startedFromSetupPosition = TRUE;\r
4814     } else \r
4815 #else\r
4816   if (appData.debugMode) {\r
4817     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);\r
4818   }\r
4819     if(shuffleOpenings) {\r
4820         SetUpShuffle(initialPosition, appData.defaultFrcPosition);\r
4821         startedFromSetupPosition = TRUE;\r
4822     }\r
4823 #endif\r
4824     if(startedFromPositionFile) {\r
4825       /* [HGM] loadPos: use PositionFile for every new game */\r
4826       CopyBoard(initialPosition, filePosition);\r
4827       for(i=0; i<nrCastlingRights; i++)\r
4828           castlingRights[0][i] = initialRights[i] = fileRights[i];\r
4829       startedFromSetupPosition = TRUE;\r
4830     }\r
4831 \r
4832     CopyBoard(boards[0], initialPosition);\r
4833 \r
4834     if(oldx != gameInfo.boardWidth ||\r
4835        oldy != gameInfo.boardHeight ||\r
4836        oldh != gameInfo.holdingsWidth\r
4837 #ifdef GOTHIC\r
4838        || oldv == VariantGothic ||        // For licensing popups\r
4839        gameInfo.variant == VariantGothic\r
4840 #endif\r
4841 #ifdef FALCON\r
4842        || oldv == VariantFalcon ||\r
4843        gameInfo.variant == VariantFalcon\r
4844 #endif\r
4845                                          )\r
4846             InitDrawingSizes(-2 ,0);\r
4847 \r
4848     if (redraw)\r
4849       DrawPosition(TRUE, boards[currentMove]);\r
4850 }\r
4851 \r
4852 void\r
4853 SendBoard(cps, moveNum)\r
4854      ChessProgramState *cps;\r
4855      int moveNum;\r
4856 {\r
4857     char message[MSG_SIZ];\r
4858     \r
4859     if (cps->useSetboard) {\r
4860       char* fen = PositionToFEN(moveNum, cps->useFEN960);\r
4861       sprintf(message, "setboard %s\n", fen);\r
4862       SendToProgram(message, cps);\r
4863       free(fen);\r
4864 \r
4865     } else {\r
4866       ChessSquare *bp;\r
4867       int i, j;\r
4868       /* Kludge to set black to move, avoiding the troublesome and now\r
4869        * deprecated "black" command.\r
4870        */\r
4871       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);\r
4872 \r
4873       SendToProgram("edit\n", cps);\r
4874       SendToProgram("#\n", cps);\r
4875       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4876         bp = &boards[moveNum][i][BOARD_LEFT];\r
4877         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4878           if ((int) *bp < (int) BlackPawn) {\r
4879             sprintf(message, "%c%c%c\n", PieceToChar(*bp), \r
4880                     AAA + j, ONE + i);\r
4881             if(message[0] == '+' || message[0] == '~') {\r
4882                 sprintf(message, "%c%c%c+\n",\r
4883                         PieceToChar((ChessSquare)(DEMOTED *bp)),\r
4884                         AAA + j, ONE + i);\r
4885             }\r
4886             if(cps->alphaRank) { /* [HGM] shogi: translate coords */\r
4887                 message[1] = BOARD_RGHT   - 1 - j + '1';\r
4888                 message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
4889             }\r
4890             SendToProgram(message, cps);\r
4891           }\r
4892         }\r
4893       }\r
4894     \r
4895       SendToProgram("c\n", cps);\r
4896       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4897         bp = &boards[moveNum][i][BOARD_LEFT];\r
4898         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4899           if (((int) *bp != (int) EmptySquare)\r
4900               && ((int) *bp >= (int) BlackPawn)) {\r
4901             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),\r
4902                     AAA + j, ONE + i);\r
4903             if(message[0] == '+' || message[0] == '~') {\r
4904                 sprintf(message, "%c%c%c+\n",\r
4905                         PieceToChar((ChessSquare)(DEMOTED *bp)),\r
4906                         AAA + j, ONE + i);\r
4907             }\r
4908             if(cps->alphaRank) { /* [HGM] shogi: translate coords */\r
4909                 message[1] = BOARD_RGHT   - 1 - j + '1';\r
4910                 message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
4911             }\r
4912             SendToProgram(message, cps);\r
4913           }\r
4914         }\r
4915       }\r
4916     \r
4917       SendToProgram(".\n", cps);\r
4918     }\r
4919     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */\r
4920 }\r
4921 \r
4922 int\r
4923 IsPromotion(fromX, fromY, toX, toY)\r
4924      int fromX, fromY, toX, toY;\r
4925 {\r
4926     /* [HGM] add Shogi promotions */\r
4927     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;\r
4928     ChessSquare piece;\r
4929 \r
4930     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||\r
4931       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;\r
4932    /* [HGM] Note to self: line above also weeds out drops */\r
4933     piece = boards[currentMove][fromY][fromX];\r
4934     if(gameInfo.variant == VariantShogi) {\r
4935         promotionZoneSize = 3;\r
4936         highestPromotingPiece = (int)WhiteKing;\r
4937         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,\r
4938            and if in normal chess we then allow promotion to King, why not\r
4939            allow promotion of other piece in Shogi?                         */\r
4940     }\r
4941     if((int)piece >= BlackPawn) {\r
4942         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)\r
4943              return FALSE;\r
4944         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;\r
4945     } else {\r
4946         if(  toY < BOARD_HEIGHT - promotionZoneSize &&\r
4947            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;\r
4948     }\r
4949     return ( (int)piece <= highestPromotingPiece );\r
4950 }\r
4951 \r
4952 int\r
4953 InPalace(row, column)\r
4954      int row, column;\r
4955 {   /* [HGM] for Xiangqi */\r
4956     if( (row < 3 || row > BOARD_HEIGHT-4) &&\r
4957          column < (BOARD_WIDTH + 4)/2 &&\r
4958          column > (BOARD_WIDTH - 5)/2 ) return TRUE;\r
4959     return FALSE;\r
4960 }\r
4961 \r
4962 int\r
4963 PieceForSquare (x, y)\r
4964      int x;\r
4965      int y;\r
4966 {\r
4967   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)\r
4968      return -1;\r
4969   else\r
4970      return boards[currentMove][y][x];\r
4971 }\r
4972 \r
4973 int\r
4974 OKToStartUserMove(x, y)\r
4975      int x, y;\r
4976 {\r
4977     ChessSquare from_piece;\r
4978     int white_piece;\r
4979 \r
4980     if (matchMode) return FALSE;\r
4981     if (gameMode == EditPosition) return TRUE;\r
4982 \r
4983     if (x >= 0 && y >= 0)\r
4984       from_piece = boards[currentMove][y][x];\r
4985     else\r
4986       from_piece = EmptySquare;\r
4987 \r
4988     if (from_piece == EmptySquare) return FALSE;\r
4989 \r
4990     white_piece = (int)from_piece >= (int)WhitePawn &&\r
4991       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */\r
4992 \r
4993     switch (gameMode) {\r
4994       case PlayFromGameFile:\r
4995       case AnalyzeFile:\r
4996       case TwoMachinesPlay:\r
4997       case EndOfGame:\r
4998         return FALSE;\r
4999 \r
5000       case IcsObserving:\r
5001       case IcsIdle:\r
5002         return FALSE;\r
5003 \r
5004       case MachinePlaysWhite:\r
5005       case IcsPlayingBlack:\r
5006         if (appData.zippyPlay) return FALSE;\r
5007         if (white_piece) {\r
5008             DisplayMoveError(_("You are playing Black"));\r
5009             return FALSE;\r
5010         }\r
5011         break;\r
5012 \r
5013       case MachinePlaysBlack:\r
5014       case IcsPlayingWhite:\r
5015         if (appData.zippyPlay) return FALSE;\r
5016         if (!white_piece) {\r
5017             DisplayMoveError(_("You are playing White"));\r
5018             return FALSE;\r
5019         }\r
5020         break;\r
5021 \r
5022       case EditGame:\r
5023         if (!white_piece && WhiteOnMove(currentMove)) {\r
5024             DisplayMoveError(_("It is White's turn"));\r
5025             return FALSE;\r
5026         }           \r
5027         if (white_piece && !WhiteOnMove(currentMove)) {\r
5028             DisplayMoveError(_("It is Black's turn"));\r
5029             return FALSE;\r
5030         }           \r
5031         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {\r
5032             /* Editing correspondence game history */\r
5033             /* Could disallow this or prompt for confirmation */\r
5034             cmailOldMove = -1;\r
5035         }\r
5036         if (currentMove < forwardMostMove) {\r
5037             /* Discarding moves */\r
5038             /* Could prompt for confirmation here,\r
5039                but I don't think that's such a good idea */\r
5040             forwardMostMove = currentMove;\r
5041         }\r
5042         break;\r
5043 \r
5044       case BeginningOfGame:\r
5045         if (appData.icsActive) return FALSE;\r
5046         if (!appData.noChessProgram) {\r
5047             if (!white_piece) {\r
5048                 DisplayMoveError(_("You are playing White"));\r
5049                 return FALSE;\r
5050             }\r
5051         }\r
5052         break;\r
5053         \r
5054       case Training:\r
5055         if (!white_piece && WhiteOnMove(currentMove)) {\r
5056             DisplayMoveError(_("It is White's turn"));\r
5057             return FALSE;\r
5058         }           \r
5059         if (white_piece && !WhiteOnMove(currentMove)) {\r
5060             DisplayMoveError(_("It is Black's turn"));\r
5061             return FALSE;\r
5062         }           \r
5063         break;\r
5064 \r
5065       default:\r
5066       case IcsExamining:\r
5067         break;\r
5068     }\r
5069     if (currentMove != forwardMostMove && gameMode != AnalyzeMode\r
5070         && gameMode != AnalyzeFile && gameMode != Training) {\r
5071         DisplayMoveError(_("Displayed position is not current"));\r
5072         return FALSE;\r
5073     }\r
5074     return TRUE;\r
5075 }\r
5076 \r
5077 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;\r
5078 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;\r
5079 int lastLoadGameUseList = FALSE;\r
5080 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];\r
5081 ChessMove lastLoadGameStart = (ChessMove) 0;\r
5082 \r
5083 \r
5084 ChessMove\r
5085 UserMoveTest(fromX, fromY, toX, toY, promoChar)\r
5086      int fromX, fromY, toX, toY;\r
5087      int promoChar;\r
5088 {\r
5089     ChessMove moveType;\r
5090     ChessSquare pdown, pup;\r
5091 \r
5092     if (fromX < 0 || fromY < 0) return ImpossibleMove;\r
5093     if ((fromX == toX) && (fromY == toY)) {\r
5094         return ImpossibleMove;\r
5095     }\r
5096 \r
5097     /* [HGM] suppress all moves into holdings area and guard band */\r
5098     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )\r
5099             return ImpossibleMove;\r
5100 \r
5101     /* [HGM] <sameColor> moved to here from winboard.c */\r
5102     /* note: this code seems to exist for filtering out some obviously illegal premoves */\r
5103     pdown = boards[currentMove][fromY][fromX];\r
5104     pup = boards[currentMove][toY][toX];\r
5105     if (    gameMode != EditPosition &&\r
5106             (WhitePawn <= pdown && pdown < BlackPawn &&\r
5107              WhitePawn <= pup && pup < BlackPawn  ||\r
5108              BlackPawn <= pdown && pdown < EmptySquare &&\r
5109              BlackPawn <= pup && pup < EmptySquare \r
5110             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&\r
5111                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||\r
5112                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) \r
5113         )           )\r
5114          return ImpossibleMove;\r
5115 \r
5116     /* Check if the user is playing in turn.  This is complicated because we\r
5117        let the user "pick up" a piece before it is his turn.  So the piece he\r
5118        tried to pick up may have been captured by the time he puts it down!\r
5119        Therefore we use the color the user is supposed to be playing in this\r
5120        test, not the color of the piece that is currently on the starting\r
5121        square---except in EditGame mode, where the user is playing both\r
5122        sides; fortunately there the capture race can't happen.  (It can\r
5123        now happen in IcsExamining mode, but that's just too bad.  The user\r
5124        will get a somewhat confusing message in that case.)\r
5125        */\r
5126 \r
5127     switch (gameMode) {\r
5128       case PlayFromGameFile:\r
5129       case AnalyzeFile:\r
5130       case TwoMachinesPlay:\r
5131       case EndOfGame:\r
5132       case IcsObserving:\r
5133       case IcsIdle:\r
5134         /* We switched into a game mode where moves are not accepted,\r
5135            perhaps while the mouse button was down. */\r
5136         return ImpossibleMove;\r
5137 \r
5138       case MachinePlaysWhite:\r
5139         /* User is moving for Black */\r
5140         if (WhiteOnMove(currentMove)) {\r
5141             DisplayMoveError(_("It is White's turn"));\r
5142             return ImpossibleMove;\r
5143         }\r
5144         break;\r
5145 \r
5146       case MachinePlaysBlack:\r
5147         /* User is moving for White */\r
5148         if (!WhiteOnMove(currentMove)) {\r
5149             DisplayMoveError(_("It is Black's turn"));\r
5150             return ImpossibleMove;\r
5151         }\r
5152         break;\r
5153 \r
5154       case EditGame:\r
5155       case IcsExamining:\r
5156       case BeginningOfGame:\r
5157       case AnalyzeMode:\r
5158       case Training:\r
5159         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&\r
5160             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {\r
5161             /* User is moving for Black */\r
5162             if (WhiteOnMove(currentMove)) {\r
5163                 DisplayMoveError(_("It is White's turn"));\r
5164                 return ImpossibleMove;\r
5165             }\r
5166         } else {\r
5167             /* User is moving for White */\r
5168             if (!WhiteOnMove(currentMove)) {\r
5169                 DisplayMoveError(_("It is Black's turn"));\r
5170                 return ImpossibleMove;\r
5171             }\r
5172         }\r
5173         break;\r
5174 \r
5175       case IcsPlayingBlack:\r
5176         /* User is moving for Black */\r
5177         if (WhiteOnMove(currentMove)) {\r
5178             if (!appData.premove) {\r
5179                 DisplayMoveError(_("It is White's turn"));\r
5180             } else if (toX >= 0 && toY >= 0) {\r
5181                 premoveToX = toX;\r
5182                 premoveToY = toY;\r
5183                 premoveFromX = fromX;\r
5184                 premoveFromY = fromY;\r
5185                 premovePromoChar = promoChar;\r
5186                 gotPremove = 1;\r
5187                 if (appData.debugMode) \r
5188                     fprintf(debugFP, "Got premove: fromX %d,"\r
5189                             "fromY %d, toX %d, toY %d\n",\r
5190                             fromX, fromY, toX, toY);\r
5191             }\r
5192             return ImpossibleMove;\r
5193         }\r
5194         break;\r
5195 \r
5196       case IcsPlayingWhite:\r
5197         /* User is moving for White */\r
5198         if (!WhiteOnMove(currentMove)) {\r
5199             if (!appData.premove) {\r
5200                 DisplayMoveError(_("It is Black's turn"));\r
5201             } else if (toX >= 0 && toY >= 0) {\r
5202                 premoveToX = toX;\r
5203                 premoveToY = toY;\r
5204                 premoveFromX = fromX;\r
5205                 premoveFromY = fromY;\r
5206                 premovePromoChar = promoChar;\r
5207                 gotPremove = 1;\r
5208                 if (appData.debugMode) \r
5209                     fprintf(debugFP, "Got premove: fromX %d,"\r
5210                             "fromY %d, toX %d, toY %d\n",\r
5211                             fromX, fromY, toX, toY);\r
5212             }\r
5213             return ImpossibleMove;\r
5214         }\r
5215         break;\r
5216 \r
5217       default:\r
5218         break;\r
5219 \r
5220       case EditPosition:\r
5221         /* EditPosition, empty square, or different color piece;\r
5222            click-click move is possible */\r
5223         if (toX == -2 || toY == -2) {\r
5224             boards[0][fromY][fromX] = EmptySquare;\r
5225             return AmbiguousMove;\r
5226         } else if (toX >= 0 && toY >= 0) {\r
5227             boards[0][toY][toX] = boards[0][fromY][fromX];\r
5228             boards[0][fromY][fromX] = EmptySquare;\r
5229             return AmbiguousMove;\r
5230         }\r
5231         return ImpossibleMove;\r
5232     }\r
5233 \r
5234     /* [HGM] If move started in holdings, it means a drop */\r
5235     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { \r
5236          if( pup != EmptySquare ) return ImpossibleMove;\r
5237          if(appData.testLegality) {\r
5238              /* it would be more logical if LegalityTest() also figured out\r
5239               * which drops are legal. For now we forbid pawns on back rank.\r
5240               * Shogi is on its own here...\r
5241               */\r
5242              if( (pdown == WhitePawn || pdown == BlackPawn) &&\r
5243                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )\r
5244                  return(ImpossibleMove); /* no pawn drops on 1st/8th */\r
5245          }\r
5246          return WhiteDrop; /* Not needed to specify white or black yet */\r
5247     }\r
5248 \r
5249     userOfferedDraw = FALSE;\r
5250         \r
5251     /* [HGM] always test for legality, to get promotion info */\r
5252     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),\r
5253                           epStatus[currentMove], castlingRights[currentMove],\r
5254                                          fromY, fromX, toY, toX, promoChar);\r
5255 \r
5256     /* [HGM] but possibly ignore an IllegalMove result */\r
5257     if (appData.testLegality) {\r
5258         if (moveType == IllegalMove || moveType == ImpossibleMove) {\r
5259             DisplayMoveError(_("Illegal move"));\r
5260             return ImpossibleMove;\r
5261         }\r
5262     }\r
5263 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);\r
5264     return moveType;\r
5265     /* [HGM] <popupFix> in stead of calling FinishMove directly, this\r
5266        function is made into one that returns an OK move type if FinishMove\r
5267        should be called. This to give the calling driver routine the\r
5268        opportunity to finish the userMove input with a promotion popup,\r
5269        without bothering the user with this for invalid or illegal moves */\r
5270 \r
5271 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */\r
5272 }\r
5273 \r
5274 /* Common tail of UserMoveEvent and DropMenuEvent */\r
5275 int\r
5276 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)\r
5277      ChessMove moveType;\r
5278      int fromX, fromY, toX, toY;\r
5279      /*char*/int promoChar;\r
5280 {\r
5281     char *bookHit = 0;\r
5282 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);\r
5283     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { \r
5284         // [HGM] superchess: suppress promotions to non-available piece\r
5285         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));\r
5286         if(WhiteOnMove(currentMove)) {\r
5287             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;\r
5288         } else {\r
5289             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;\r
5290         }\r
5291     }\r
5292 \r
5293     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion\r
5294        move type in caller when we know the move is a legal promotion */\r
5295     if(moveType == NormalMove && promoChar)\r
5296         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);\r
5297 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);\r
5298     /* [HGM] convert drag-and-drop piece drops to standard form */\r
5299     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {\r
5300          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
5301          fromX = boards[currentMove][fromY][fromX];\r
5302          fromY = DROP_RANK;\r
5303     }\r
5304 \r
5305     /* [HGM] <popupFix> The following if has been moved here from\r
5306        UserMoveEvent(). Because it seemed to belon here (why not allow\r
5307        piece drops in training games?), and because it can only be\r
5308        performed after it is known to what we promote. */\r
5309     if (gameMode == Training) {\r
5310       /* compare the move played on the board to the next move in the\r
5311        * game. If they match, display the move and the opponent's response. \r
5312        * If they don't match, display an error message.\r
5313        */\r
5314       int saveAnimate;\r
5315       Board testBoard;\r
5316       CopyBoard(testBoard, boards[currentMove]);\r
5317       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);\r
5318 \r
5319       if (CompareBoards(testBoard, boards[currentMove+1])) {\r
5320         ForwardInner(currentMove+1);\r
5321 \r
5322         /* Autoplay the opponent's response.\r
5323          * if appData.animate was TRUE when Training mode was entered,\r
5324          * the response will be animated.\r
5325          */\r
5326         saveAnimate = appData.animate;\r
5327         appData.animate = animateTraining;\r
5328         ForwardInner(currentMove+1);\r
5329         appData.animate = saveAnimate;\r
5330 \r
5331         /* check for the end of the game */\r
5332         if (currentMove >= forwardMostMove) {\r
5333           gameMode = PlayFromGameFile;\r
5334           ModeHighlight();\r
5335           SetTrainingModeOff();\r
5336           DisplayInformation(_("End of game"));\r
5337         }\r
5338       } else {\r
5339         DisplayError(_("Incorrect move"), 0);\r
5340       }\r
5341       return 1;\r
5342     }\r
5343 \r
5344   /* Ok, now we know that the move is good, so we can kill\r
5345      the previous line in Analysis Mode */\r
5346   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {\r
5347     forwardMostMove = currentMove;\r
5348   }\r
5349 \r
5350   /* If we need the chess program but it's dead, restart it */\r
5351   ResurrectChessProgram();\r
5352 \r
5353   /* A user move restarts a paused game*/\r
5354   if (pausing)\r
5355     PauseEvent();\r
5356 \r
5357   thinkOutput[0] = NULLCHAR;\r
5358 \r
5359   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/\r
5360 \r
5361     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) \r
5362                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { \r
5363         // [HGM] superchess: take promotion piece out of holdings\r
5364         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));\r
5365         if(WhiteOnMove(forwardMostMove-1)) {\r
5366             if(!--boards[forwardMostMove][k][BOARD_WIDTH-2])\r
5367                 boards[forwardMostMove][k][BOARD_WIDTH-1] = EmptySquare;\r
5368         } else {\r
5369             if(!--boards[forwardMostMove][BOARD_HEIGHT-1-k][1])\r
5370                 boards[forwardMostMove][BOARD_HEIGHT-1-k][0] = EmptySquare;\r
5371         }\r
5372     }\r
5373 \r
5374   if (gameMode == BeginningOfGame) {\r
5375     if (appData.noChessProgram) {\r
5376       gameMode = EditGame;\r
5377       SetGameInfo();\r
5378     } else {\r
5379       char buf[MSG_SIZ];\r
5380       gameMode = MachinePlaysBlack;\r
5381       StartClocks();\r
5382       SetGameInfo();\r
5383       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
5384       DisplayTitle(buf);\r
5385       if (first.sendName) {\r
5386         sprintf(buf, "name %s\n", gameInfo.white);\r
5387         SendToProgram(buf, &first);\r
5388       }\r
5389       StartClocks();\r
5390     }\r
5391     ModeHighlight();\r
5392   }\r
5393 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);\r
5394   /* Relay move to ICS or chess engine */\r
5395   if (appData.icsActive) {\r
5396     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
5397         gameMode == IcsExamining) {\r
5398       SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
5399       ics_user_moved = 1;\r
5400     }\r
5401   } else {\r
5402     if (first.sendTime && (gameMode == BeginningOfGame ||\r
5403                            gameMode == MachinePlaysWhite ||\r
5404                            gameMode == MachinePlaysBlack)) {\r
5405       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);\r
5406     }\r
5407     if (gameMode != EditGame && gameMode != PlayFromGameFile) {\r
5408          // [HGM] book: if program might be playing, let it use book\r
5409         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);\r
5410         first.maybeThinking = TRUE;\r
5411     } else SendMoveToProgram(forwardMostMove-1, &first);\r
5412     if (currentMove == cmailOldMove + 1) {\r
5413       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
5414     }\r
5415   }\r
5416 \r
5417   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5418 \r
5419   switch (gameMode) {\r
5420   case EditGame:\r
5421     switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
5422                      EP_UNKNOWN, castlingRights[currentMove]) ) {\r
5423     case MT_NONE:\r
5424     case MT_CHECK:\r
5425       break;\r
5426     case MT_CHECKMATE:\r
5427       if (WhiteOnMove(currentMove)) {\r
5428         GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
5429       } else {\r
5430         GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
5431       }\r
5432       break;\r
5433     case MT_STALEMATE:\r
5434       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
5435       break;\r
5436     }\r
5437     break;\r
5438     \r
5439   case MachinePlaysBlack:\r
5440   case MachinePlaysWhite:\r
5441     /* disable certain menu options while machine is thinking */\r
5442     SetMachineThinkingEnables();\r
5443     break;\r
5444 \r
5445   default:\r
5446     break;\r
5447   }\r
5448 \r
5449   if(bookHit) { // [HGM] book: simulate book reply\r
5450         static char bookMove[MSG_SIZ]; // a bit generous?\r
5451 \r
5452         programStats.nodes = programStats.depth = programStats.time = \r
5453         programStats.score = programStats.got_only_move = 0;\r
5454         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
5455 \r
5456         strcpy(bookMove, "move ");\r
5457         strcat(bookMove, bookHit);\r
5458         HandleMachineMove(bookMove, &first);\r
5459   }\r
5460   return 1;\r
5461 }\r
5462 \r
5463 void\r
5464 UserMoveEvent(fromX, fromY, toX, toY, promoChar)\r
5465      int fromX, fromY, toX, toY;\r
5466      int promoChar;\r
5467 {\r
5468     /* [HGM] This routine was added to allow calling of its two logical\r
5469        parts from other modules in the old way. Before, UserMoveEvent()\r
5470        automatically called FinishMove() if the move was OK, and returned\r
5471        otherwise. I separated the two, in order to make it possible to\r
5472        slip a promotion popup in between. But that it always needs two\r
5473        calls, to the first part, (now called UserMoveTest() ), and to\r
5474        FinishMove if the first part succeeded. Calls that do not need\r
5475        to do anything in between, can call this routine the old way. \r
5476     */\r
5477     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);\r
5478 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);\r
5479     if(moveType != ImpossibleMove)\r
5480         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);\r
5481 }\r
5482 \r
5483 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )\r
5484 {\r
5485 //    char * hint = lastHint;\r
5486     FrontEndProgramStats stats;\r
5487 \r
5488     stats.which = cps == &first ? 0 : 1;\r
5489     stats.depth = cpstats->depth;\r
5490     stats.nodes = cpstats->nodes;\r
5491     stats.score = cpstats->score;\r
5492     stats.time = cpstats->time;\r
5493     stats.pv = cpstats->movelist;\r
5494     stats.hint = lastHint;\r
5495     stats.an_move_index = 0;\r
5496     stats.an_move_count = 0;\r
5497 \r
5498     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {\r
5499         stats.hint = cpstats->move_name;\r
5500         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;\r
5501         stats.an_move_count = cpstats->nr_moves;\r
5502     }\r
5503 \r
5504     SetProgramStats( &stats );\r
5505 }\r
5506 \r
5507 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)\r
5508 {   // [HGM] book: this routine intercepts moves to simulate book replies\r
5509     char *bookHit = NULL;\r
5510 \r
5511     //first determine if the incoming move brings opponent into his book\r
5512     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))\r
5513         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move\r
5514     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");\r
5515     if(bookHit != NULL && !cps->bookSuspend) {\r
5516         // make sure opponent is not going to reply after receiving move to book position\r
5517         SendToProgram("force\n", cps);\r
5518         cps->bookSuspend = TRUE; // flag indicating it has to be restarted\r
5519     }\r
5520     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move\r
5521     // now arrange restart after book miss\r
5522     if(bookHit) {\r
5523         // after a book hit we never send 'go', and the code after the call to this routine\r
5524         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').\r
5525         char buf[MSG_SIZ];\r
5526         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(\r
5527         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it\r
5528         SendToProgram(buf, cps);\r
5529         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'\r
5530     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine\r
5531         SendToProgram("go\n", cps);\r
5532         cps->bookSuspend = FALSE; // after a 'go' we are never suspended\r
5533     } else { // 'go' might be sent based on 'firstMove' after this routine returns\r
5534         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return\r
5535             SendToProgram("go\n", cps); \r
5536         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss\r
5537     }\r
5538     return bookHit; // notify caller of hit, so it can take action to send move to opponent\r
5539 }\r
5540 \r
5541 char *savedMessage;\r
5542 ChessProgramState *savedState;\r
5543 void DeferredBookMove(void)\r
5544 {\r
5545         if(savedState->lastPing != savedState->lastPong)\r
5546                     ScheduleDelayedEvent(DeferredBookMove, 10);\r
5547         else\r
5548         HandleMachineMove(savedMessage, savedState);\r
5549 }\r
5550 \r
5551 void\r
5552 HandleMachineMove(message, cps)\r
5553      char *message;\r
5554      ChessProgramState *cps;\r
5555 {\r
5556     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];\r
5557     char realname[MSG_SIZ];\r
5558     int fromX, fromY, toX, toY;\r
5559     ChessMove moveType;\r
5560     char promoChar;\r
5561     char *p;\r
5562     int machineWhite;\r
5563     char *bookHit;\r
5564 \r
5565 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit\r
5566     /*\r
5567      * Kludge to ignore BEL characters\r
5568      */\r
5569     while (*message == '\007') message++;\r
5570 \r
5571     /*\r
5572      * [HGM] engine debug message: ignore lines starting with '#' character\r
5573      */\r
5574     if(cps->debug && *message == '#') return;\r
5575 \r
5576     /*\r
5577      * Look for book output\r
5578      */\r
5579     if (cps == &first && bookRequested) {\r
5580         if (message[0] == '\t' || message[0] == ' ') {\r
5581             /* Part of the book output is here; append it */\r
5582             strcat(bookOutput, message);\r
5583             strcat(bookOutput, "  \n");\r
5584             return;\r
5585         } else if (bookOutput[0] != NULLCHAR) {\r
5586             /* All of book output has arrived; display it */\r
5587             char *p = bookOutput;\r
5588             while (*p != NULLCHAR) {\r
5589                 if (*p == '\t') *p = ' ';\r
5590                 p++;\r
5591             }\r
5592             DisplayInformation(bookOutput);\r
5593             bookRequested = FALSE;\r
5594             /* Fall through to parse the current output */\r
5595         }\r
5596     }\r
5597 \r
5598     /*\r
5599      * Look for machine move.\r
5600      */\r
5601     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||\r
5602         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) \r
5603     {\r
5604         /* This method is only useful on engines that support ping */\r
5605         if (cps->lastPing != cps->lastPong) {\r
5606           if (gameMode == BeginningOfGame) {\r
5607             /* Extra move from before last new; ignore */\r
5608             if (appData.debugMode) {\r
5609                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
5610             }\r
5611           } else {\r
5612             if (appData.debugMode) {\r
5613                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
5614                         cps->which, gameMode);\r
5615             }\r
5616 \r
5617             SendToProgram("undo\n", cps);\r
5618           }\r
5619           return;\r
5620         }\r
5621 \r
5622         switch (gameMode) {\r
5623           case BeginningOfGame:\r
5624             /* Extra move from before last reset; ignore */\r
5625             if (appData.debugMode) {\r
5626                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
5627             }\r
5628             return;\r
5629 \r
5630           case EndOfGame:\r
5631           case IcsIdle:\r
5632           default:\r
5633             /* Extra move after we tried to stop.  The mode test is\r
5634                not a reliable way of detecting this problem, but it's\r
5635                the best we can do on engines that don't support ping.\r
5636             */\r
5637             if (appData.debugMode) {\r
5638                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
5639                         cps->which, gameMode);\r
5640             }\r
5641             SendToProgram("undo\n", cps);\r
5642             return;\r
5643 \r
5644           case MachinePlaysWhite:\r
5645           case IcsPlayingWhite:\r
5646             machineWhite = TRUE;\r
5647             break;\r
5648 \r
5649           case MachinePlaysBlack:\r
5650           case IcsPlayingBlack:\r
5651             machineWhite = FALSE;\r
5652             break;\r
5653 \r
5654           case TwoMachinesPlay:\r
5655             machineWhite = (cps->twoMachinesColor[0] == 'w');\r
5656             break;\r
5657         }\r
5658         if (WhiteOnMove(forwardMostMove) != machineWhite) {\r
5659             if (appData.debugMode) {\r
5660                 fprintf(debugFP,\r
5661                         "Ignoring move out of turn by %s, gameMode %d"\r
5662                         ", forwardMost %d\n",\r
5663                         cps->which, gameMode, forwardMostMove);\r
5664             }\r
5665             return;\r
5666         }\r
5667 \r
5668     if (appData.debugMode) { int f = forwardMostMove;\r
5669         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,\r
5670                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
5671     }\r
5672         if(cps->alphaRank) AlphaRank(machineMove, 4);\r
5673         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,\r
5674                               &fromX, &fromY, &toX, &toY, &promoChar)) {\r
5675             /* Machine move could not be parsed; ignore it. */\r
5676             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),\r
5677                     machineMove, cps->which);\r
5678             DisplayError(buf1, 0);\r
5679             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",\r
5680                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);\r
5681             if (gameMode == TwoMachinesPlay) {\r
5682               GameEnds(machineWhite ? BlackWins : WhiteWins,\r
5683                        buf1, GE_XBOARD);\r
5684             }\r
5685             return;\r
5686         }\r
5687 \r
5688         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */\r
5689         /* So we have to redo legality test with true e.p. status here,  */\r
5690         /* to make sure an illegal e.p. capture does not slip through,   */\r
5691         /* to cause a forfeit on a justified illegal-move complaint      */\r
5692         /* of the opponent.                                              */\r
5693         if( gameMode==TwoMachinesPlay && appData.testLegality\r
5694             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */\r
5695                                                               ) {\r
5696            ChessMove moveType;\r
5697            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
5698                         epStatus[forwardMostMove], castlingRights[forwardMostMove],\r
5699                              fromY, fromX, toY, toX, promoChar);\r
5700             if (appData.debugMode) {\r
5701                 int i;\r
5702                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",\r
5703                     castlingRights[forwardMostMove][i], castlingRank[i]);\r
5704                 fprintf(debugFP, "castling rights\n");\r
5705             }\r
5706             if(moveType == IllegalMove) {\r
5707                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",\r
5708                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);\r
5709                 GameEnds(machineWhite ? BlackWins : WhiteWins,\r
5710                            buf1, GE_XBOARD);\r
5711            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)\r
5712            /* [HGM] Kludge to handle engines that send FRC-style castling\r
5713               when they shouldn't (like TSCP-Gothic) */\r
5714            switch(moveType) {\r
5715              case WhiteASideCastleFR:\r
5716              case BlackASideCastleFR:\r
5717                toX+=2;\r
5718                currentMoveString[2]++;\r
5719                break;\r
5720              case WhiteHSideCastleFR:\r
5721              case BlackHSideCastleFR:\r
5722                toX--;\r
5723                currentMoveString[2]--;\r
5724                break;\r
5725              default: ; // nothing to do, but suppresses warning of pedantic compilers\r
5726            }\r
5727         }\r
5728         hintRequested = FALSE;\r
5729         lastHint[0] = NULLCHAR;\r
5730         bookRequested = FALSE;\r
5731         /* Program may be pondering now */\r
5732         cps->maybeThinking = TRUE;\r
5733         if (cps->sendTime == 2) cps->sendTime = 1;\r
5734         if (cps->offeredDraw) cps->offeredDraw--;\r
5735 \r
5736 #if ZIPPY\r
5737         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&\r
5738             first.initDone) {\r
5739           SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
5740           ics_user_moved = 1;\r
5741           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */\r
5742                 char buf[3*MSG_SIZ];\r
5743 \r
5744                 sprintf(buf, "kibitz %d/%+.2f (%.2f sec, %.0f nodes, %1.0f knps) PV = %s\n",\r
5745                         programStats.depth,\r
5746                         programStats.score / 100.,\r
5747                         programStats.time / 100.,\r
5748                         u64ToDouble(programStats.nodes),\r
5749                         u64ToDouble(programStats.nodes) / (10*abs(programStats.time) + 1.),\r
5750                         programStats.movelist);\r
5751                 SendToICS(buf);\r
5752           }\r
5753         }\r
5754 #endif\r
5755         /* currentMoveString is set as a side-effect of ParseOneMove */\r
5756         strcpy(machineMove, currentMoveString);\r
5757         strcat(machineMove, "\n");\r
5758         strcpy(moveList[forwardMostMove], machineMove);\r
5759 \r
5760         /* [AS] Save move info and clear stats for next move */\r
5761         pvInfoList[ forwardMostMove ].score = programStats.score;\r
5762         pvInfoList[ forwardMostMove ].depth = programStats.depth;\r
5763         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats\r
5764         ClearProgramStats();\r
5765         thinkOutput[0] = NULLCHAR;\r
5766         hiddenThinkOutputState = 0;\r
5767 \r
5768         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/\r
5769 \r
5770         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */\r
5771         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {\r
5772             int count = 0;\r
5773 \r
5774             while( count < adjudicateLossPlies ) {\r
5775                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;\r
5776 \r
5777                 if( count & 1 ) {\r
5778                     score = -score; /* Flip score for winning side */\r
5779                 }\r
5780 \r
5781                 if( score > adjudicateLossThreshold ) {\r
5782                     break;\r
5783                 }\r
5784 \r
5785                 count++;\r
5786             }\r
5787 \r
5788             if( count >= adjudicateLossPlies ) {\r
5789                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5790 \r
5791                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5792                     "Xboard adjudication", \r
5793                     GE_XBOARD );\r
5794 \r
5795                 return;\r
5796             }\r
5797         }\r
5798 \r
5799         if( gameMode == TwoMachinesPlay ) {\r
5800           // [HGM] some adjudications useful with buggy engines\r
5801             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;\r
5802           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {\r
5803 \r
5804             if(appData.testLegality)\r
5805             // don't wait for engine to announce game end if we can judge ourselves\r
5806             switch (MateTest(boards[forwardMostMove],\r
5807                                  PosFlags(forwardMostMove), epFile,\r
5808                                        castlingRights[forwardMostMove]) ) {\r
5809               case MT_NONE:\r
5810               case MT_CHECK:\r
5811               default:\r
5812                 break;\r
5813               case MT_STALEMATE:\r
5814                 epStatus[forwardMostMove] = EP_STALEMATE;\r
5815                 if(appData.checkMates) {\r
5816                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5817                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5818                     GameEnds( GameIsDrawn, "Xboard adjudication: Stalemate",\r
5819                         GE_XBOARD );\r
5820                 }\r
5821                 break;\r
5822               case MT_CHECKMATE:\r
5823                 epStatus[forwardMostMove] = EP_CHECKMATE;\r
5824                 if(appData.checkMates) {\r
5825                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5826                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5827                     GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, \r
5828                     "Xboard adjudication: Checkmate", \r
5829                     GE_XBOARD );\r
5830                 }\r
5831                 break;\r
5832             }\r
5833 \r
5834             if( appData.testLegality )\r
5835             {   /* [HGM] Some more adjudications for obstinate engines */\r
5836                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,\r
5837                     NrWQ=0, NrBQ=0, NrW=0, bishopsColor = 0,\r
5838                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;\r
5839                 static int moveCount = 6;\r
5840 \r
5841                 /* First absolutely insufficient mating material. Count what is on board. */\r
5842                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
5843                 {   ChessSquare p = boards[forwardMostMove][i][j];\r
5844                     int m=i;\r
5845 \r
5846                     switch((int) p)\r
5847                     {   /* count B,N,R and other of each side */\r
5848                         case WhiteKnight:\r
5849                              NrWN++; break;\r
5850                         case WhiteBishop:\r
5851                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj\r
5852                              bishopsColor |= 1 << ((i^j)&1);\r
5853                              NrWB++; break;\r
5854                         case BlackKnight:\r
5855                              NrBN++; break;\r
5856                         case BlackBishop:\r
5857                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj\r
5858                              bishopsColor |= 1 << ((i^j)&1);\r
5859                              NrBB++; break;\r
5860                         case WhiteRook:\r
5861                              NrWR++; break;\r
5862                         case BlackRook:\r
5863                              NrBR++; break;\r
5864                         case WhiteQueen:\r
5865                              NrWQ++; break;\r
5866                         case BlackQueen:\r
5867                              NrBQ++; break;\r
5868                         case EmptySquare: \r
5869                              break;\r
5870                         case BlackPawn:\r
5871                              m = 7-i;\r
5872                         case WhitePawn:\r
5873                              PawnAdvance += m; NrPawns++;\r
5874                     }\r
5875                     NrPieces += (p != EmptySquare);\r
5876                     NrW += ((int)p < (int)BlackPawn);\r
5877                     if(gameInfo.variant == VariantXiangqi && \r
5878                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {\r
5879                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces\r
5880                         NrW -= ((int)p < (int)BlackPawn);\r
5881                     }\r
5882                 }\r
5883 \r
5884                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && \r
5885                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible\r
5886                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||\r
5887                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color\r
5888                 {    /* KBK, KNK, KK of KBKB with like Bishops */\r
5889 \r
5890                      /* always flag draws, for judging claims */\r
5891                      epStatus[forwardMostMove] = EP_INSUF_DRAW;\r
5892 \r
5893                      if(appData.materialDraws) {\r
5894                          /* but only adjudicate them if adjudication enabled */\r
5895                          SendToProgram("force\n", cps->other); // suppress reply\r
5896                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */\r
5897                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5898                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );\r
5899                          return;\r
5900                      }\r
5901                 }\r
5902 \r
5903                 /* Shatranj baring rule */\r
5904                 if( gameInfo.variant == VariantShatranj && (NrW == 1 || NrPieces - NrW == 1) )\r
5905                 {    /* bare King */\r
5906 \r
5907                      if(--bare < 0 && appData.checkMates) {\r
5908                          /* but only adjudicate them if adjudication enabled */\r
5909                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5910                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5911                          GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, \r
5912                                                         "Xboard adjudication: Bare king", GE_XBOARD );\r
5913                          return;\r
5914                      }\r
5915                 } else bare = 1;\r
5916 \r
5917                 /* Then some trivial draws (only adjudicate, cannot be claimed) */\r
5918                 if(NrPieces == 4 && \r
5919                    (   NrWR == 1 && NrBR == 1 /* KRKR */\r
5920                    || NrWQ==1 && NrBQ==1     /* KQKQ */\r
5921                    || NrWN==2 || NrBN==2     /* KNNK */\r
5922                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */\r
5923                   ) ) {\r
5924                      if(--moveCount < 0 && appData.trivialDraws)\r
5925                      {    /* if the first 3 moves do not show a tactical win, declare draw */\r
5926                           SendToProgram("force\n", cps->other); // suppress reply\r
5927                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5928                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5929                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );\r
5930                           return;\r
5931                      }\r
5932                 } else moveCount = 6;\r
5933             }\r
5934           }\r
5935 #if 1\r
5936     if (appData.debugMode) { int i;\r
5937       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",\r
5938               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],\r
5939               appData.drawRepeats);\r
5940       for( i=forwardMostMove; i>=backwardMostMove; i-- )\r
5941            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);\r
5942 \r
5943     }\r
5944 #endif\r
5945                 /* Check for rep-draws */\r
5946                 count = 0;\r
5947                 for(k = forwardMostMove-2;\r
5948                     k>=backwardMostMove && k>=forwardMostMove-100 &&\r
5949                         epStatus[k] < EP_UNKNOWN &&\r
5950                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;\r
5951                     k-=2)\r
5952                 {   int rights=0;\r
5953 #if 0\r
5954     if (appData.debugMode) {\r
5955       fprintf(debugFP, " loop\n");\r
5956     }\r
5957 #endif\r
5958                     if(CompareBoards(boards[k], boards[forwardMostMove])) {\r
5959 #if 0\r
5960     if (appData.debugMode) {\r
5961       fprintf(debugFP, "match\n");\r
5962     }\r
5963 #endif\r
5964                         /* compare castling rights */\r
5965                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&\r
5966                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )\r
5967                                 rights++; /* King lost rights, while rook still had them */\r
5968                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */\r
5969                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||\r
5970                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )\r
5971                                    rights++; /* but at least one rook lost them */\r
5972                         }\r
5973                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&\r
5974                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )\r
5975                                 rights++; \r
5976                         if( castlingRights[forwardMostMove][5] >= 0 ) {\r
5977                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||\r
5978                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )\r
5979                                    rights++;\r
5980                         }\r
5981 #if 0\r
5982     if (appData.debugMode) {\r
5983       for(i=0; i<nrCastlingRights; i++)\r
5984       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);\r
5985     }\r
5986 \r
5987     if (appData.debugMode) {\r
5988       fprintf(debugFP, " %d %d\n", rights, k);\r
5989     }\r
5990 #endif\r
5991                         if( rights == 0 && ++count > appData.drawRepeats-2\r
5992                             && appData.drawRepeats > 1) {\r
5993                              /* adjudicate after user-specified nr of repeats */\r
5994                              SendToProgram("force\n", cps->other); // suppress reply\r
5995                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5996                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5997                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { \r
5998                                 // [HGM] xiangqi: check for forbidden perpetuals\r
5999                                 int m, ourPerpetual = 1, hisPerpetual = 1;\r
6000                                 for(m=forwardMostMove; m>k; m-=2) {\r
6001                                     if(MateTest(boards[m], PosFlags(m), \r
6002                                                         EP_NONE, castlingRights[m]) != MT_CHECK)\r
6003                                         ourPerpetual = 0; // the current mover did not always check\r
6004                                     if(MateTest(boards[m-1], PosFlags(m-1), \r
6005                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)\r
6006                                         hisPerpetual = 0; // the opponent did not always check\r
6007                                 }\r
6008                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",\r
6009                                                                         ourPerpetual, hisPerpetual);\r
6010                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit\r
6011                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
6012                                            "Xboard adjudication: perpetual checking", GE_XBOARD );\r
6013                                     return;\r
6014                                 }\r
6015                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet\r
6016                                     break; // (or we would have caught him before). Abort repetition-checking loop.\r
6017                                 // Now check for perpetual chases\r
6018                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase\r
6019                                     hisPerpetual = PerpetualChase(k, forwardMostMove);\r
6020                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);\r
6021                                     if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit\r
6022                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
6023                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );\r
6024                                         return;\r
6025                                     }\r
6026                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet\r
6027                                         break; // Abort repetition-checking loop.\r
6028                                 }\r
6029                                 // if neither of us is checking or chasing all the time, or both are, it is draw\r
6030                              }\r
6031                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );\r
6032                              return;\r
6033                         }\r
6034                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */\r
6035                              epStatus[forwardMostMove] = EP_REP_DRAW;\r
6036                     }\r
6037                 }\r
6038 \r
6039                 /* Now we test for 50-move draws. Determine ply count */\r
6040                 count = forwardMostMove;\r
6041                 /* look for last irreversble move */\r
6042                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )\r
6043                     count--;\r
6044                 /* if we hit starting position, add initial plies */\r
6045                 if( count == backwardMostMove )\r
6046                     count -= initialRulePlies;\r
6047                 count = forwardMostMove - count; \r
6048                 if( count >= 100)\r
6049                          epStatus[forwardMostMove] = EP_RULE_DRAW;\r
6050                          /* this is used to judge if draw claims are legal */\r
6051                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {\r
6052                          SendToProgram("force\n", cps->other); // suppress reply\r
6053                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6054                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6055                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );\r
6056                          return;\r
6057                 }\r
6058 \r
6059                 /* if draw offer is pending, treat it as a draw claim\r
6060                  * when draw condition present, to allow engines a way to\r
6061                  * claim draws before making their move to avoid a race\r
6062                  * condition occurring after their move\r
6063                  */\r
6064                 if( cps->other->offeredDraw || cps->offeredDraw ) {\r
6065                          char *p = NULL;\r
6066                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)\r
6067                              p = "Draw claim: 50-move rule";\r
6068                          if(epStatus[forwardMostMove] == EP_REP_DRAW)\r
6069                              p = "Draw claim: 3-fold repetition";\r
6070                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)\r
6071                              p = "Draw claim: insufficient mating material";\r
6072                          if( p != NULL ) {\r
6073                              SendToProgram("force\n", cps->other); // suppress reply\r
6074                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6075                              GameEnds( GameIsDrawn, p, GE_XBOARD );\r
6076                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6077                              return;\r
6078                          }\r
6079                 }\r
6080 \r
6081 \r
6082                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {\r
6083                     SendToProgram("force\n", cps->other); // suppress reply\r
6084                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6085                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6086 \r
6087                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );\r
6088 \r
6089                     return;\r
6090                 }\r
6091         }\r
6092 \r
6093         bookHit = NULL;\r
6094         if (gameMode == TwoMachinesPlay) {\r
6095             /* [HGM] relaying draw offers moved to after reception of move */\r
6096             /* and interpreting offer as claim if it brings draw condition */\r
6097             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {\r
6098                 SendToProgram("draw\n", cps->other);\r
6099             }\r
6100             if (cps->other->sendTime) {\r
6101                 SendTimeRemaining(cps->other,\r
6102                                   cps->other->twoMachinesColor[0] == 'w');\r
6103             }\r
6104             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);\r
6105             if (firstMove && !bookHit) {\r
6106                 firstMove = FALSE;\r
6107                 if (cps->other->useColors) {\r
6108                   SendToProgram(cps->other->twoMachinesColor, cps->other);\r
6109                 }\r
6110                 SendToProgram("go\n", cps->other);\r
6111             }\r
6112             cps->other->maybeThinking = TRUE;\r
6113         }\r
6114 \r
6115         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6116         \r
6117         if (!pausing && appData.ringBellAfterMoves) {\r
6118             RingBell();\r
6119         }\r
6120 \r
6121         /* \r
6122          * Reenable menu items that were disabled while\r
6123          * machine was thinking\r
6124          */\r
6125         if (gameMode != TwoMachinesPlay)\r
6126             SetUserThinkingEnables();\r
6127 \r
6128         // [HGM] book: after book hit opponent has received move and is now in force mode\r
6129         // force the book reply into it, and then fake that it outputted this move by jumping\r
6130         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move\r
6131         if(bookHit) {\r
6132                 static char bookMove[MSG_SIZ]; // a bit generous?\r
6133 \r
6134                 strcpy(bookMove, "move ");\r
6135                 strcat(bookMove, bookHit);\r
6136                 message = bookMove;\r
6137                 cps = cps->other;\r
6138                 programStats.nodes = programStats.depth = programStats.time = \r
6139                 programStats.score = programStats.got_only_move = 0;\r
6140                 sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
6141 \r
6142                 if(cps->lastPing != cps->lastPong) {\r
6143                     savedMessage = message; // args for deferred call\r
6144                     savedState = cps;\r
6145                     ScheduleDelayedEvent(DeferredBookMove, 10);\r
6146                     return;\r
6147                 }\r
6148                 goto FakeBookMove;\r
6149         }\r
6150 \r
6151         return;\r
6152     }\r
6153 \r
6154     /* Set special modes for chess engines.  Later something general\r
6155      *  could be added here; for now there is just one kludge feature,\r
6156      *  needed because Crafty 15.10 and earlier don't ignore SIGINT\r
6157      *  when "xboard" is given as an interactive command.\r
6158      */\r
6159     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {\r
6160         cps->useSigint = FALSE;\r
6161         cps->useSigterm = FALSE;\r
6162     }\r
6163 \r
6164     /* [HGM] Allow engine to set up a position. Don't ask me why one would\r
6165      * want this, I was asked to put it in, and obliged.\r
6166      */\r
6167     if (!strncmp(message, "setboard ", 9)) {\r
6168         Board initial_position; int i;\r
6169 \r
6170         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);\r
6171 \r
6172         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {\r
6173             DisplayError(_("Bad FEN received from engine"), 0);\r
6174             return ;\r
6175         } else {\r
6176            Reset(FALSE, FALSE);\r
6177            CopyBoard(boards[0], initial_position);\r
6178            initialRulePlies = FENrulePlies;\r
6179            epStatus[0] = FENepStatus;\r
6180            for( i=0; i<nrCastlingRights; i++ )\r
6181                 castlingRights[0][i] = FENcastlingRights[i];\r
6182            if(blackPlaysFirst) gameMode = MachinePlaysWhite;\r
6183            else gameMode = MachinePlaysBlack;                 \r
6184            DrawPosition(FALSE, boards[currentMove]);\r
6185         }\r
6186         return;\r
6187     }\r
6188 \r
6189     /*\r
6190      * Look for communication commands\r
6191      */\r
6192     if (!strncmp(message, "telluser ", 9)) {\r
6193         DisplayNote(message + 9);\r
6194         return;\r
6195     }\r
6196     if (!strncmp(message, "tellusererror ", 14)) {\r
6197         DisplayError(message + 14, 0);\r
6198         return;\r
6199     }\r
6200     if (!strncmp(message, "tellopponent ", 13)) {\r
6201       if (appData.icsActive) {\r
6202         if (loggedOn) {\r
6203           sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);\r
6204           SendToICS(buf1);\r
6205         }\r
6206       } else {\r
6207         DisplayNote(message + 13);\r
6208       }\r
6209       return;\r
6210     }\r
6211     if (!strncmp(message, "tellothers ", 11)) {\r
6212       if (appData.icsActive) {\r
6213         if (loggedOn) {\r
6214           sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);\r
6215           SendToICS(buf1);\r
6216         }\r
6217       }\r
6218       return;\r
6219     }\r
6220     if (!strncmp(message, "tellall ", 8)) {\r
6221       if (appData.icsActive) {\r
6222         if (loggedOn) {\r
6223           sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);\r
6224           SendToICS(buf1);\r
6225         }\r
6226       } else {\r
6227         DisplayNote(message + 8);\r
6228       }\r
6229       return;\r
6230     }\r
6231     if (strncmp(message, "warning", 7) == 0) {\r
6232         /* Undocumented feature, use tellusererror in new code */\r
6233         DisplayError(message, 0);\r
6234         return;\r
6235     }\r
6236     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {\r
6237         strcpy(realname, cps->tidy);\r
6238         strcat(realname, " query");\r
6239         AskQuestion(realname, buf2, buf1, cps->pr);\r
6240         return;\r
6241     }\r
6242     /* Commands from the engine directly to ICS.  We don't allow these to be \r
6243      *  sent until we are logged on. Crafty kibitzes have been known to \r
6244      *  interfere with the login process.\r
6245      */\r
6246     if (loggedOn) {\r
6247         if (!strncmp(message, "tellics ", 8)) {\r
6248             SendToICS(message + 8);\r
6249             SendToICS("\n");\r
6250             return;\r
6251         }\r
6252         if (!strncmp(message, "tellicsnoalias ", 15)) {\r
6253             SendToICS(ics_prefix);\r
6254             SendToICS(message + 15);\r
6255             SendToICS("\n");\r
6256             return;\r
6257         }\r
6258         /* The following are for backward compatibility only */\r
6259         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||\r
6260             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {\r
6261             SendToICS(ics_prefix);\r
6262             SendToICS(message);\r
6263             SendToICS("\n");\r
6264             return;\r
6265         }\r
6266     }\r
6267     if (strncmp(message, "feature ", 8) == 0) {\r
6268       ParseFeatures(message+8, cps);\r
6269     }\r
6270     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {\r
6271         return;\r
6272     }\r
6273     /*\r
6274      * If the move is illegal, cancel it and redraw the board.\r
6275      * Also deal with other error cases.  Matching is rather loose\r
6276      * here to accommodate engines written before the spec.\r
6277      */\r
6278     if (strncmp(message + 1, "llegal move", 11) == 0 ||\r
6279         strncmp(message, "Error", 5) == 0) {\r
6280         if (StrStr(message, "name") || \r
6281             StrStr(message, "rating") || StrStr(message, "?") ||\r
6282             StrStr(message, "result") || StrStr(message, "board") ||\r
6283             StrStr(message, "bk") || StrStr(message, "computer") ||\r
6284             StrStr(message, "variant") || StrStr(message, "hint") ||\r
6285             StrStr(message, "random") || StrStr(message, "depth") ||\r
6286             StrStr(message, "accepted")) {\r
6287             return;\r
6288         }\r
6289         if (StrStr(message, "protover")) {\r
6290           /* Program is responding to input, so it's apparently done\r
6291              initializing, and this error message indicates it is\r
6292              protocol version 1.  So we don't need to wait any longer\r
6293              for it to initialize and send feature commands. */\r
6294           FeatureDone(cps, 1);\r
6295           cps->protocolVersion = 1;\r
6296           return;\r
6297         }\r
6298         cps->maybeThinking = FALSE;\r
6299 \r
6300         if (StrStr(message, "draw")) {\r
6301             /* Program doesn't have "draw" command */\r
6302             cps->sendDrawOffers = 0;\r
6303             return;\r
6304         }\r
6305         if (cps->sendTime != 1 &&\r
6306             (StrStr(message, "time") || StrStr(message, "otim"))) {\r
6307           /* Program apparently doesn't have "time" or "otim" command */\r
6308           cps->sendTime = 0;\r
6309           return;\r
6310         }\r
6311         if (StrStr(message, "analyze")) {\r
6312             cps->analysisSupport = FALSE;\r
6313             cps->analyzing = FALSE;\r
6314             Reset(FALSE, TRUE);\r
6315             sprintf(buf2, _("%s does not support analysis"), cps->tidy);\r
6316             DisplayError(buf2, 0);\r
6317             return;\r
6318         }\r
6319         if (StrStr(message, "(no matching move)st")) {\r
6320           /* Special kludge for GNU Chess 4 only */\r
6321           cps->stKludge = TRUE;\r
6322           SendTimeControl(cps, movesPerSession, timeControl,\r
6323                           timeIncrement, appData.searchDepth,\r
6324                           searchTime);\r
6325           return;\r
6326         }\r
6327         if (StrStr(message, "(no matching move)sd")) {\r
6328           /* Special kludge for GNU Chess 4 only */\r
6329           cps->sdKludge = TRUE;\r
6330           SendTimeControl(cps, movesPerSession, timeControl,\r
6331                           timeIncrement, appData.searchDepth,\r
6332                           searchTime);\r
6333           return;\r
6334         }\r
6335         if (!StrStr(message, "llegal")) {\r
6336             return;\r
6337         }\r
6338         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
6339             gameMode == IcsIdle) return;\r
6340         if (forwardMostMove <= backwardMostMove) return;\r
6341 #if 0\r
6342         /* Following removed: it caused a bug where a real illegal move\r
6343            message in analyze mored would be ignored. */\r
6344         if (cps == &first && programStats.ok_to_send == 0) {\r
6345             /* Bogus message from Crafty responding to "."  This filtering\r
6346                can miss some of the bad messages, but fortunately the bug \r
6347                is fixed in current Crafty versions, so it doesn't matter. */\r
6348             return;\r
6349         }\r
6350 #endif\r
6351         if (pausing) PauseEvent();\r
6352         if (gameMode == PlayFromGameFile) {\r
6353             /* Stop reading this game file */\r
6354             gameMode = EditGame;\r
6355             ModeHighlight();\r
6356         }\r
6357         currentMove = --forwardMostMove;\r
6358         DisplayMove(currentMove-1); /* before DisplayMoveError */\r
6359         SwitchClocks();\r
6360         DisplayBothClocks();\r
6361         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),\r
6362                 parseList[currentMove], cps->which);\r
6363         DisplayMoveError(buf1);\r
6364         DrawPosition(FALSE, boards[currentMove]);\r
6365 \r
6366         /* [HGM] illegal-move claim should forfeit game when Xboard */\r
6367         /* only passes fully legal moves                            */\r
6368         if( appData.testLegality && gameMode == TwoMachinesPlay ) {\r
6369             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,\r
6370                                 "False illegal-move claim", GE_XBOARD );\r
6371         }\r
6372         return;\r
6373     }\r
6374     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {\r
6375         /* Program has a broken "time" command that\r
6376            outputs a string not ending in newline.\r
6377            Don't use it. */\r
6378         cps->sendTime = 0;\r
6379     }\r
6380     \r
6381     /*\r
6382      * If chess program startup fails, exit with an error message.\r
6383      * Attempts to recover here are futile.\r
6384      */\r
6385     if ((StrStr(message, "unknown host") != NULL)\r
6386         || (StrStr(message, "No remote directory") != NULL)\r
6387         || (StrStr(message, "not found") != NULL)\r
6388         || (StrStr(message, "No such file") != NULL)\r
6389         || (StrStr(message, "can't alloc") != NULL)\r
6390         || (StrStr(message, "Permission denied") != NULL)) {\r
6391 \r
6392         cps->maybeThinking = FALSE;\r
6393         sprintf(buf1, _("Failed to start %s chess program %s on %s: %s\n"),\r
6394                 cps->which, cps->program, cps->host, message);\r
6395         RemoveInputSource(cps->isr);\r
6396         DisplayFatalError(buf1, 0, 1);\r
6397         return;\r
6398     }\r
6399     \r
6400     /* \r
6401      * Look for hint output\r
6402      */\r
6403     if (sscanf(message, "Hint: %s", buf1) == 1) {\r
6404         if (cps == &first && hintRequested) {\r
6405             hintRequested = FALSE;\r
6406             if (ParseOneMove(buf1, forwardMostMove, &moveType,\r
6407                                  &fromX, &fromY, &toX, &toY, &promoChar)) {\r
6408                 (void) CoordsToAlgebraic(boards[forwardMostMove],\r
6409                                     PosFlags(forwardMostMove), EP_UNKNOWN,\r
6410                                     fromY, fromX, toY, toX, promoChar, buf1);\r
6411                 sprintf(buf2, _("Hint: %s"), buf1);\r
6412                 DisplayInformation(buf2);\r
6413             } else {\r
6414                 /* Hint move could not be parsed!? */\r
6415                 sprintf(buf2,\r
6416                         _("Illegal hint move \"%s\"\nfrom %s chess program"),\r
6417                         buf1, cps->which);\r
6418                 DisplayError(buf2, 0);\r
6419             }\r
6420         } else {\r
6421             strcpy(lastHint, buf1);\r
6422         }\r
6423         return;\r
6424     }\r
6425 \r
6426     /*\r
6427      * Ignore other messages if game is not in progress\r
6428      */\r
6429     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
6430         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;\r
6431 \r
6432     /*\r
6433      * look for win, lose, draw, or draw offer\r
6434      */\r
6435     if (strncmp(message, "1-0", 3) == 0) {\r
6436         char *p, *q, *r = "";\r
6437         p = strchr(message, '{');\r
6438         if (p) {\r
6439             q = strchr(p, '}');\r
6440             if (q) {\r
6441                 *q = NULLCHAR;\r
6442                 r = p + 1;\r
6443             }\r
6444         }\r
6445         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */\r
6446         return;\r
6447     } else if (strncmp(message, "0-1", 3) == 0) {\r
6448         char *p, *q, *r = "";\r
6449         p = strchr(message, '{');\r
6450         if (p) {\r
6451             q = strchr(p, '}');\r
6452             if (q) {\r
6453                 *q = NULLCHAR;\r
6454                 r = p + 1;\r
6455             }\r
6456         }\r
6457         /* Kludge for Arasan 4.1 bug */\r
6458         if (strcmp(r, "Black resigns") == 0) {\r
6459             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));\r
6460             return;\r
6461         }\r
6462         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));\r
6463         return;\r
6464     } else if (strncmp(message, "1/2", 3) == 0) {\r
6465         char *p, *q, *r = "";\r
6466         p = strchr(message, '{');\r
6467         if (p) {\r
6468             q = strchr(p, '}');\r
6469             if (q) {\r
6470                 *q = NULLCHAR;\r
6471                 r = p + 1;\r
6472             }\r
6473         }\r
6474             \r
6475         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));\r
6476         return;\r
6477 \r
6478     } else if (strncmp(message, "White resign", 12) == 0) {\r
6479         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
6480         return;\r
6481     } else if (strncmp(message, "Black resign", 12) == 0) {\r
6482         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
6483         return;\r
6484     } else if (strncmp(message, "White matches", 13) == 0 ||\r
6485                strncmp(message, "Black matches", 13) == 0   ) {\r
6486         /* [HGM] ignore GNUShogi noises */\r
6487         return;\r
6488     } else if (strncmp(message, "White", 5) == 0 &&\r
6489                message[5] != '(' &&\r
6490                StrStr(message, "Black") == NULL) {\r
6491         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6492         return;\r
6493     } else if (strncmp(message, "Black", 5) == 0 &&\r
6494                message[5] != '(') {\r
6495         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6496         return;\r
6497     } else if (strcmp(message, "resign") == 0 ||\r
6498                strcmp(message, "computer resigns") == 0) {\r
6499         switch (gameMode) {\r
6500           case MachinePlaysBlack:\r
6501           case IcsPlayingBlack:\r
6502             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);\r
6503             break;\r
6504           case MachinePlaysWhite:\r
6505           case IcsPlayingWhite:\r
6506             GameEnds(BlackWins, "White resigns", GE_ENGINE);\r
6507             break;\r
6508           case TwoMachinesPlay:\r
6509             if (cps->twoMachinesColor[0] == 'w')\r
6510               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
6511             else\r
6512               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
6513             break;\r
6514           default:\r
6515             /* can't happen */\r
6516             break;\r
6517         }\r
6518         return;\r
6519     } else if (strncmp(message, "opponent mates", 14) == 0) {\r
6520         switch (gameMode) {\r
6521           case MachinePlaysBlack:\r
6522           case IcsPlayingBlack:\r
6523             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
6524             break;\r
6525           case MachinePlaysWhite:\r
6526           case IcsPlayingWhite:\r
6527             GameEnds(BlackWins, "Black mates", GE_ENGINE);\r
6528             break;\r
6529           case TwoMachinesPlay:\r
6530             if (cps->twoMachinesColor[0] == 'w')\r
6531               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6532             else\r
6533               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6534             break;\r
6535           default:\r
6536             /* can't happen */\r
6537             break;\r
6538         }\r
6539         return;\r
6540     } else if (strncmp(message, "computer mates", 14) == 0) {\r
6541         switch (gameMode) {\r
6542           case MachinePlaysBlack:\r
6543           case IcsPlayingBlack:\r
6544             GameEnds(BlackWins, "Black mates", GE_ENGINE1);\r
6545             break;\r
6546           case MachinePlaysWhite:\r
6547           case IcsPlayingWhite:\r
6548             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
6549             break;\r
6550           case TwoMachinesPlay:\r
6551             if (cps->twoMachinesColor[0] == 'w')\r
6552               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6553             else\r
6554               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6555             break;\r
6556           default:\r
6557             /* can't happen */\r
6558             break;\r
6559         }\r
6560         return;\r
6561     } else if (strncmp(message, "checkmate", 9) == 0) {\r
6562         if (WhiteOnMove(forwardMostMove)) {\r
6563             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6564         } else {\r
6565             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6566         }\r
6567         return;\r
6568     } else if (strstr(message, "Draw") != NULL ||\r
6569                strstr(message, "game is a draw") != NULL) {\r
6570         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));\r
6571         return;\r
6572     } else if (strstr(message, "offer") != NULL &&\r
6573                strstr(message, "draw") != NULL) {\r
6574 #if ZIPPY\r
6575         if (appData.zippyPlay && first.initDone) {\r
6576             /* Relay offer to ICS */\r
6577             SendToICS(ics_prefix);\r
6578             SendToICS("draw\n");\r
6579         }\r
6580 #endif\r
6581         cps->offeredDraw = 2; /* valid until this engine moves twice */\r
6582         if (gameMode == TwoMachinesPlay) {\r
6583             if (cps->other->offeredDraw) {\r
6584                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
6585             /* [HGM] in two-machine mode we delay relaying draw offer      */\r
6586             /* until after we also have move, to see if it is really claim */\r
6587             }\r
6588 #if 0\r
6589               else {\r
6590                 if (cps->other->sendDrawOffers) {\r
6591                     SendToProgram("draw\n", cps->other);\r
6592                 }\r
6593             }\r
6594 #endif\r
6595         } else if (gameMode == MachinePlaysWhite ||\r
6596                    gameMode == MachinePlaysBlack) {\r
6597           if (userOfferedDraw) {\r
6598             DisplayInformation(_("Machine accepts your draw offer"));\r
6599             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
6600           } else {\r
6601             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));\r
6602           }\r
6603         }\r
6604     }\r
6605 \r
6606     \r
6607     /*\r
6608      * Look for thinking output\r
6609      */\r
6610     if ( appData.showThinking // [HGM] thinking: test all options that cause this output\r
6611           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()\r
6612                                 ) {\r
6613         int plylev, mvleft, mvtot, curscore, time;\r
6614         char mvname[MOVE_LEN];\r
6615         u64 nodes; // [DM]\r
6616         char plyext;\r
6617         int ignore = FALSE;\r
6618         int prefixHint = FALSE;\r
6619         mvname[0] = NULLCHAR;\r
6620 \r
6621         switch (gameMode) {\r
6622           case MachinePlaysBlack:\r
6623           case IcsPlayingBlack:\r
6624             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
6625             break;\r
6626           case MachinePlaysWhite:\r
6627           case IcsPlayingWhite:\r
6628             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
6629             break;\r
6630           case AnalyzeMode:\r
6631           case AnalyzeFile:\r
6632             break;\r
6633           case IcsObserving: /* [DM] icsEngineAnalyze */\r
6634             if (!appData.icsEngineAnalyze) ignore = TRUE;\r
6635             break;\r
6636           case TwoMachinesPlay:\r
6637             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {\r
6638                 ignore = TRUE;\r
6639             }\r
6640             break;\r
6641           default:\r
6642             ignore = TRUE;\r
6643             break;\r
6644         }\r
6645 \r
6646         if (!ignore) {\r
6647             buf1[0] = NULLCHAR;\r
6648             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",\r
6649                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {\r
6650 \r
6651                 if (plyext != ' ' && plyext != '\t') {\r
6652                     time *= 100;\r
6653                 }\r
6654 \r
6655                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
6656                 if( cps->scoreIsAbsolute && \r
6657                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )\r
6658                 {\r
6659                     curscore = -curscore;\r
6660                 }\r
6661 \r
6662 \r
6663                 programStats.depth = plylev;\r
6664                 programStats.nodes = nodes;\r
6665                 programStats.time = time;\r
6666                 programStats.score = curscore;\r
6667                 programStats.got_only_move = 0;\r
6668 \r
6669                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */\r
6670                         int ticklen;\r
6671 \r
6672                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time\r
6673                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time\r
6674                         if(WhiteOnMove(forwardMostMove)) \r
6675                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;\r
6676                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;\r
6677                 }\r
6678 \r
6679                 /* Buffer overflow protection */\r
6680                 if (buf1[0] != NULLCHAR) {\r
6681                     if (strlen(buf1) >= sizeof(programStats.movelist)\r
6682                         && appData.debugMode) {\r
6683                         fprintf(debugFP,\r
6684                                 "PV is too long; using the first %d bytes.\n",\r
6685                                 sizeof(programStats.movelist) - 1);\r
6686                     }\r
6687 \r
6688                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );\r
6689                 } else {\r
6690                     sprintf(programStats.movelist, " no PV\n");\r
6691                 }\r
6692 \r
6693                 if (programStats.seen_stat) {\r
6694                     programStats.ok_to_send = 1;\r
6695                 }\r
6696 \r
6697                 if (strchr(programStats.movelist, '(') != NULL) {\r
6698                     programStats.line_is_book = 1;\r
6699                     programStats.nr_moves = 0;\r
6700                     programStats.moves_left = 0;\r
6701                 } else {\r
6702                     programStats.line_is_book = 0;\r
6703                 }\r
6704 \r
6705                 SendProgramStatsToFrontend( cps, &programStats );\r
6706 \r
6707                 /* \r
6708                     [AS] Protect the thinkOutput buffer from overflow... this\r
6709                     is only useful if buf1 hasn't overflowed first!\r
6710                 */\r
6711                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",\r
6712                         plylev, \r
6713                         (gameMode == TwoMachinesPlay ?\r
6714                          ToUpper(cps->twoMachinesColor[0]) : ' '),\r
6715                         ((double) curscore) / 100.0,\r
6716                         prefixHint ? lastHint : "",\r
6717                         prefixHint ? " " : "" );\r
6718 \r
6719                 if( buf1[0] != NULLCHAR ) {\r
6720                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;\r
6721 \r
6722                     if( strlen(buf1) > max_len ) {\r
6723                         if( appData.debugMode) {\r
6724                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");\r
6725                         }\r
6726                         buf1[max_len+1] = '\0';\r
6727                     }\r
6728 \r
6729                     strcat( thinkOutput, buf1 );\r
6730                 }\r
6731 \r
6732                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode\r
6733                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6734                     DisplayMove(currentMove - 1);\r
6735                     DisplayAnalysis();\r
6736                 }\r
6737                 return;\r
6738 \r
6739             } else if ((p=StrStr(message, "(only move)")) != NULL) {\r
6740                 /* crafty (9.25+) says "(only move) <move>"\r
6741                  * if there is only 1 legal move\r
6742                  */\r
6743                 sscanf(p, "(only move) %s", buf1);\r
6744                 sprintf(thinkOutput, "%s (only move)", buf1);\r
6745                 sprintf(programStats.movelist, "%s (only move)", buf1);\r
6746                 programStats.depth = 1;\r
6747                 programStats.nr_moves = 1;\r
6748                 programStats.moves_left = 1;\r
6749                 programStats.nodes = 1;\r
6750                 programStats.time = 1;\r
6751                 programStats.got_only_move = 1;\r
6752 \r
6753                 /* Not really, but we also use this member to\r
6754                    mean "line isn't going to change" (Crafty\r
6755                    isn't searching, so stats won't change) */\r
6756                 programStats.line_is_book = 1;\r
6757 \r
6758                 SendProgramStatsToFrontend( cps, &programStats );\r
6759                 \r
6760                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || \r
6761                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6762                     DisplayMove(currentMove - 1);\r
6763                     DisplayAnalysis();\r
6764                 }\r
6765                 return;\r
6766             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",\r
6767                               &time, &nodes, &plylev, &mvleft,\r
6768                               &mvtot, mvname) >= 5) {\r
6769                 /* The stat01: line is from Crafty (9.29+) in response\r
6770                    to the "." command */\r
6771                 programStats.seen_stat = 1;\r
6772                 cps->maybeThinking = TRUE;\r
6773 \r
6774                 if (programStats.got_only_move || !appData.periodicUpdates)\r
6775                   return;\r
6776 \r
6777                 programStats.depth = plylev;\r
6778                 programStats.time = time;\r
6779                 programStats.nodes = nodes;\r
6780                 programStats.moves_left = mvleft;\r
6781                 programStats.nr_moves = mvtot;\r
6782                 strcpy(programStats.move_name, mvname);\r
6783                 programStats.ok_to_send = 1;\r
6784                 programStats.movelist[0] = '\0';\r
6785 \r
6786                 SendProgramStatsToFrontend( cps, &programStats );\r
6787 \r
6788                 DisplayAnalysis();\r
6789                 return;\r
6790 \r
6791             } else if (strncmp(message,"++",2) == 0) {\r
6792                 /* Crafty 9.29+ outputs this */\r
6793                 programStats.got_fail = 2;\r
6794                 return;\r
6795 \r
6796             } else if (strncmp(message,"--",2) == 0) {\r
6797                 /* Crafty 9.29+ outputs this */\r
6798                 programStats.got_fail = 1;\r
6799                 return;\r
6800 \r
6801             } else if (thinkOutput[0] != NULLCHAR &&\r
6802                        strncmp(message, "    ", 4) == 0) {\r
6803                 unsigned message_len;\r
6804 \r
6805                 p = message;\r
6806                 while (*p && *p == ' ') p++;\r
6807 \r
6808                 message_len = strlen( p );\r
6809 \r
6810                 /* [AS] Avoid buffer overflow */\r
6811                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {\r
6812                     strcat(thinkOutput, " ");\r
6813                     strcat(thinkOutput, p);\r
6814                 }\r
6815 \r
6816                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {\r
6817                     strcat(programStats.movelist, " ");\r
6818                     strcat(programStats.movelist, p);\r
6819                 }\r
6820 \r
6821                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||\r
6822                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6823                     DisplayMove(currentMove - 1);\r
6824                     DisplayAnalysis();\r
6825                 }\r
6826                 return;\r
6827             }\r
6828         }\r
6829         else {\r
6830             buf1[0] = NULLCHAR;\r
6831 \r
6832             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",\r
6833                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) \r
6834             {\r
6835                 ChessProgramStats cpstats;\r
6836 \r
6837                 if (plyext != ' ' && plyext != '\t') {\r
6838                     time *= 100;\r
6839                 }\r
6840 \r
6841                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
6842                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {\r
6843                     curscore = -curscore;\r
6844                 }\r
6845 \r
6846                 cpstats.depth = plylev;\r
6847                 cpstats.nodes = nodes;\r
6848                 cpstats.time = time;\r
6849                 cpstats.score = curscore;\r
6850                 cpstats.got_only_move = 0;\r
6851                 cpstats.movelist[0] = '\0';\r
6852 \r
6853                 if (buf1[0] != NULLCHAR) {\r
6854                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );\r
6855                 }\r
6856 \r
6857                 cpstats.ok_to_send = 0;\r
6858                 cpstats.line_is_book = 0;\r
6859                 cpstats.nr_moves = 0;\r
6860                 cpstats.moves_left = 0;\r
6861 \r
6862                 SendProgramStatsToFrontend( cps, &cpstats );\r
6863             }\r
6864         }\r
6865     }\r
6866 }\r
6867 \r
6868 \r
6869 /* Parse a game score from the character string "game", and\r
6870    record it as the history of the current game.  The game\r
6871    score is NOT assumed to start from the standard position. \r
6872    The display is not updated in any way.\r
6873    */\r
6874 void\r
6875 ParseGameHistory(game)\r
6876      char *game;\r
6877 {\r
6878     ChessMove moveType;\r
6879     int fromX, fromY, toX, toY, boardIndex;\r
6880     char promoChar;\r
6881     char *p, *q;\r
6882     char buf[MSG_SIZ];\r
6883 \r
6884     if (appData.debugMode)\r
6885       fprintf(debugFP, "Parsing game history: %s\n", game);\r
6886 \r
6887     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");\r
6888     gameInfo.site = StrSave(appData.icsHost);\r
6889     gameInfo.date = PGNDate();\r
6890     gameInfo.round = StrSave("-");\r
6891 \r
6892     /* Parse out names of players */\r
6893     while (*game == ' ') game++;\r
6894     p = buf;\r
6895     while (*game != ' ') *p++ = *game++;\r
6896     *p = NULLCHAR;\r
6897     gameInfo.white = StrSave(buf);\r
6898     while (*game == ' ') game++;\r
6899     p = buf;\r
6900     while (*game != ' ' && *game != '\n') *p++ = *game++;\r
6901     *p = NULLCHAR;\r
6902     gameInfo.black = StrSave(buf);\r
6903 \r
6904     /* Parse moves */\r
6905     boardIndex = blackPlaysFirst ? 1 : 0;\r
6906     yynewstr(game);\r
6907     for (;;) {\r
6908         yyboardindex = boardIndex;\r
6909         moveType = (ChessMove) yylex();\r
6910         switch (moveType) {\r
6911           case IllegalMove:             /* maybe suicide chess, etc. */\r
6912   if (appData.debugMode) {\r
6913     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);\r
6914     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6915     setbuf(debugFP, NULL);\r
6916   }\r
6917           case WhitePromotionChancellor:\r
6918           case BlackPromotionChancellor:\r
6919           case WhitePromotionArchbishop:\r
6920           case BlackPromotionArchbishop:\r
6921           case WhitePromotionQueen:\r
6922           case BlackPromotionQueen:\r
6923           case WhitePromotionRook:\r
6924           case BlackPromotionRook:\r
6925           case WhitePromotionBishop:\r
6926           case BlackPromotionBishop:\r
6927           case WhitePromotionKnight:\r
6928           case BlackPromotionKnight:\r
6929           case WhitePromotionKing:\r
6930           case BlackPromotionKing:\r
6931           case NormalMove:\r
6932           case WhiteCapturesEnPassant:\r
6933           case BlackCapturesEnPassant:\r
6934           case WhiteKingSideCastle:\r
6935           case WhiteQueenSideCastle:\r
6936           case BlackKingSideCastle:\r
6937           case BlackQueenSideCastle:\r
6938           case WhiteKingSideCastleWild:\r
6939           case WhiteQueenSideCastleWild:\r
6940           case BlackKingSideCastleWild:\r
6941           case BlackQueenSideCastleWild:\r
6942           /* PUSH Fabien */\r
6943           case WhiteHSideCastleFR:\r
6944           case WhiteASideCastleFR:\r
6945           case BlackHSideCastleFR:\r
6946           case BlackASideCastleFR:\r
6947           /* POP Fabien */\r
6948             fromX = currentMoveString[0] - AAA;\r
6949             fromY = currentMoveString[1] - ONE;\r
6950             toX = currentMoveString[2] - AAA;\r
6951             toY = currentMoveString[3] - ONE;\r
6952             promoChar = currentMoveString[4];\r
6953             break;\r
6954           case WhiteDrop:\r
6955           case BlackDrop:\r
6956             fromX = moveType == WhiteDrop ?\r
6957               (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
6958             (int) CharToPiece(ToLower(currentMoveString[0]));\r
6959             fromY = DROP_RANK;\r
6960             toX = currentMoveString[2] - AAA;\r
6961             toY = currentMoveString[3] - ONE;\r
6962             promoChar = NULLCHAR;\r
6963             break;\r
6964           case AmbiguousMove:\r
6965             /* bug? */\r
6966             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);\r
6967   if (appData.debugMode) {\r
6968     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);\r
6969     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6970     setbuf(debugFP, NULL);\r
6971   }\r
6972             DisplayError(buf, 0);\r
6973             return;\r
6974           case ImpossibleMove:\r
6975             /* bug? */\r
6976             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);\r
6977   if (appData.debugMode) {\r
6978     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);\r
6979     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6980     setbuf(debugFP, NULL);\r
6981   }\r
6982             DisplayError(buf, 0);\r
6983             return;\r
6984           case (ChessMove) 0:   /* end of file */\r
6985             if (boardIndex < backwardMostMove) {\r
6986                 /* Oops, gap.  How did that happen? */\r
6987                 DisplayError(_("Gap in move list"), 0);\r
6988                 return;\r
6989             }\r
6990             backwardMostMove =  blackPlaysFirst ? 1 : 0;\r
6991             if (boardIndex > forwardMostMove) {\r
6992                 forwardMostMove = boardIndex;\r
6993             }\r
6994             return;\r
6995           case ElapsedTime:\r
6996             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {\r
6997                 strcat(parseList[boardIndex-1], " ");\r
6998                 strcat(parseList[boardIndex-1], yy_text);\r
6999             }\r
7000             continue;\r
7001           case Comment:\r
7002           case PGNTag:\r
7003           case NAG:\r
7004           default:\r
7005             /* ignore */\r
7006             continue;\r
7007           case WhiteWins:\r
7008           case BlackWins:\r
7009           case GameIsDrawn:\r
7010           case GameUnfinished:\r
7011             if (gameMode == IcsExamining) {\r
7012                 if (boardIndex < backwardMostMove) {\r
7013                     /* Oops, gap.  How did that happen? */\r
7014                     return;\r
7015                 }\r
7016                 backwardMostMove = blackPlaysFirst ? 1 : 0;\r
7017                 return;\r
7018             }\r
7019             gameInfo.result = moveType;\r
7020             p = strchr(yy_text, '{');\r
7021             if (p == NULL) p = strchr(yy_text, '(');\r
7022             if (p == NULL) {\r
7023                 p = yy_text;\r
7024                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
7025             } else {\r
7026                 q = strchr(p, *p == '{' ? '}' : ')');\r
7027                 if (q != NULL) *q = NULLCHAR;\r
7028                 p++;\r
7029             }\r
7030             gameInfo.resultDetails = StrSave(p);\r
7031             continue;\r
7032         }\r
7033         if (boardIndex >= forwardMostMove &&\r
7034             !(gameMode == IcsObserving && ics_gamenum == -1)) {\r
7035             backwardMostMove = blackPlaysFirst ? 1 : 0;\r
7036             return;\r
7037         }\r
7038         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),\r
7039                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,\r
7040                                  parseList[boardIndex]);\r
7041         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);\r
7042         /* currentMoveString is set as a side-effect of yylex */\r
7043         strcpy(moveList[boardIndex], currentMoveString);\r
7044         strcat(moveList[boardIndex], "\n");\r
7045         boardIndex++;\r
7046         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);\r
7047         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),\r
7048                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {\r
7049           case MT_NONE:\r
7050           case MT_STALEMATE:\r
7051           default:\r
7052             break;\r
7053           case MT_CHECK:\r
7054             if(gameInfo.variant != VariantShogi)\r
7055                 strcat(parseList[boardIndex - 1], "+");\r
7056             break;\r
7057           case MT_CHECKMATE:\r
7058             strcat(parseList[boardIndex - 1], "#");\r
7059             break;\r
7060         }\r
7061     }\r
7062 }\r
7063 \r
7064 \r
7065 /* Apply a move to the given board  */\r
7066 void\r
7067 ApplyMove(fromX, fromY, toX, toY, promoChar, board)\r
7068      int fromX, fromY, toX, toY;\r
7069      int promoChar;\r
7070      Board board;\r
7071 {\r
7072   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;\r
7073 \r
7074     /* [HGM] compute & store e.p. status and castling rights for new position */\r
7075     /* if we are updating a board for which those exist (i.e. in boards[])    */\r
7076     if((p = ((int)board - (int)boards[0])/((int)boards[1]-(int)boards[0])) < MAX_MOVES && p > 0)\r
7077     { int i;\r
7078 \r
7079       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;\r
7080       oldEP = epStatus[p-1];\r
7081       epStatus[p] = EP_NONE;\r
7082 \r
7083       if( board[toY][toX] != EmptySquare ) \r
7084            epStatus[p] = EP_CAPTURE;  \r
7085 \r
7086       if( board[fromY][fromX] == WhitePawn ) {\r
7087            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers\r
7088                epStatus[p] = EP_PAWN_MOVE;\r
7089            if( toY-fromY==2) {\r
7090                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&\r
7091                         gameInfo.variant != VariantBerolina || toX < fromX)\r
7092                       epStatus[p] = toX | berolina;\r
7093                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&\r
7094                         gameInfo.variant != VariantBerolina || toX > fromX) \r
7095                       epStatus[p] = toX;\r
7096            }\r
7097       } else \r
7098       if( board[fromY][fromX] == BlackPawn ) {\r
7099            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers\r
7100                epStatus[p] = EP_PAWN_MOVE; \r
7101            if( toY-fromY== -2) {\r
7102                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&\r
7103                         gameInfo.variant != VariantBerolina || toX < fromX)\r
7104                       epStatus[p] = toX | berolina;\r
7105                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&\r
7106                         gameInfo.variant != VariantBerolina || toX > fromX) \r
7107                       epStatus[p] = toX;\r
7108            }\r
7109        }\r
7110 \r
7111        for(i=0; i<nrCastlingRights; i++) {\r
7112            castlingRights[p][i] = castlingRights[p-1][i];\r
7113            if(castlingRights[p][i] == fromX && castlingRank[i] == fromY ||\r
7114               castlingRights[p][i] == toX   && castlingRank[i] == toY   \r
7115              ) castlingRights[p][i] = -1; // revoke for moved or captured piece\r
7116        }\r
7117 \r
7118     }\r
7119 \r
7120   /* [HGM] In Shatranj and Courier all promotions are to Ferz */\r
7121   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)\r
7122        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);\r
7123          \r
7124   if (fromX == toX && fromY == toY) return;\r
7125 \r
7126   if (fromY == DROP_RANK) {\r
7127         /* must be first */\r
7128         piece = board[toY][toX] = (ChessSquare) fromX;\r
7129   } else {\r
7130      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */\r
7131      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */\r
7132      if(gameInfo.variant == VariantKnightmate)\r
7133          king += (int) WhiteUnicorn - (int) WhiteKing;\r
7134 \r
7135     /* Code added by Tord: */\r
7136     /* FRC castling assumed when king captures friendly rook. */\r
7137     if (board[fromY][fromX] == WhiteKing &&\r
7138              board[toY][toX] == WhiteRook) {\r
7139       board[fromY][fromX] = EmptySquare;\r
7140       board[toY][toX] = EmptySquare;\r
7141       if(toX > fromX) {\r
7142         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;\r
7143       } else {\r
7144         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;\r
7145       }\r
7146     } else if (board[fromY][fromX] == BlackKing &&\r
7147                board[toY][toX] == BlackRook) {\r
7148       board[fromY][fromX] = EmptySquare;\r
7149       board[toY][toX] = EmptySquare;\r
7150       if(toX > fromX) {\r
7151         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;\r
7152       } else {\r
7153         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;\r
7154       }\r
7155     /* End of code added by Tord */\r
7156 \r
7157     } else if (board[fromY][fromX] == king\r
7158         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7159         && toY == fromY && toX > fromX+1) {\r
7160         board[fromY][fromX] = EmptySquare;\r
7161         board[toY][toX] = king;\r
7162         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
7163         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
7164     } else if (board[fromY][fromX] == king\r
7165         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7166                && toY == fromY && toX < fromX-1) {\r
7167         board[fromY][fromX] = EmptySquare;\r
7168         board[toY][toX] = king;\r
7169         board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
7170         board[fromY][BOARD_LEFT] = EmptySquare;\r
7171     } else if (board[fromY][fromX] == WhitePawn\r
7172                && toY == BOARD_HEIGHT-1\r
7173                && gameInfo.variant != VariantXiangqi\r
7174                ) {\r
7175         /* white pawn promotion */\r
7176         board[toY][toX] = CharToPiece(ToUpper(promoChar));\r
7177         if (board[toY][toX] == EmptySquare) {\r
7178             board[toY][toX] = WhiteQueen;\r
7179         }\r
7180         if(gameInfo.variant==VariantBughouse ||\r
7181            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
7182             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
7183         board[fromY][fromX] = EmptySquare;\r
7184     } else if ((fromY == BOARD_HEIGHT-4)\r
7185                && (toX != fromX)\r
7186                && gameInfo.variant != VariantXiangqi\r
7187                && gameInfo.variant != VariantBerolina\r
7188                && (board[fromY][fromX] == WhitePawn)\r
7189                && (board[toY][toX] == EmptySquare)) {\r
7190         board[fromY][fromX] = EmptySquare;\r
7191         board[toY][toX] = WhitePawn;\r
7192         captured = board[toY - 1][toX];\r
7193         board[toY - 1][toX] = EmptySquare;\r
7194     } else if ((fromY == BOARD_HEIGHT-4)\r
7195                && (toX == fromX)\r
7196                && gameInfo.variant == VariantBerolina\r
7197                && (board[fromY][fromX] == WhitePawn)\r
7198                && (board[toY][toX] == EmptySquare)) {\r
7199         board[fromY][fromX] = EmptySquare;\r
7200         board[toY][toX] = WhitePawn;\r
7201         if(oldEP & EP_BEROLIN_A) {\r
7202                 captured = board[fromY][fromX-1];\r
7203                 board[fromY][fromX-1] = EmptySquare;\r
7204         }else{  captured = board[fromY][fromX+1];\r
7205                 board[fromY][fromX+1] = EmptySquare;\r
7206         }\r
7207     } else if (board[fromY][fromX] == king\r
7208         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7209                && toY == fromY && toX > fromX+1) {\r
7210         board[fromY][fromX] = EmptySquare;\r
7211         board[toY][toX] = king;\r
7212         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
7213         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
7214     } else if (board[fromY][fromX] == king\r
7215         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7216                && toY == fromY && toX < fromX-1) {\r
7217         board[fromY][fromX] = EmptySquare;\r
7218         board[toY][toX] = king;\r
7219         board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
7220         board[fromY][BOARD_LEFT] = EmptySquare;\r
7221     } else if (fromY == 7 && fromX == 3\r
7222                && board[fromY][fromX] == BlackKing\r
7223                && toY == 7 && toX == 5) {\r
7224         board[fromY][fromX] = EmptySquare;\r
7225         board[toY][toX] = BlackKing;\r
7226         board[fromY][7] = EmptySquare;\r
7227         board[toY][4] = BlackRook;\r
7228     } else if (fromY == 7 && fromX == 3\r
7229                && board[fromY][fromX] == BlackKing\r
7230                && toY == 7 && toX == 1) {\r
7231         board[fromY][fromX] = EmptySquare;\r
7232         board[toY][toX] = BlackKing;\r
7233         board[fromY][0] = EmptySquare;\r
7234         board[toY][2] = BlackRook;\r
7235     } else if (board[fromY][fromX] == BlackPawn\r
7236                && toY == 0\r
7237                && gameInfo.variant != VariantXiangqi\r
7238                ) {\r
7239         /* black pawn promotion */\r
7240         board[0][toX] = CharToPiece(ToLower(promoChar));\r
7241         if (board[0][toX] == EmptySquare) {\r
7242             board[0][toX] = BlackQueen;\r
7243         }\r
7244         if(gameInfo.variant==VariantBughouse ||\r
7245            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
7246             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
7247         board[fromY][fromX] = EmptySquare;\r
7248     } else if ((fromY == 3)\r
7249                && (toX != fromX)\r
7250                && gameInfo.variant != VariantXiangqi\r
7251                && gameInfo.variant != VariantBerolina\r
7252                && (board[fromY][fromX] == BlackPawn)\r
7253                && (board[toY][toX] == EmptySquare)) {\r
7254         board[fromY][fromX] = EmptySquare;\r
7255         board[toY][toX] = BlackPawn;\r
7256         captured = board[toY + 1][toX];\r
7257         board[toY + 1][toX] = EmptySquare;\r
7258     } else if ((fromY == 3)\r
7259                && (toX == fromX)\r
7260                && gameInfo.variant == VariantBerolina\r
7261                && (board[fromY][fromX] == BlackPawn)\r
7262                && (board[toY][toX] == EmptySquare)) {\r
7263         board[fromY][fromX] = EmptySquare;\r
7264         board[toY][toX] = BlackPawn;\r
7265         if(oldEP & EP_BEROLIN_A) {\r
7266                 captured = board[fromY][fromX-1];\r
7267                 board[fromY][fromX-1] = EmptySquare;\r
7268         }else{  captured = board[fromY][fromX+1];\r
7269                 board[fromY][fromX+1] = EmptySquare;\r
7270         }\r
7271     } else {\r
7272         board[toY][toX] = board[fromY][fromX];\r
7273         board[fromY][fromX] = EmptySquare;\r
7274     }\r
7275 \r
7276     /* [HGM] now we promote for Shogi, if needed */\r
7277     if(gameInfo.variant == VariantShogi && promoChar == 'q')\r
7278         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
7279   }\r
7280 \r
7281     if (gameInfo.holdingsWidth != 0) {\r
7282 \r
7283       /* !!A lot more code needs to be written to support holdings  */\r
7284       /* [HGM] OK, so I have written it. Holdings are stored in the */\r
7285       /* penultimate board files, so they are automaticlly stored   */\r
7286       /* in the game history.                                       */\r
7287       if (fromY == DROP_RANK) {\r
7288         /* Delete from holdings, by decreasing count */\r
7289         /* and erasing image if necessary            */\r
7290         p = (int) fromX;\r
7291         if(p < (int) BlackPawn) { /* white drop */\r
7292              p -= (int)WhitePawn;\r
7293              if(p >= gameInfo.holdingsSize) p = 0;\r
7294              if(--board[p][BOARD_WIDTH-2] == 0)\r
7295                   board[p][BOARD_WIDTH-1] = EmptySquare;\r
7296         } else {                  /* black drop */\r
7297              p -= (int)BlackPawn;\r
7298              if(p >= gameInfo.holdingsSize) p = 0;\r
7299              if(--board[BOARD_HEIGHT-1-p][1] == 0)\r
7300                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;\r
7301         }\r
7302       }\r
7303       if (captured != EmptySquare && gameInfo.holdingsSize > 0\r
7304           && gameInfo.variant != VariantBughouse        ) {\r
7305         /* [HGM] holdings: Add to holdings, if holdings exist */\r
7306         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { \r
7307                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip\r
7308                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;\r
7309         }\r
7310         p = (int) captured;\r
7311         if (p >= (int) BlackPawn) {\r
7312           p -= (int)BlackPawn;\r
7313           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
7314                   /* in Shogi restore piece to its original  first */\r
7315                   captured = (ChessSquare) (DEMOTED captured);\r
7316                   p = DEMOTED p;\r
7317           }\r
7318           p = PieceToNumber((ChessSquare)p);\r
7319           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }\r
7320           board[p][BOARD_WIDTH-2]++;\r
7321           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;\r
7322         } else {\r
7323           p -= (int)WhitePawn;\r
7324           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
7325                   captured = (ChessSquare) (DEMOTED captured);\r
7326                   p = DEMOTED p;\r
7327           }\r
7328           p = PieceToNumber((ChessSquare)p);\r
7329           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }\r
7330           board[BOARD_HEIGHT-1-p][1]++;\r
7331           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;\r
7332         }\r
7333       }\r
7334 \r
7335     } else if (gameInfo.variant == VariantAtomic) {\r
7336       if (captured != EmptySquare) {\r
7337         int y, x;\r
7338         for (y = toY-1; y <= toY+1; y++) {\r
7339           for (x = toX-1; x <= toX+1; x++) {\r
7340             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&\r
7341                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {\r
7342               board[y][x] = EmptySquare;\r
7343             }\r
7344           }\r
7345         }\r
7346         board[toY][toX] = EmptySquare;\r
7347       }\r
7348     }\r
7349     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {\r
7350         /* [HGM] Shogi promotions */\r
7351         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
7352     }\r
7353 \r
7354 }\r
7355 \r
7356 /* Updates forwardMostMove */\r
7357 void\r
7358 MakeMove(fromX, fromY, toX, toY, promoChar)\r
7359      int fromX, fromY, toX, toY;\r
7360      int promoChar;\r
7361 {\r
7362 //    forwardMostMove++; // [HGM] bare: moved downstream\r
7363 \r
7364     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */\r
7365         int timeLeft; static int lastLoadFlag=0; int king, piece;\r
7366         piece = boards[forwardMostMove][fromY][fromX];\r
7367         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;\r
7368         if(gameInfo.variant == VariantKnightmate)\r
7369             king += (int) WhiteUnicorn - (int) WhiteKing;\r
7370         if(forwardMostMove == 0) {\r
7371             if(blackPlaysFirst) \r
7372                 fprintf(serverMoves, "%s;", second.tidy);\r
7373             fprintf(serverMoves, "%s;", first.tidy);\r
7374             if(!blackPlaysFirst) \r
7375                 fprintf(serverMoves, "%s;", second.tidy);\r
7376         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");\r
7377         lastLoadFlag = loadFlag;\r
7378         // print base move\r
7379         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);\r
7380         // print castling suffix\r
7381         if( toY == fromY && piece == king ) {\r
7382             if(toX-fromX > 1)\r
7383                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);\r
7384             if(fromX-toX >1)\r
7385                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);\r
7386         }\r
7387         // e.p. suffix\r
7388         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||\r
7389              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&\r
7390              boards[forwardMostMove][toY][toX] == EmptySquare\r
7391              && fromX != toX )\r
7392                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);\r
7393         // promotion suffix\r
7394         if(promoChar != NULLCHAR)\r
7395                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);\r
7396         if(!loadFlag) {\r
7397             fprintf(serverMoves, "/%d/%d",\r
7398                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);\r
7399             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;\r
7400             else                      timeLeft = blackTimeRemaining/1000;\r
7401             fprintf(serverMoves, "/%d", timeLeft);\r
7402         }\r
7403         fflush(serverMoves);\r
7404     }\r
7405 \r
7406     if (forwardMostMove+1 >= MAX_MOVES) {\r
7407       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),\r
7408                         0, 1);\r
7409       return;\r
7410     }\r
7411     SwitchClocks();\r
7412     timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;\r
7413     timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;\r
7414     if (commentList[forwardMostMove+1] != NULL) {\r
7415         free(commentList[forwardMostMove+1]);\r
7416         commentList[forwardMostMove+1] = NULL;\r
7417     }\r
7418     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);\r
7419     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);\r
7420     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board\r
7421     gameInfo.result = GameUnfinished;\r
7422     if (gameInfo.resultDetails != NULL) {\r
7423         free(gameInfo.resultDetails);\r
7424         gameInfo.resultDetails = NULL;\r
7425     }\r
7426     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,\r
7427                               moveList[forwardMostMove - 1]);\r
7428     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],\r
7429                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,\r
7430                              fromY, fromX, toY, toX, promoChar,\r
7431                              parseList[forwardMostMove - 1]);\r
7432     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
7433                        epStatus[forwardMostMove], /* [HGM] use true e.p. */\r
7434                             castlingRights[forwardMostMove]) ) {\r
7435       case MT_NONE:\r
7436       case MT_STALEMATE:\r
7437       default:\r
7438         break;\r
7439       case MT_CHECK:\r
7440         if(gameInfo.variant != VariantShogi)\r
7441             strcat(parseList[forwardMostMove - 1], "+");\r
7442         break;\r
7443       case MT_CHECKMATE:\r
7444         strcat(parseList[forwardMostMove - 1], "#");\r
7445         break;\r
7446     }\r
7447     if (appData.debugMode) {\r
7448         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);\r
7449     }\r
7450 \r
7451 }\r
7452 \r
7453 /* Updates currentMove if not pausing */\r
7454 void\r
7455 ShowMove(fromX, fromY, toX, toY)\r
7456 {\r
7457     int instant = (gameMode == PlayFromGameFile) ?\r
7458         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;\r
7459     if(appData.noGUI) return;\r
7460     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
7461         if (!instant) {\r
7462             if (forwardMostMove == currentMove + 1) {\r
7463                 AnimateMove(boards[forwardMostMove - 1],\r
7464                             fromX, fromY, toX, toY);\r
7465             }\r
7466             if (appData.highlightLastMove) {\r
7467                 SetHighlights(fromX, fromY, toX, toY);\r
7468             }\r
7469         }\r
7470         currentMove = forwardMostMove;\r
7471     }\r
7472 \r
7473     if (instant) return;\r
7474 \r
7475     DisplayMove(currentMove - 1);\r
7476     DrawPosition(FALSE, boards[currentMove]);\r
7477     DisplayBothClocks();\r
7478     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
7479 }\r
7480 \r
7481 void SendEgtPath(ChessProgramState *cps)\r
7482 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */\r
7483         char buf[MSG_SIZ], name[MSG_SIZ], *p;\r
7484 \r
7485         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;\r
7486 \r
7487         while(*p) {\r
7488             char c, *q = name+1, *r, *s;\r
7489 \r
7490             name[0] = ','; // extract next format name from feature and copy with prefixed ','\r
7491             while(*p && *p != ',') *q++ = *p++;\r
7492             *q++ = ':'; *q = 0;\r
7493             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && \r
7494                 strcmp(name, ",nalimov:") == 0 ) {\r
7495                 // take nalimov path from the menu-changeable option first, if it is defined\r
7496                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);\r
7497                 SendToProgram(buf,cps);     // send egtbpath command for nalimov\r
7498             } else\r
7499             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||\r
7500                 (s = StrStr(appData.egtFormats, name)) != NULL) {\r
7501                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma\r
7502                 s = r = StrStr(s, ":") + 1; // beginning of path info\r
7503                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string\r
7504                 c = *r; *r = 0;             // temporarily null-terminate path info\r
7505                     *--q = 0;               // strip of trailig ':' from name\r
7506                     sprintf(buf, "egtbpath %s %s\n", name+1, s);\r
7507                 *r = c;\r
7508                 SendToProgram(buf,cps);     // send egtbpath command for this format\r
7509             }\r
7510             if(*p == ',') p++; // read away comma to position for next format name\r
7511         }\r
7512 }\r
7513 \r
7514 void\r
7515 InitChessProgram(cps, setup)\r
7516      ChessProgramState *cps;\r
7517      int setup; /* [HGM] needed to setup FRC opening position */\r
7518 {\r
7519     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;\r
7520     if (appData.noChessProgram) return;\r
7521     hintRequested = FALSE;\r
7522     bookRequested = FALSE;\r
7523 \r
7524     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */\r
7525     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */\r
7526     if(cps->memSize) { /* [HGM] memory */\r
7527         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);\r
7528         SendToProgram(buf, cps);\r
7529     }\r
7530     SendEgtPath(cps); /* [HGM] EGT */\r
7531     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */\r
7532         sprintf(buf, "cores %d\n", appData.smpCores);\r
7533         SendToProgram(buf, cps);\r
7534     }\r
7535 \r
7536     SendToProgram(cps->initString, cps);\r
7537     if (gameInfo.variant != VariantNormal &&\r
7538         gameInfo.variant != VariantLoadable\r
7539         /* [HGM] also send variant if board size non-standard */\r
7540         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0\r
7541                                             ) {\r
7542       char *v = VariantName(gameInfo.variant);\r
7543       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {\r
7544         /* [HGM] in protocol 1 we have to assume all variants valid */\r
7545         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);\r
7546         DisplayFatalError(buf, 0, 1);\r
7547         return;\r
7548       }\r
7549 \r
7550       /* [HGM] make prefix for non-standard board size. Awkward testing... */\r
7551       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7552       if( gameInfo.variant == VariantXiangqi )\r
7553            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;\r
7554       if( gameInfo.variant == VariantShogi )\r
7555            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;\r
7556       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )\r
7557            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;\r
7558       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || \r
7559                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )\r
7560            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7561       if( gameInfo.variant == VariantCourier )\r
7562            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7563       if( gameInfo.variant == VariantSuper )\r
7564            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;\r
7565       if( gameInfo.variant == VariantGreat )\r
7566            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;\r
7567 \r
7568       if(overruled) {\r
7569            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, \r
7570                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name\r
7571            /* [HGM] varsize: try first if this defiant size variant is specifically known */\r
7572            if(StrStr(cps->variants, b) == NULL) { \r
7573                // specific sized variant not known, check if general sizing allowed\r
7574                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best\r
7575                    if(StrStr(cps->variants, "boardsize") == NULL) {\r
7576                        sprintf(buf, "Board size %dx%d+%d not supported by %s",\r
7577                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);\r
7578                        DisplayFatalError(buf, 0, 1);\r
7579                        return;\r
7580                    }\r
7581                    /* [HGM] here we really should compare with the maximum supported board size */\r
7582                }\r
7583            }\r
7584       } else sprintf(b, "%s", VariantName(gameInfo.variant));\r
7585       sprintf(buf, "variant %s\n", b);\r
7586       SendToProgram(buf, cps);\r
7587     }\r
7588     currentlyInitializedVariant = gameInfo.variant;\r
7589 \r
7590     /* [HGM] send opening position in FRC to first engine */\r
7591     if(setup) {\r
7592           SendToProgram("force\n", cps);\r
7593           SendBoard(cps, 0);\r
7594           /* engine is now in force mode! Set flag to wake it up after first move. */\r
7595           setboardSpoiledMachineBlack = 1;\r
7596     }\r
7597 \r
7598     if (cps->sendICS) {\r
7599       sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");\r
7600       SendToProgram(buf, cps);\r
7601     }\r
7602     cps->maybeThinking = FALSE;\r
7603     cps->offeredDraw = 0;\r
7604     if (!appData.icsActive) {\r
7605         SendTimeControl(cps, movesPerSession, timeControl,\r
7606                         timeIncrement, appData.searchDepth,\r
7607                         searchTime);\r
7608     }\r
7609     if (appData.showThinking \r
7610         // [HGM] thinking: four options require thinking output to be sent\r
7611         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()\r
7612                                 ) {\r
7613         SendToProgram("post\n", cps);\r
7614     }\r
7615     SendToProgram("hard\n", cps);\r
7616     if (!appData.ponderNextMove) {\r
7617         /* Warning: "easy" is a toggle in GNU Chess, so don't send\r
7618            it without being sure what state we are in first.  "hard"\r
7619            is not a toggle, so that one is OK.\r
7620          */\r
7621         SendToProgram("easy\n", cps);\r
7622     }\r
7623     if (cps->usePing) {\r
7624       sprintf(buf, "ping %d\n", ++cps->lastPing);\r
7625       SendToProgram(buf, cps);\r
7626     }\r
7627     cps->initDone = TRUE;\r
7628 }   \r
7629 \r
7630 \r
7631 void\r
7632 StartChessProgram(cps)\r
7633      ChessProgramState *cps;\r
7634 {\r
7635     char buf[MSG_SIZ];\r
7636     int err;\r
7637 \r
7638     if (appData.noChessProgram) return;\r
7639     cps->initDone = FALSE;\r
7640 \r
7641     if (strcmp(cps->host, "localhost") == 0) {\r
7642         err = StartChildProcess(cps->program, cps->dir, &cps->pr);\r
7643     } else if (*appData.remoteShell == NULLCHAR) {\r
7644         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);\r
7645     } else {\r
7646         if (*appData.remoteUser == NULLCHAR) {\r
7647             sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,\r
7648                     cps->program);\r
7649         } else {\r
7650             sprintf(buf, "%s %s -l %s %s", appData.remoteShell,\r
7651                     cps->host, appData.remoteUser, cps->program);\r
7652         }\r
7653         err = StartChildProcess(buf, "", &cps->pr);\r
7654     }\r
7655     \r
7656     if (err != 0) {\r
7657         sprintf(buf, _("Startup failure on '%s'"), cps->program);\r
7658         DisplayFatalError(buf, err, 1);\r
7659         cps->pr = NoProc;\r
7660         cps->isr = NULL;\r
7661         return;\r
7662     }\r
7663     \r
7664     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);\r
7665     if (cps->protocolVersion > 1) {\r
7666       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);\r
7667       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options\r
7668       cps->comboCnt = 0;  //                and values of combo boxes\r
7669       SendToProgram(buf, cps);\r
7670     } else {\r
7671       SendToProgram("xboard\n", cps);\r
7672     }\r
7673 }\r
7674 \r
7675 \r
7676 void\r
7677 TwoMachinesEventIfReady P((void))\r
7678 {\r
7679   if (first.lastPing != first.lastPong) {\r
7680     DisplayMessage("", _("Waiting for first chess program"));\r
7681     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000\r
7682     return;\r
7683   }\r
7684   if (second.lastPing != second.lastPong) {\r
7685     DisplayMessage("", _("Waiting for second chess program"));\r
7686     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000\r
7687     return;\r
7688   }\r
7689   ThawUI();\r
7690   TwoMachinesEvent();\r
7691 }\r
7692 \r
7693 void\r
7694 NextMatchGame P((void))\r
7695 {\r
7696     int index; /* [HGM] autoinc: step lod index during match */\r
7697     Reset(FALSE, TRUE);\r
7698     if (*appData.loadGameFile != NULLCHAR) {\r
7699         index = appData.loadGameIndex;\r
7700         if(index < 0) { // [HGM] autoinc\r
7701             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;\r
7702             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;\r
7703         } \r
7704         LoadGameFromFile(appData.loadGameFile,\r
7705                          index,\r
7706                          appData.loadGameFile, FALSE);\r
7707     } else if (*appData.loadPositionFile != NULLCHAR) {\r
7708         index = appData.loadPositionIndex;\r
7709         if(index < 0) { // [HGM] autoinc\r
7710             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;\r
7711             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;\r
7712         } \r
7713         LoadPositionFromFile(appData.loadPositionFile,\r
7714                              index,\r
7715                              appData.loadPositionFile);\r
7716     }\r
7717     TwoMachinesEventIfReady();\r
7718 }\r
7719 \r
7720 void UserAdjudicationEvent( int result )\r
7721 {\r
7722     ChessMove gameResult = GameIsDrawn;\r
7723 \r
7724     if( result > 0 ) {\r
7725         gameResult = WhiteWins;\r
7726     }\r
7727     else if( result < 0 ) {\r
7728         gameResult = BlackWins;\r
7729     }\r
7730 \r
7731     if( gameMode == TwoMachinesPlay ) {\r
7732         GameEnds( gameResult, "User adjudication", GE_XBOARD );\r
7733     }\r
7734 }\r
7735 \r
7736 \r
7737 void\r
7738 GameEnds(result, resultDetails, whosays)\r
7739      ChessMove result;\r
7740      char *resultDetails;\r
7741      int whosays;\r
7742 {\r
7743     GameMode nextGameMode;\r
7744     int isIcsGame;\r
7745     char buf[MSG_SIZ];\r
7746 \r
7747     if(endingGame) return; /* [HGM] crash: forbid recursion */\r
7748     endingGame = 1;\r
7749 \r
7750     if (appData.debugMode) {\r
7751       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",\r
7752               result, resultDetails ? resultDetails : "(null)", whosays);\r
7753     }\r
7754 \r
7755     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {\r
7756         /* If we are playing on ICS, the server decides when the\r
7757            game is over, but the engine can offer to draw, claim \r
7758            a draw, or resign. \r
7759          */\r
7760 #if ZIPPY\r
7761         if (appData.zippyPlay && first.initDone) {\r
7762             if (result == GameIsDrawn) {\r
7763                 /* In case draw still needs to be claimed */\r
7764                 SendToICS(ics_prefix);\r
7765                 SendToICS("draw\n");\r
7766             } else if (StrCaseStr(resultDetails, "resign")) {\r
7767                 SendToICS(ics_prefix);\r
7768                 SendToICS("resign\n");\r
7769             }\r
7770         }\r
7771 #endif\r
7772         endingGame = 0; /* [HGM] crash */\r
7773         return;\r
7774     }\r
7775 \r
7776     /* If we're loading the game from a file, stop */\r
7777     if (whosays == GE_FILE) {\r
7778       (void) StopLoadGameTimer();\r
7779       gameFileFP = NULL;\r
7780     }\r
7781 \r
7782     /* Cancel draw offers */\r
7783     first.offeredDraw = second.offeredDraw = 0;\r
7784 \r
7785     /* If this is an ICS game, only ICS can really say it's done;\r
7786        if not, anyone can. */\r
7787     isIcsGame = (gameMode == IcsPlayingWhite || \r
7788                  gameMode == IcsPlayingBlack || \r
7789                  gameMode == IcsObserving    || \r
7790                  gameMode == IcsExamining);\r
7791 \r
7792     if (!isIcsGame || whosays == GE_ICS) {\r
7793         /* OK -- not an ICS game, or ICS said it was done */\r
7794         StopClocks();\r
7795         if (!isIcsGame && !appData.noChessProgram) \r
7796           SetUserThinkingEnables();\r
7797     \r
7798         /* [HGM] if a machine claims the game end we verify this claim */\r
7799         if(gameMode == TwoMachinesPlay && appData.testClaims) {\r
7800             if(appData.testLegality && whosays >= GE_ENGINE1 ) {\r
7801                 char claimer;\r
7802 \r
7803                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */\r
7804                                             first.twoMachinesColor[0] :\r
7805                                             second.twoMachinesColor[0] ;\r
7806                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) &&\r
7807                     (result == WhiteWins && claimer == 'w' ||\r
7808                      result == BlackWins && claimer == 'b'   ) ) {\r
7809                 if (appData.debugMode) {\r
7810                      fprintf(debugFP, "result=%d sp=%d move=%d\n",\r
7811                         result, epStatus[forwardMostMove], forwardMostMove);\r
7812                 }\r
7813                       /* [HGM] verify: engine mate claims accepted if they were flagged */\r
7814                       if(epStatus[forwardMostMove] != EP_CHECKMATE &&\r
7815                          result != (WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins)) {\r
7816                               sprintf(buf, "False win claim: '%s'", resultDetails);\r
7817                               result = claimer == 'w' ? BlackWins : WhiteWins;\r
7818                               resultDetails = buf;\r
7819                       }\r
7820                 } else\r
7821                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS\r
7822                     && (forwardMostMove <= backwardMostMove ||\r
7823                         epStatus[forwardMostMove-1] > EP_DRAWS ||\r
7824                         (claimer=='b')==(forwardMostMove&1))\r
7825                                                                                   ) {\r
7826                       /* [HGM] verify: draws that were not flagged are false claims */\r
7827                       sprintf(buf, "False draw claim: '%s'", resultDetails);\r
7828                       result = claimer == 'w' ? BlackWins : WhiteWins;\r
7829                       resultDetails = buf;\r
7830                 }\r
7831                 /* (Claiming a loss is accepted no questions asked!) */\r
7832             }\r
7833             /* [HGM] bare: don't allow bare King to win */\r
7834             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)\r
7835                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway \r
7836                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...\r
7837                && result != GameIsDrawn)\r
7838             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);\r
7839                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {\r
7840                         int p = (int)boards[forwardMostMove][i][j] - color;\r
7841                         if(p >= 0 && p <= (int)WhiteKing) k++;\r
7842                 }\r
7843                 if (appData.debugMode) {\r
7844                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",\r
7845                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);\r
7846                 }\r
7847                 if(k <= 1) {\r
7848                         result = GameIsDrawn;\r
7849                         sprintf(buf, "%s but bare king", resultDetails);\r
7850                         resultDetails = buf;\r
7851                 }\r
7852             }\r
7853         }\r
7854 \r
7855 \r
7856         if(serverMoves != NULL && !loadFlag) { char c = '=';\r
7857             if(result==WhiteWins) c = '+';\r
7858             if(result==BlackWins) c = '-';\r
7859             if(resultDetails != NULL)\r
7860                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);\r
7861         }\r
7862         if (resultDetails != NULL) {\r
7863             gameInfo.result = result;\r
7864             gameInfo.resultDetails = StrSave(resultDetails);\r
7865 \r
7866             /* display last move only if game was not loaded from file */\r
7867             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))\r
7868                 DisplayMove(currentMove - 1);\r
7869     \r
7870             if (forwardMostMove != 0) {\r
7871                 if (gameMode != PlayFromGameFile && gameMode != EditGame) {\r
7872                     if (*appData.saveGameFile != NULLCHAR) {\r
7873                         SaveGameToFile(appData.saveGameFile, TRUE);\r
7874                     } else if (appData.autoSaveGames) {\r
7875                         AutoSaveGame();\r
7876                     }\r
7877                     if (*appData.savePositionFile != NULLCHAR) {\r
7878                         SavePositionToFile(appData.savePositionFile);\r
7879                     }\r
7880                 }\r
7881             }\r
7882 \r
7883             /* Tell program how game ended in case it is learning */\r
7884             /* [HGM] Moved this to after saving the PGN, just in case */\r
7885             /* engine died and we got here through time loss. In that */\r
7886             /* case we will get a fatal error writing the pipe, which */\r
7887             /* would otherwise lose us the PGN.                       */\r
7888             /* [HGM] crash: not needed anymore, but doesn't hurt;     */\r
7889             /* output during GameEnds should never be fatal anymore   */\r
7890             if (gameMode == MachinePlaysWhite ||\r
7891                 gameMode == MachinePlaysBlack ||\r
7892                 gameMode == TwoMachinesPlay ||\r
7893                 gameMode == IcsPlayingWhite ||\r
7894                 gameMode == IcsPlayingBlack ||\r
7895                 gameMode == BeginningOfGame) {\r
7896                 char buf[MSG_SIZ];\r
7897                 sprintf(buf, "result %s {%s}\n", PGNResult(result),\r
7898                         resultDetails);\r
7899                 if (first.pr != NoProc) {\r
7900                     SendToProgram(buf, &first);\r
7901                 }\r
7902                 if (second.pr != NoProc &&\r
7903                     gameMode == TwoMachinesPlay) {\r
7904                     SendToProgram(buf, &second);\r
7905                 }\r
7906             }\r
7907         }\r
7908 \r
7909         if (appData.icsActive) {\r
7910             if (appData.quietPlay &&\r
7911                 (gameMode == IcsPlayingWhite ||\r
7912                  gameMode == IcsPlayingBlack)) {\r
7913                 SendToICS(ics_prefix);\r
7914                 SendToICS("set shout 1\n");\r
7915             }\r
7916             nextGameMode = IcsIdle;\r
7917             ics_user_moved = FALSE;\r
7918             /* clean up premove.  It's ugly when the game has ended and the\r
7919              * premove highlights are still on the board.\r
7920              */\r
7921             if (gotPremove) {\r
7922               gotPremove = FALSE;\r
7923               ClearPremoveHighlights();\r
7924               DrawPosition(FALSE, boards[currentMove]);\r
7925             }\r
7926             if (whosays == GE_ICS) {\r
7927                 switch (result) {\r
7928                 case WhiteWins:\r
7929                     if (gameMode == IcsPlayingWhite)\r
7930                         PlayIcsWinSound();\r
7931                     else if(gameMode == IcsPlayingBlack)\r
7932                         PlayIcsLossSound();\r
7933                     break;\r
7934                 case BlackWins:\r
7935                     if (gameMode == IcsPlayingBlack)\r
7936                         PlayIcsWinSound();\r
7937                     else if(gameMode == IcsPlayingWhite)\r
7938                         PlayIcsLossSound();\r
7939                     break;\r
7940                 case GameIsDrawn:\r
7941                     PlayIcsDrawSound();\r
7942                     break;\r
7943                 default:\r
7944                     PlayIcsUnfinishedSound();\r
7945                 }\r
7946             }\r
7947         } else if (gameMode == EditGame ||\r
7948                    gameMode == PlayFromGameFile || \r
7949                    gameMode == AnalyzeMode || \r
7950                    gameMode == AnalyzeFile) {\r
7951             nextGameMode = gameMode;\r
7952         } else {\r
7953             nextGameMode = EndOfGame;\r
7954         }\r
7955         pausing = FALSE;\r
7956         ModeHighlight();\r
7957     } else {\r
7958         nextGameMode = gameMode;\r
7959     }\r
7960 \r
7961     if (appData.noChessProgram) {\r
7962         gameMode = nextGameMode;\r
7963         ModeHighlight();\r
7964         endingGame = 0; /* [HGM] crash */\r
7965         return;\r
7966     }\r
7967 \r
7968     if (first.reuse) {\r
7969         /* Put first chess program into idle state */\r
7970         if (first.pr != NoProc &&\r
7971             (gameMode == MachinePlaysWhite ||\r
7972              gameMode == MachinePlaysBlack ||\r
7973              gameMode == TwoMachinesPlay ||\r
7974              gameMode == IcsPlayingWhite ||\r
7975              gameMode == IcsPlayingBlack ||\r
7976              gameMode == BeginningOfGame)) {\r
7977             SendToProgram("force\n", &first);\r
7978             if (first.usePing) {\r
7979               char buf[MSG_SIZ];\r
7980               sprintf(buf, "ping %d\n", ++first.lastPing);\r
7981               SendToProgram(buf, &first);\r
7982             }\r
7983         }\r
7984     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
7985         /* Kill off first chess program */\r
7986         if (first.isr != NULL)\r
7987           RemoveInputSource(first.isr);\r
7988         first.isr = NULL;\r
7989     \r
7990         if (first.pr != NoProc) {\r
7991             ExitAnalyzeMode();\r
7992             DoSleep( appData.delayBeforeQuit );\r
7993             SendToProgram("quit\n", &first);\r
7994             DoSleep( appData.delayAfterQuit );\r
7995             DestroyChildProcess(first.pr, first.useSigterm);\r
7996         }\r
7997         first.pr = NoProc;\r
7998     }\r
7999     if (second.reuse) {\r
8000         /* Put second chess program into idle state */\r
8001         if (second.pr != NoProc &&\r
8002             gameMode == TwoMachinesPlay) {\r
8003             SendToProgram("force\n", &second);\r
8004             if (second.usePing) {\r
8005               char buf[MSG_SIZ];\r
8006               sprintf(buf, "ping %d\n", ++second.lastPing);\r
8007               SendToProgram(buf, &second);\r
8008             }\r
8009         }\r
8010     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
8011         /* Kill off second chess program */\r
8012         if (second.isr != NULL)\r
8013           RemoveInputSource(second.isr);\r
8014         second.isr = NULL;\r
8015     \r
8016         if (second.pr != NoProc) {\r
8017             DoSleep( appData.delayBeforeQuit );\r
8018             SendToProgram("quit\n", &second);\r
8019             DoSleep( appData.delayAfterQuit );\r
8020             DestroyChildProcess(second.pr, second.useSigterm);\r
8021         }\r
8022         second.pr = NoProc;\r
8023     }\r
8024 \r
8025     if (matchMode && gameMode == TwoMachinesPlay) {\r
8026         switch (result) {\r
8027         case WhiteWins:\r
8028           if (first.twoMachinesColor[0] == 'w') {\r
8029             first.matchWins++;\r
8030           } else {\r
8031             second.matchWins++;\r
8032           }\r
8033           break;\r
8034         case BlackWins:\r
8035           if (first.twoMachinesColor[0] == 'b') {\r
8036             first.matchWins++;\r
8037           } else {\r
8038             second.matchWins++;\r
8039           }\r
8040           break;\r
8041         default:\r
8042           break;\r
8043         }\r
8044         if (matchGame < appData.matchGames) {\r
8045             char *tmp;\r
8046             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */\r
8047                 tmp = first.twoMachinesColor;\r
8048                 first.twoMachinesColor = second.twoMachinesColor;\r
8049                 second.twoMachinesColor = tmp;\r
8050             }\r
8051             gameMode = nextGameMode;\r
8052             matchGame++;\r
8053             if(appData.matchPause>10000 || appData.matchPause<10)\r
8054                 appData.matchPause = 10000; /* [HGM] make pause adjustable */\r
8055             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);\r
8056             endingGame = 0; /* [HGM] crash */\r
8057             return;\r
8058         } else {\r
8059             char buf[MSG_SIZ];\r
8060             gameMode = nextGameMode;\r
8061             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),\r
8062                     first.tidy, second.tidy,\r
8063                     first.matchWins, second.matchWins,\r
8064                     appData.matchGames - (first.matchWins + second.matchWins));\r
8065             DisplayFatalError(buf, 0, 0);\r
8066         }\r
8067     }\r
8068     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&\r
8069         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))\r
8070       ExitAnalyzeMode();\r
8071     gameMode = nextGameMode;\r
8072     ModeHighlight();\r
8073     endingGame = 0;  /* [HGM] crash */\r
8074 }\r
8075 \r
8076 /* Assumes program was just initialized (initString sent).\r
8077    Leaves program in force mode. */\r
8078 void\r
8079 FeedMovesToProgram(cps, upto) \r
8080      ChessProgramState *cps;\r
8081      int upto;\r
8082 {\r
8083     int i;\r
8084     \r
8085     if (appData.debugMode)\r
8086       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",\r
8087               startedFromSetupPosition ? "position and " : "",\r
8088               backwardMostMove, upto, cps->which);\r
8089     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];\r
8090         // [HGM] variantswitch: make engine aware of new variant\r
8091         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)\r
8092                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!\r
8093         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));\r
8094         SendToProgram(buf, cps);\r
8095         currentlyInitializedVariant = gameInfo.variant;\r
8096     }\r
8097     SendToProgram("force\n", cps);\r
8098     if (startedFromSetupPosition) {\r
8099         SendBoard(cps, backwardMostMove);\r
8100     if (appData.debugMode) {\r
8101         fprintf(debugFP, "feedMoves\n");\r
8102     }\r
8103     }\r
8104     for (i = backwardMostMove; i < upto; i++) {\r
8105         SendMoveToProgram(i, cps);\r
8106     }\r
8107 }\r
8108 \r
8109 \r
8110 void\r
8111 ResurrectChessProgram()\r
8112 {\r
8113      /* The chess program may have exited.\r
8114         If so, restart it and feed it all the moves made so far. */\r
8115 \r
8116     if (appData.noChessProgram || first.pr != NoProc) return;\r
8117     \r
8118     StartChessProgram(&first);\r
8119     InitChessProgram(&first, FALSE);\r
8120     FeedMovesToProgram(&first, currentMove);\r
8121 \r
8122     if (!first.sendTime) {\r
8123         /* can't tell gnuchess what its clock should read,\r
8124            so we bow to its notion. */\r
8125         ResetClocks();\r
8126         timeRemaining[0][currentMove] = whiteTimeRemaining;\r
8127         timeRemaining[1][currentMove] = blackTimeRemaining;\r
8128     }\r
8129 \r
8130     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||\r
8131                 appData.icsEngineAnalyze) && first.analysisSupport) {\r
8132       SendToProgram("analyze\n", &first);\r
8133       first.analyzing = TRUE;\r
8134     }\r
8135 }\r
8136 \r
8137 /*\r
8138  * Button procedures\r
8139  */\r
8140 void\r
8141 Reset(redraw, init)\r
8142      int redraw, init;\r
8143 {\r
8144     int i;\r
8145 \r
8146     if (appData.debugMode) {\r
8147         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",\r
8148                 redraw, init, gameMode);\r
8149     }\r
8150     pausing = pauseExamInvalid = FALSE;\r
8151     startedFromSetupPosition = blackPlaysFirst = FALSE;\r
8152     firstMove = TRUE;\r
8153     whiteFlag = blackFlag = FALSE;\r
8154     userOfferedDraw = FALSE;\r
8155     hintRequested = bookRequested = FALSE;\r
8156     first.maybeThinking = FALSE;\r
8157     second.maybeThinking = FALSE;\r
8158     first.bookSuspend = FALSE; // [HGM] book\r
8159     second.bookSuspend = FALSE;\r
8160     thinkOutput[0] = NULLCHAR;\r
8161     lastHint[0] = NULLCHAR;\r
8162     ClearGameInfo(&gameInfo);\r
8163     gameInfo.variant = StringToVariant(appData.variant);\r
8164     ics_user_moved = ics_clock_paused = FALSE;\r
8165     ics_getting_history = H_FALSE;\r
8166     ics_gamenum = -1;\r
8167     white_holding[0] = black_holding[0] = NULLCHAR;\r
8168     ClearProgramStats();\r
8169     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode\r
8170     \r
8171     ResetFrontEnd();\r
8172     ClearHighlights();\r
8173     flipView = appData.flipView;\r
8174     ClearPremoveHighlights();\r
8175     gotPremove = FALSE;\r
8176     alarmSounded = FALSE;\r
8177 \r
8178     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
8179     if(appData.serverMovesName != NULL) {\r
8180         /* [HGM] prepare to make moves file for broadcasting */\r
8181         clock_t t = clock();\r
8182         if(serverMoves != NULL) fclose(serverMoves);\r
8183         serverMoves = fopen(appData.serverMovesName, "r");\r
8184         if(serverMoves != NULL) {\r
8185             fclose(serverMoves);\r
8186             /* delay 15 sec before overwriting, so all clients can see end */\r
8187             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);\r
8188         }\r
8189         serverMoves = fopen(appData.serverMovesName, "w");\r
8190     }\r
8191 \r
8192     ExitAnalyzeMode();\r
8193     gameMode = BeginningOfGame;\r
8194     ModeHighlight();\r
8195     if(appData.icsActive) gameInfo.variant = VariantNormal;\r
8196     InitPosition(redraw);\r
8197     for (i = 0; i < MAX_MOVES; i++) {\r
8198         if (commentList[i] != NULL) {\r
8199             free(commentList[i]);\r
8200             commentList[i] = NULL;\r
8201         }\r
8202     }\r
8203     ResetClocks();\r
8204     timeRemaining[0][0] = whiteTimeRemaining;\r
8205     timeRemaining[1][0] = blackTimeRemaining;\r
8206     if (first.pr == NULL) {\r
8207         StartChessProgram(&first);\r
8208     }\r
8209     if (init) {\r
8210             InitChessProgram(&first, startedFromSetupPosition);\r
8211     }\r
8212     DisplayTitle("");\r
8213     DisplayMessage("", "");\r
8214     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
8215 }\r
8216 \r
8217 void\r
8218 AutoPlayGameLoop()\r
8219 {\r
8220     for (;;) {\r
8221         if (!AutoPlayOneMove())\r
8222           return;\r
8223         if (matchMode || appData.timeDelay == 0)\r
8224           continue;\r
8225         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)\r
8226           return;\r
8227         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));\r
8228         break;\r
8229     }\r
8230 }\r
8231 \r
8232 \r
8233 int\r
8234 AutoPlayOneMove()\r
8235 {\r
8236     int fromX, fromY, toX, toY;\r
8237 \r
8238     if (appData.debugMode) {\r
8239       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);\r
8240     }\r
8241 \r
8242     if (gameMode != PlayFromGameFile)\r
8243       return FALSE;\r
8244 \r
8245     if (currentMove >= forwardMostMove) {\r
8246       gameMode = EditGame;\r
8247       ModeHighlight();\r
8248 \r
8249       /* [AS] Clear current move marker at the end of a game */\r
8250       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */\r
8251 \r
8252       return FALSE;\r
8253     }\r
8254     \r
8255     toX = moveList[currentMove][2] - AAA;\r
8256     toY = moveList[currentMove][3] - ONE;\r
8257 \r
8258     if (moveList[currentMove][1] == '@') {\r
8259         if (appData.highlightLastMove) {\r
8260             SetHighlights(-1, -1, toX, toY);\r
8261         }\r
8262     } else {\r
8263         fromX = moveList[currentMove][0] - AAA;\r
8264         fromY = moveList[currentMove][1] - ONE;\r
8265 \r
8266         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */\r
8267 \r
8268         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
8269 \r
8270         if (appData.highlightLastMove) {\r
8271             SetHighlights(fromX, fromY, toX, toY);\r
8272         }\r
8273     }\r
8274     DisplayMove(currentMove);\r
8275     SendMoveToProgram(currentMove++, &first);\r
8276     DisplayBothClocks();\r
8277     DrawPosition(FALSE, boards[currentMove]);\r
8278     // [HGM] PV info: always display, routine tests if empty\r
8279     DisplayComment(currentMove - 1, commentList[currentMove]);\r
8280     return TRUE;\r
8281 }\r
8282 \r
8283 \r
8284 int\r
8285 LoadGameOneMove(readAhead)\r
8286      ChessMove readAhead;\r
8287 {\r
8288     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;\r
8289     char promoChar = NULLCHAR;\r
8290     ChessMove moveType;\r
8291     char move[MSG_SIZ];\r
8292     char *p, *q;\r
8293     \r
8294     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && \r
8295         gameMode != AnalyzeMode && gameMode != Training) {\r
8296         gameFileFP = NULL;\r
8297         return FALSE;\r
8298     }\r
8299     \r
8300     yyboardindex = forwardMostMove;\r
8301     if (readAhead != (ChessMove)0) {\r
8302       moveType = readAhead;\r
8303     } else {\r
8304       if (gameFileFP == NULL)\r
8305           return FALSE;\r
8306       moveType = (ChessMove) yylex();\r
8307     }\r
8308     \r
8309     done = FALSE;\r
8310     switch (moveType) {\r
8311       case Comment:\r
8312         if (appData.debugMode) \r
8313           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
8314         p = yy_text;\r
8315         if (*p == '{' || *p == '[' || *p == '(') {\r
8316             p[strlen(p) - 1] = NULLCHAR;\r
8317             p++;\r
8318         }\r
8319 \r
8320         /* append the comment but don't display it */\r
8321         while (*p == '\n') p++;\r
8322         AppendComment(currentMove, p);\r
8323         return TRUE;\r
8324 \r
8325       case WhiteCapturesEnPassant:\r
8326       case BlackCapturesEnPassant:\r
8327       case WhitePromotionChancellor:\r
8328       case BlackPromotionChancellor:\r
8329       case WhitePromotionArchbishop:\r
8330       case BlackPromotionArchbishop:\r
8331       case WhitePromotionCentaur:\r
8332       case BlackPromotionCentaur:\r
8333       case WhitePromotionQueen:\r
8334       case BlackPromotionQueen:\r
8335       case WhitePromotionRook:\r
8336       case BlackPromotionRook:\r
8337       case WhitePromotionBishop:\r
8338       case BlackPromotionBishop:\r
8339       case WhitePromotionKnight:\r
8340       case BlackPromotionKnight:\r
8341       case WhitePromotionKing:\r
8342       case BlackPromotionKing:\r
8343       case NormalMove:\r
8344       case WhiteKingSideCastle:\r
8345       case WhiteQueenSideCastle:\r
8346       case BlackKingSideCastle:\r
8347       case BlackQueenSideCastle:\r
8348       case WhiteKingSideCastleWild:\r
8349       case WhiteQueenSideCastleWild:\r
8350       case BlackKingSideCastleWild:\r
8351       case BlackQueenSideCastleWild:\r
8352       /* PUSH Fabien */\r
8353       case WhiteHSideCastleFR:\r
8354       case WhiteASideCastleFR:\r
8355       case BlackHSideCastleFR:\r
8356       case BlackASideCastleFR:\r
8357       /* POP Fabien */\r
8358         if (appData.debugMode)\r
8359           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
8360         fromX = currentMoveString[0] - AAA;\r
8361         fromY = currentMoveString[1] - ONE;\r
8362         toX = currentMoveString[2] - AAA;\r
8363         toY = currentMoveString[3] - ONE;\r
8364         promoChar = currentMoveString[4];\r
8365         break;\r
8366 \r
8367       case WhiteDrop:\r
8368       case BlackDrop:\r
8369         if (appData.debugMode)\r
8370           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
8371         fromX = moveType == WhiteDrop ?\r
8372           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
8373         (int) CharToPiece(ToLower(currentMoveString[0]));\r
8374         fromY = DROP_RANK;\r
8375         toX = currentMoveString[2] - AAA;\r
8376         toY = currentMoveString[3] - ONE;\r
8377         break;\r
8378 \r
8379       case WhiteWins:\r
8380       case BlackWins:\r
8381       case GameIsDrawn:\r
8382       case GameUnfinished:\r
8383         if (appData.debugMode)\r
8384           fprintf(debugFP, "Parsed game end: %s\n", yy_text);\r
8385         p = strchr(yy_text, '{');\r
8386         if (p == NULL) p = strchr(yy_text, '(');\r
8387         if (p == NULL) {\r
8388             p = yy_text;\r
8389             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
8390         } else {\r
8391             q = strchr(p, *p == '{' ? '}' : ')');\r
8392             if (q != NULL) *q = NULLCHAR;\r
8393             p++;\r
8394         }\r
8395         GameEnds(moveType, p, GE_FILE);\r
8396         done = TRUE;\r
8397         if (cmailMsgLoaded) {\r
8398             ClearHighlights();\r
8399             flipView = WhiteOnMove(currentMove);\r
8400             if (moveType == GameUnfinished) flipView = !flipView;\r
8401             if (appData.debugMode)\r
8402               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;\r
8403         }\r
8404         break;\r
8405 \r
8406       case (ChessMove) 0:       /* end of file */\r
8407         if (appData.debugMode)\r
8408           fprintf(debugFP, "Parser hit end of file\n");\r
8409         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8410                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8411           case MT_NONE:\r
8412           case MT_CHECK:\r
8413             break;\r
8414           case MT_CHECKMATE:\r
8415             if (WhiteOnMove(currentMove)) {\r
8416                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
8417             } else {\r
8418                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
8419             }\r
8420             break;\r
8421           case MT_STALEMATE:\r
8422             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
8423             break;\r
8424         }\r
8425         done = TRUE;\r
8426         break;\r
8427 \r
8428       case MoveNumberOne:\r
8429         if (lastLoadGameStart == GNUChessGame) {\r
8430             /* GNUChessGames have numbers, but they aren't move numbers */\r
8431             if (appData.debugMode)\r
8432               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
8433                       yy_text, (int) moveType);\r
8434             return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
8435         }\r
8436         /* else fall thru */\r
8437 \r
8438       case XBoardGame:\r
8439       case GNUChessGame:\r
8440       case PGNTag:\r
8441         /* Reached start of next game in file */\r
8442         if (appData.debugMode)\r
8443           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);\r
8444         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8445                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8446           case MT_NONE:\r
8447           case MT_CHECK:\r
8448             break;\r
8449           case MT_CHECKMATE:\r
8450             if (WhiteOnMove(currentMove)) {\r
8451                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
8452             } else {\r
8453                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
8454             }\r
8455             break;\r
8456           case MT_STALEMATE:\r
8457             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
8458             break;\r
8459         }\r
8460         done = TRUE;\r
8461         break;\r
8462 \r
8463       case PositionDiagram:     /* should not happen; ignore */\r
8464       case ElapsedTime:         /* ignore */\r
8465       case NAG:                 /* ignore */\r
8466         if (appData.debugMode)\r
8467           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
8468                   yy_text, (int) moveType);\r
8469         return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
8470 \r
8471       case IllegalMove:\r
8472         if (appData.testLegality) {\r
8473             if (appData.debugMode)\r
8474               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);\r
8475             sprintf(move, _("Illegal move: %d.%s%s"),\r
8476                     (forwardMostMove / 2) + 1,\r
8477                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8478             DisplayError(move, 0);\r
8479             done = TRUE;\r
8480         } else {\r
8481             if (appData.debugMode)\r
8482               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",\r
8483                       yy_text, currentMoveString);\r
8484             fromX = currentMoveString[0] - AAA;\r
8485             fromY = currentMoveString[1] - ONE;\r
8486             toX = currentMoveString[2] - AAA;\r
8487             toY = currentMoveString[3] - ONE;\r
8488             promoChar = currentMoveString[4];\r
8489         }\r
8490         break;\r
8491 \r
8492       case AmbiguousMove:\r
8493         if (appData.debugMode)\r
8494           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);\r
8495         sprintf(move, _("Ambiguous move: %d.%s%s"),\r
8496                 (forwardMostMove / 2) + 1,\r
8497                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8498         DisplayError(move, 0);\r
8499         done = TRUE;\r
8500         break;\r
8501 \r
8502       default:\r
8503       case ImpossibleMove:\r
8504         if (appData.debugMode)\r
8505           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);\r
8506         sprintf(move, _("Illegal move: %d.%s%s"),\r
8507                 (forwardMostMove / 2) + 1,\r
8508                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8509         DisplayError(move, 0);\r
8510         done = TRUE;\r
8511         break;\r
8512     }\r
8513 \r
8514     if (done) {\r
8515         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {\r
8516             DrawPosition(FALSE, boards[currentMove]);\r
8517             DisplayBothClocks();\r
8518             if (!appData.matchMode) // [HGM] PV info: routine tests if empty\r
8519               DisplayComment(currentMove - 1, commentList[currentMove]);\r
8520         }\r
8521         (void) StopLoadGameTimer();\r
8522         gameFileFP = NULL;\r
8523         cmailOldMove = forwardMostMove;\r
8524         return FALSE;\r
8525     } else {\r
8526         /* currentMoveString is set as a side-effect of yylex */\r
8527         strcat(currentMoveString, "\n");\r
8528         strcpy(moveList[forwardMostMove], currentMoveString);\r
8529         \r
8530         thinkOutput[0] = NULLCHAR;\r
8531         MakeMove(fromX, fromY, toX, toY, promoChar);\r
8532         currentMove = forwardMostMove;\r
8533         return TRUE;\r
8534     }\r
8535 }\r
8536 \r
8537 /* Load the nth game from the given file */\r
8538 int\r
8539 LoadGameFromFile(filename, n, title, useList)\r
8540      char *filename;\r
8541      int n;\r
8542      char *title;\r
8543      /*Boolean*/ int useList;\r
8544 {\r
8545     FILE *f;\r
8546     char buf[MSG_SIZ];\r
8547 \r
8548     if (strcmp(filename, "-") == 0) {\r
8549         f = stdin;\r
8550         title = "stdin";\r
8551     } else {\r
8552         f = fopen(filename, "rb");\r
8553         if (f == NULL) {\r
8554             sprintf(buf, _("Can't open \"%s\""), filename);\r
8555             DisplayError(buf, errno);\r
8556             return FALSE;\r
8557         }\r
8558     }\r
8559     if (fseek(f, 0, 0) == -1) {\r
8560         /* f is not seekable; probably a pipe */\r
8561         useList = FALSE;\r
8562     }\r
8563     if (useList && n == 0) {\r
8564         int error = GameListBuild(f);\r
8565         if (error) {\r
8566             DisplayError(_("Cannot build game list"), error);\r
8567         } else if (!ListEmpty(&gameList) &&\r
8568                    ((ListGame *) gameList.tailPred)->number > 1) {\r
8569             GameListPopUp(f, title);\r
8570             return TRUE;\r
8571         }\r
8572         GameListDestroy();\r
8573         n = 1;\r
8574     }\r
8575     if (n == 0) n = 1;\r
8576     return LoadGame(f, n, title, FALSE);\r
8577 }\r
8578 \r
8579 \r
8580 void\r
8581 MakeRegisteredMove()\r
8582 {\r
8583     int fromX, fromY, toX, toY;\r
8584     char promoChar;\r
8585     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8586         switch (cmailMoveType[lastLoadGameNumber - 1]) {\r
8587           case CMAIL_MOVE:\r
8588           case CMAIL_DRAW:\r
8589             if (appData.debugMode)\r
8590               fprintf(debugFP, "Restoring %s for game %d\n",\r
8591                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
8592     \r
8593             thinkOutput[0] = NULLCHAR;\r
8594             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);\r
8595             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;\r
8596             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;\r
8597             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;\r
8598             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;\r
8599             promoChar = cmailMove[lastLoadGameNumber - 1][4];\r
8600             MakeMove(fromX, fromY, toX, toY, promoChar);\r
8601             ShowMove(fromX, fromY, toX, toY);\r
8602               \r
8603             switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8604                              EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8605               case MT_NONE:\r
8606               case MT_CHECK:\r
8607                 break;\r
8608                 \r
8609               case MT_CHECKMATE:\r
8610                 if (WhiteOnMove(currentMove)) {\r
8611                     GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
8612                 } else {\r
8613                     GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
8614                 }\r
8615                 break;\r
8616                 \r
8617               case MT_STALEMATE:\r
8618                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
8619                 break;\r
8620             }\r
8621 \r
8622             break;\r
8623             \r
8624           case CMAIL_RESIGN:\r
8625             if (WhiteOnMove(currentMove)) {\r
8626                 GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
8627             } else {\r
8628                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
8629             }\r
8630             break;\r
8631             \r
8632           case CMAIL_ACCEPT:\r
8633             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
8634             break;\r
8635               \r
8636           default:\r
8637             break;\r
8638         }\r
8639     }\r
8640 \r
8641     return;\r
8642 }\r
8643 \r
8644 /* Wrapper around LoadGame for use when a Cmail message is loaded */\r
8645 int\r
8646 CmailLoadGame(f, gameNumber, title, useList)\r
8647      FILE *f;\r
8648      int gameNumber;\r
8649      char *title;\r
8650      int useList;\r
8651 {\r
8652     int retVal;\r
8653 \r
8654     if (gameNumber > nCmailGames) {\r
8655         DisplayError(_("No more games in this message"), 0);\r
8656         return FALSE;\r
8657     }\r
8658     if (f == lastLoadGameFP) {\r
8659         int offset = gameNumber - lastLoadGameNumber;\r
8660         if (offset == 0) {\r
8661             cmailMsg[0] = NULLCHAR;\r
8662             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8663                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
8664                 nCmailMovesRegistered--;\r
8665             }\r
8666             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
8667             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {\r
8668                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;\r
8669             }\r
8670         } else {\r
8671             if (! RegisterMove()) return FALSE;\r
8672         }\r
8673     }\r
8674 \r
8675     retVal = LoadGame(f, gameNumber, title, useList);\r
8676 \r
8677     /* Make move registered during previous look at this game, if any */\r
8678     MakeRegisteredMove();\r
8679 \r
8680     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {\r
8681         commentList[currentMove]\r
8682           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);\r
8683         DisplayComment(currentMove - 1, commentList[currentMove]);\r
8684     }\r
8685 \r
8686     return retVal;\r
8687 }\r
8688 \r
8689 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */\r
8690 int\r
8691 ReloadGame(offset)\r
8692      int offset;\r
8693 {\r
8694     int gameNumber = lastLoadGameNumber + offset;\r
8695     if (lastLoadGameFP == NULL) {\r
8696         DisplayError(_("No game has been loaded yet"), 0);\r
8697         return FALSE;\r
8698     }\r
8699     if (gameNumber <= 0) {\r
8700         DisplayError(_("Can't back up any further"), 0);\r
8701         return FALSE;\r
8702     }\r
8703     if (cmailMsgLoaded) {\r
8704         return CmailLoadGame(lastLoadGameFP, gameNumber,\r
8705                              lastLoadGameTitle, lastLoadGameUseList);\r
8706     } else {\r
8707         return LoadGame(lastLoadGameFP, gameNumber,\r
8708                         lastLoadGameTitle, lastLoadGameUseList);\r
8709     }\r
8710 }\r
8711 \r
8712 \r
8713 \r
8714 /* Load the nth game from open file f */\r
8715 int\r
8716 LoadGame(f, gameNumber, title, useList)\r
8717      FILE *f;\r
8718      int gameNumber;\r
8719      char *title;\r
8720      int useList;\r
8721 {\r
8722     ChessMove cm;\r
8723     char buf[MSG_SIZ];\r
8724     int gn = gameNumber;\r
8725     ListGame *lg = NULL;\r
8726     int numPGNTags = 0;\r
8727     int err;\r
8728     GameMode oldGameMode;\r
8729     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */\r
8730 \r
8731     if (appData.debugMode) \r
8732         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);\r
8733 \r
8734     if (gameMode == Training )\r
8735         SetTrainingModeOff();\r
8736 \r
8737     oldGameMode = gameMode;\r
8738     if (gameMode != BeginningOfGame) {\r
8739       Reset(FALSE, TRUE);\r
8740     }\r
8741 \r
8742     gameFileFP = f;\r
8743     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {\r
8744         fclose(lastLoadGameFP);\r
8745     }\r
8746 \r
8747     if (useList) {\r
8748         lg = (ListGame *) ListElem(&gameList, gameNumber-1);\r
8749         \r
8750         if (lg) {\r
8751             fseek(f, lg->offset, 0);\r
8752             GameListHighlight(gameNumber);\r
8753             gn = 1;\r
8754         }\r
8755         else {\r
8756             DisplayError(_("Game number out of range"), 0);\r
8757             return FALSE;\r
8758         }\r
8759     } else {\r
8760         GameListDestroy();\r
8761         if (fseek(f, 0, 0) == -1) {\r
8762             if (f == lastLoadGameFP ?\r
8763                 gameNumber == lastLoadGameNumber + 1 :\r
8764                 gameNumber == 1) {\r
8765                 gn = 1;\r
8766             } else {\r
8767                 DisplayError(_("Can't seek on game file"), 0);\r
8768                 return FALSE;\r
8769             }\r
8770         }\r
8771     }\r
8772     lastLoadGameFP = f;\r
8773     lastLoadGameNumber = gameNumber;\r
8774     strcpy(lastLoadGameTitle, title);\r
8775     lastLoadGameUseList = useList;\r
8776 \r
8777     yynewfile(f);\r
8778 \r
8779     if (lg && lg->gameInfo.white && lg->gameInfo.black) {\r
8780         sprintf(buf, "%s vs. %s", lg->gameInfo.white,\r
8781                 lg->gameInfo.black);\r
8782             DisplayTitle(buf);\r
8783     } else if (*title != NULLCHAR) {\r
8784         if (gameNumber > 1) {\r
8785             sprintf(buf, "%s %d", title, gameNumber);\r
8786             DisplayTitle(buf);\r
8787         } else {\r
8788             DisplayTitle(title);\r
8789         }\r
8790     }\r
8791 \r
8792     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {\r
8793         gameMode = PlayFromGameFile;\r
8794         ModeHighlight();\r
8795     }\r
8796 \r
8797     currentMove = forwardMostMove = backwardMostMove = 0;\r
8798     CopyBoard(boards[0], initialPosition);\r
8799     StopClocks();\r
8800 \r
8801     /*\r
8802      * Skip the first gn-1 games in the file.\r
8803      * Also skip over anything that precedes an identifiable \r
8804      * start of game marker, to avoid being confused by \r
8805      * garbage at the start of the file.  Currently \r
8806      * recognized start of game markers are the move number "1",\r
8807      * the pattern "gnuchess .* game", the pattern\r
8808      * "^[#;%] [^ ]* game file", and a PGN tag block.  \r
8809      * A game that starts with one of the latter two patterns\r
8810      * will also have a move number 1, possibly\r
8811      * following a position diagram.\r
8812      * 5-4-02: Let's try being more lenient and allowing a game to\r
8813      * start with an unnumbered move.  Does that break anything?\r
8814      */\r
8815     cm = lastLoadGameStart = (ChessMove) 0;\r
8816     while (gn > 0) {\r
8817         yyboardindex = forwardMostMove;\r
8818         cm = (ChessMove) yylex();\r
8819         switch (cm) {\r
8820           case (ChessMove) 0:\r
8821             if (cmailMsgLoaded) {\r
8822                 nCmailGames = CMAIL_MAX_GAMES - gn;\r
8823             } else {\r
8824                 Reset(TRUE, TRUE);\r
8825                 DisplayError(_("Game not found in file"), 0);\r
8826             }\r
8827             return FALSE;\r
8828 \r
8829           case GNUChessGame:\r
8830           case XBoardGame:\r
8831             gn--;\r
8832             lastLoadGameStart = cm;\r
8833             break;\r
8834             \r
8835           case MoveNumberOne:\r
8836             switch (lastLoadGameStart) {\r
8837               case GNUChessGame:\r
8838               case XBoardGame:\r
8839               case PGNTag:\r
8840                 break;\r
8841               case MoveNumberOne:\r
8842               case (ChessMove) 0:\r
8843                 gn--;           /* count this game */\r
8844                 lastLoadGameStart = cm;\r
8845                 break;\r
8846               default:\r
8847                 /* impossible */\r
8848                 break;\r
8849             }\r
8850             break;\r
8851 \r
8852           case PGNTag:\r
8853             switch (lastLoadGameStart) {\r
8854               case GNUChessGame:\r
8855               case PGNTag:\r
8856               case MoveNumberOne:\r
8857               case (ChessMove) 0:\r
8858                 gn--;           /* count this game */\r
8859                 lastLoadGameStart = cm;\r
8860                 break;\r
8861               case XBoardGame:\r
8862                 lastLoadGameStart = cm; /* game counted already */\r
8863                 break;\r
8864               default:\r
8865                 /* impossible */\r
8866                 break;\r
8867             }\r
8868             if (gn > 0) {\r
8869                 do {\r
8870                     yyboardindex = forwardMostMove;\r
8871                     cm = (ChessMove) yylex();\r
8872                 } while (cm == PGNTag || cm == Comment);\r
8873             }\r
8874             break;\r
8875 \r
8876           case WhiteWins:\r
8877           case BlackWins:\r
8878           case GameIsDrawn:\r
8879             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {\r
8880                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]\r
8881                     != CMAIL_OLD_RESULT) {\r
8882                     nCmailResults ++ ;\r
8883                     cmailResult[  CMAIL_MAX_GAMES\r
8884                                 - gn - 1] = CMAIL_OLD_RESULT;\r
8885                 }\r
8886             }\r
8887             break;\r
8888 \r
8889           case NormalMove:\r
8890             /* Only a NormalMove can be at the start of a game\r
8891              * without a position diagram. */\r
8892             if (lastLoadGameStart == (ChessMove) 0) {\r
8893               gn--;\r
8894               lastLoadGameStart = MoveNumberOne;\r
8895             }\r
8896             break;\r
8897 \r
8898           default:\r
8899             break;\r
8900         }\r
8901     }\r
8902     \r
8903     if (appData.debugMode)\r
8904       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);\r
8905 \r
8906     if (cm == XBoardGame) {\r
8907         /* Skip any header junk before position diagram and/or move 1 */\r
8908         for (;;) {\r
8909             yyboardindex = forwardMostMove;\r
8910             cm = (ChessMove) yylex();\r
8911 \r
8912             if (cm == (ChessMove) 0 ||\r
8913                 cm == GNUChessGame || cm == XBoardGame) {\r
8914                 /* Empty game; pretend end-of-file and handle later */\r
8915                 cm = (ChessMove) 0;\r
8916                 break;\r
8917             }\r
8918 \r
8919             if (cm == MoveNumberOne || cm == PositionDiagram ||\r
8920                 cm == PGNTag || cm == Comment)\r
8921               break;\r
8922         }\r
8923     } else if (cm == GNUChessGame) {\r
8924         if (gameInfo.event != NULL) {\r
8925             free(gameInfo.event);\r
8926         }\r
8927         gameInfo.event = StrSave(yy_text);\r
8928     }   \r
8929 \r
8930     startedFromSetupPosition = FALSE;\r
8931     while (cm == PGNTag) {\r
8932         if (appData.debugMode) \r
8933           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);\r
8934         err = ParsePGNTag(yy_text, &gameInfo);\r
8935         if (!err) numPGNTags++;\r
8936 \r
8937         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */\r
8938         if(gameInfo.variant != oldVariant) {\r
8939             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */\r
8940             InitPosition(TRUE);\r
8941             oldVariant = gameInfo.variant;\r
8942             if (appData.debugMode) \r
8943               fprintf(debugFP, "New variant %d\n", (int) oldVariant);\r
8944         }\r
8945 \r
8946 \r
8947         if (gameInfo.fen != NULL) {\r
8948           Board initial_position;\r
8949           startedFromSetupPosition = TRUE;\r
8950           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {\r
8951             Reset(TRUE, TRUE);\r
8952             DisplayError(_("Bad FEN position in file"), 0);\r
8953             return FALSE;\r
8954           }\r
8955           CopyBoard(boards[0], initial_position);\r
8956           if (blackPlaysFirst) {\r
8957             currentMove = forwardMostMove = backwardMostMove = 1;\r
8958             CopyBoard(boards[1], initial_position);\r
8959             strcpy(moveList[0], "");\r
8960             strcpy(parseList[0], "");\r
8961             timeRemaining[0][1] = whiteTimeRemaining;\r
8962             timeRemaining[1][1] = blackTimeRemaining;\r
8963             if (commentList[0] != NULL) {\r
8964               commentList[1] = commentList[0];\r
8965               commentList[0] = NULL;\r
8966             }\r
8967           } else {\r
8968             currentMove = forwardMostMove = backwardMostMove = 0;\r
8969           }\r
8970           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */\r
8971           {   int i;\r
8972               initialRulePlies = FENrulePlies;\r
8973               epStatus[forwardMostMove] = FENepStatus;\r
8974               for( i=0; i< nrCastlingRights; i++ )\r
8975                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];\r
8976           }\r
8977           yyboardindex = forwardMostMove;\r
8978           free(gameInfo.fen);\r
8979           gameInfo.fen = NULL;\r
8980         }\r
8981 \r
8982         yyboardindex = forwardMostMove;\r
8983         cm = (ChessMove) yylex();\r
8984 \r
8985         /* Handle comments interspersed among the tags */\r
8986         while (cm == Comment) {\r
8987             char *p;\r
8988             if (appData.debugMode) \r
8989               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
8990             p = yy_text;\r
8991             if (*p == '{' || *p == '[' || *p == '(') {\r
8992                 p[strlen(p) - 1] = NULLCHAR;\r
8993                 p++;\r
8994             }\r
8995             while (*p == '\n') p++;\r
8996             AppendComment(currentMove, p);\r
8997             yyboardindex = forwardMostMove;\r
8998             cm = (ChessMove) yylex();\r
8999         }\r
9000     }\r
9001 \r
9002     /* don't rely on existence of Event tag since if game was\r
9003      * pasted from clipboard the Event tag may not exist\r
9004      */\r
9005     if (numPGNTags > 0){\r
9006         char *tags;\r
9007         if (gameInfo.variant == VariantNormal) {\r
9008           gameInfo.variant = StringToVariant(gameInfo.event);\r
9009         }\r
9010         if (!matchMode) {\r
9011           if( appData.autoDisplayTags ) {\r
9012             tags = PGNTags(&gameInfo);\r
9013             TagsPopUp(tags, CmailMsg());\r
9014             free(tags);\r
9015           }\r
9016         }\r
9017     } else {\r
9018         /* Make something up, but don't display it now */\r
9019         SetGameInfo();\r
9020         TagsPopDown();\r
9021     }\r
9022 \r
9023     if (cm == PositionDiagram) {\r
9024         int i, j;\r
9025         char *p;\r
9026         Board initial_position;\r
9027 \r
9028         if (appData.debugMode)\r
9029           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);\r
9030 \r
9031         if (!startedFromSetupPosition) {\r
9032             p = yy_text;\r
9033             for (i = BOARD_HEIGHT - 1; i >= 0; i--)\r
9034               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)\r
9035                 switch (*p) {\r
9036                   case '[':\r
9037                   case '-':\r
9038                   case ' ':\r
9039                   case '\t':\r
9040                   case '\n':\r
9041                   case '\r':\r
9042                     break;\r
9043                   default:\r
9044                     initial_position[i][j++] = CharToPiece(*p);\r
9045                     break;\r
9046                 }\r
9047             while (*p == ' ' || *p == '\t' ||\r
9048                    *p == '\n' || *p == '\r') p++;\r
9049         \r
9050             if (strncmp(p, "black", strlen("black"))==0)\r
9051               blackPlaysFirst = TRUE;\r
9052             else\r
9053               blackPlaysFirst = FALSE;\r
9054             startedFromSetupPosition = TRUE;\r
9055         \r
9056             CopyBoard(boards[0], initial_position);\r
9057             if (blackPlaysFirst) {\r
9058                 currentMove = forwardMostMove = backwardMostMove = 1;\r
9059                 CopyBoard(boards[1], initial_position);\r
9060                 strcpy(moveList[0], "");\r
9061                 strcpy(parseList[0], "");\r
9062                 timeRemaining[0][1] = whiteTimeRemaining;\r
9063                 timeRemaining[1][1] = blackTimeRemaining;\r
9064                 if (commentList[0] != NULL) {\r
9065                     commentList[1] = commentList[0];\r
9066                     commentList[0] = NULL;\r
9067                 }\r
9068             } else {\r
9069                 currentMove = forwardMostMove = backwardMostMove = 0;\r
9070             }\r
9071         }\r
9072         yyboardindex = forwardMostMove;\r
9073         cm = (ChessMove) yylex();\r
9074     }\r
9075 \r
9076     if (first.pr == NoProc) {\r
9077         StartChessProgram(&first);\r
9078     }\r
9079     InitChessProgram(&first, FALSE);\r
9080     SendToProgram("force\n", &first);\r
9081     if (startedFromSetupPosition) {\r
9082         SendBoard(&first, forwardMostMove);\r
9083     if (appData.debugMode) {\r
9084         fprintf(debugFP, "Load Game\n");\r
9085     }\r
9086         DisplayBothClocks();\r
9087     }      \r
9088 \r
9089     /* [HGM] server: flag to write setup moves in broadcast file as one */\r
9090     loadFlag = appData.suppressLoadMoves;\r
9091 \r
9092     while (cm == Comment) {\r
9093         char *p;\r
9094         if (appData.debugMode) \r
9095           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
9096         p = yy_text;\r
9097         if (*p == '{' || *p == '[' || *p == '(') {\r
9098             p[strlen(p) - 1] = NULLCHAR;\r
9099             p++;\r
9100         }\r
9101         while (*p == '\n') p++;\r
9102         AppendComment(currentMove, p);\r
9103         yyboardindex = forwardMostMove;\r
9104         cm = (ChessMove) yylex();\r
9105     }\r
9106 \r
9107     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||\r
9108         cm == WhiteWins || cm == BlackWins ||\r
9109         cm == GameIsDrawn || cm == GameUnfinished) {\r
9110         DisplayMessage("", _("No moves in game"));\r
9111         if (cmailMsgLoaded) {\r
9112             if (appData.debugMode)\r
9113               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);\r
9114             ClearHighlights();\r
9115             flipView = FALSE;\r
9116         }\r
9117         DrawPosition(FALSE, boards[currentMove]);\r
9118         DisplayBothClocks();\r
9119         gameMode = EditGame;\r
9120         ModeHighlight();\r
9121         gameFileFP = NULL;\r
9122         cmailOldMove = 0;\r
9123         return TRUE;\r
9124     }\r
9125 \r
9126     // [HGM] PV info: routine tests if comment empty\r
9127     if (!matchMode && (pausing || appData.timeDelay != 0)) {\r
9128         DisplayComment(currentMove - 1, commentList[currentMove]);\r
9129     }\r
9130     if (!matchMode && appData.timeDelay != 0) \r
9131       DrawPosition(FALSE, boards[currentMove]);\r
9132 \r
9133     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {\r
9134       programStats.ok_to_send = 1;\r
9135     }\r
9136 \r
9137     /* if the first token after the PGN tags is a move\r
9138      * and not move number 1, retrieve it from the parser \r
9139      */\r
9140     if (cm != MoveNumberOne)\r
9141         LoadGameOneMove(cm);\r
9142 \r
9143     /* load the remaining moves from the file */\r
9144     while (LoadGameOneMove((ChessMove)0)) {\r
9145       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
9146       timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
9147     }\r
9148 \r
9149     /* rewind to the start of the game */\r
9150     currentMove = backwardMostMove;\r
9151 \r
9152     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
9153 \r
9154     if (oldGameMode == AnalyzeFile ||\r
9155         oldGameMode == AnalyzeMode) {\r
9156       AnalyzeFileEvent();\r
9157     }\r
9158 \r
9159     if (matchMode || appData.timeDelay == 0) {\r
9160       ToEndEvent();\r
9161       gameMode = EditGame;\r
9162       ModeHighlight();\r
9163     } else if (appData.timeDelay > 0) {\r
9164       AutoPlayGameLoop();\r
9165     }\r
9166 \r
9167     if (appData.debugMode) \r
9168         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);\r
9169 \r
9170     loadFlag = 0; /* [HGM] true game starts */\r
9171     return TRUE;\r
9172 }\r
9173 \r
9174 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */\r
9175 int\r
9176 ReloadPosition(offset)\r
9177      int offset;\r
9178 {\r
9179     int positionNumber = lastLoadPositionNumber + offset;\r
9180     if (lastLoadPositionFP == NULL) {\r
9181         DisplayError(_("No position has been loaded yet"), 0);\r
9182         return FALSE;\r
9183     }\r
9184     if (positionNumber <= 0) {\r
9185         DisplayError(_("Can't back up any further"), 0);\r
9186         return FALSE;\r
9187     }\r
9188     return LoadPosition(lastLoadPositionFP, positionNumber,\r
9189                         lastLoadPositionTitle);\r
9190 }\r
9191 \r
9192 /* Load the nth position from the given file */\r
9193 int\r
9194 LoadPositionFromFile(filename, n, title)\r
9195      char *filename;\r
9196      int n;\r
9197      char *title;\r
9198 {\r
9199     FILE *f;\r
9200     char buf[MSG_SIZ];\r
9201 \r
9202     if (strcmp(filename, "-") == 0) {\r
9203         return LoadPosition(stdin, n, "stdin");\r
9204     } else {\r
9205         f = fopen(filename, "rb");\r
9206         if (f == NULL) {\r
9207             sprintf(buf, _("Can't open \"%s\""), filename);\r
9208             DisplayError(buf, errno);\r
9209             return FALSE;\r
9210         } else {\r
9211             return LoadPosition(f, n, title);\r
9212         }\r
9213     }\r
9214 }\r
9215 \r
9216 /* Load the nth position from the given open file, and close it */\r
9217 int\r
9218 LoadPosition(f, positionNumber, title)\r
9219      FILE *f;\r
9220      int positionNumber;\r
9221      char *title;\r
9222 {\r
9223     char *p, line[MSG_SIZ];\r
9224     Board initial_position;\r
9225     int i, j, fenMode, pn;\r
9226     \r
9227     if (gameMode == Training )\r
9228         SetTrainingModeOff();\r
9229 \r
9230     if (gameMode != BeginningOfGame) {\r
9231         Reset(FALSE, TRUE);\r
9232     }\r
9233     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {\r
9234         fclose(lastLoadPositionFP);\r
9235     }\r
9236     if (positionNumber == 0) positionNumber = 1;\r
9237     lastLoadPositionFP = f;\r
9238     lastLoadPositionNumber = positionNumber;\r
9239     strcpy(lastLoadPositionTitle, title);\r
9240     if (first.pr == NoProc) {\r
9241       StartChessProgram(&first);\r
9242       InitChessProgram(&first, FALSE);\r
9243     }    \r
9244     pn = positionNumber;\r
9245     if (positionNumber < 0) {\r
9246         /* Negative position number means to seek to that byte offset */\r
9247         if (fseek(f, -positionNumber, 0) == -1) {\r
9248             DisplayError(_("Can't seek on position file"), 0);\r
9249             return FALSE;\r
9250         };\r
9251         pn = 1;\r
9252     } else {\r
9253         if (fseek(f, 0, 0) == -1) {\r
9254             if (f == lastLoadPositionFP ?\r
9255                 positionNumber == lastLoadPositionNumber + 1 :\r
9256                 positionNumber == 1) {\r
9257                 pn = 1;\r
9258             } else {\r
9259                 DisplayError(_("Can't seek on position file"), 0);\r
9260                 return FALSE;\r
9261             }\r
9262         }\r
9263     }\r
9264     /* See if this file is FEN or old-style xboard */\r
9265     if (fgets(line, MSG_SIZ, f) == NULL) {\r
9266         DisplayError(_("Position not found in file"), 0);\r
9267         return FALSE;\r
9268     }\r
9269 #if 0\r
9270     switch (line[0]) {\r
9271       case '#':  case 'x':\r
9272       default:\r
9273         fenMode = FALSE;\r
9274         break;\r
9275       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':\r
9276       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':\r
9277       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':\r
9278       case '7':  case '8':  case '9':\r
9279       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':\r
9280       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':\r
9281       case 'C':  case 'W':             case 'c':  case 'w': \r
9282         fenMode = TRUE;\r
9283         break;\r
9284     }\r
9285 #else\r
9286     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces\r
9287     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;\r
9288 #endif\r
9289 \r
9290     if (pn >= 2) {\r
9291         if (fenMode || line[0] == '#') pn--;\r
9292         while (pn > 0) {\r
9293             /* skip positions before number pn */\r
9294             if (fgets(line, MSG_SIZ, f) == NULL) {\r
9295                 Reset(TRUE, TRUE);\r
9296                 DisplayError(_("Position not found in file"), 0);\r
9297                 return FALSE;\r
9298             }\r
9299             if (fenMode || line[0] == '#') pn--;\r
9300         }\r
9301     }\r
9302 \r
9303     if (fenMode) {\r
9304         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {\r
9305             DisplayError(_("Bad FEN position in file"), 0);\r
9306             return FALSE;\r
9307         }\r
9308     } else {\r
9309         (void) fgets(line, MSG_SIZ, f);\r
9310         (void) fgets(line, MSG_SIZ, f);\r
9311     \r
9312         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
9313             (void) fgets(line, MSG_SIZ, f);\r
9314             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {\r
9315                 if (*p == ' ')\r
9316                   continue;\r
9317                 initial_position[i][j++] = CharToPiece(*p);\r
9318             }\r
9319         }\r
9320     \r
9321         blackPlaysFirst = FALSE;\r
9322         if (!feof(f)) {\r
9323             (void) fgets(line, MSG_SIZ, f);\r
9324             if (strncmp(line, "black", strlen("black"))==0)\r
9325               blackPlaysFirst = TRUE;\r
9326         }\r
9327     }\r
9328     startedFromSetupPosition = TRUE;\r
9329     \r
9330     SendToProgram("force\n", &first);\r
9331     CopyBoard(boards[0], initial_position);\r
9332     if (blackPlaysFirst) {\r
9333         currentMove = forwardMostMove = backwardMostMove = 1;\r
9334         strcpy(moveList[0], "");\r
9335         strcpy(parseList[0], "");\r
9336         CopyBoard(boards[1], initial_position);\r
9337         DisplayMessage("", _("Black to play"));\r
9338     } else {\r
9339         currentMove = forwardMostMove = backwardMostMove = 0;\r
9340         DisplayMessage("", _("White to play"));\r
9341     }\r
9342           /* [HGM] copy FEN attributes as well */\r
9343           {   int i;\r
9344               initialRulePlies = FENrulePlies;\r
9345               epStatus[forwardMostMove] = FENepStatus;\r
9346               for( i=0; i< nrCastlingRights; i++ )\r
9347                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];\r
9348           }\r
9349     SendBoard(&first, forwardMostMove);\r
9350     if (appData.debugMode) {\r
9351 int i, j;\r
9352   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}\r
9353   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");\r
9354         fprintf(debugFP, "Load Position\n");\r
9355     }\r
9356 \r
9357     if (positionNumber > 1) {\r
9358         sprintf(line, "%s %d", title, positionNumber);\r
9359         DisplayTitle(line);\r
9360     } else {\r
9361         DisplayTitle(title);\r
9362     }\r
9363     gameMode = EditGame;\r
9364     ModeHighlight();\r
9365     ResetClocks();\r
9366     timeRemaining[0][1] = whiteTimeRemaining;\r
9367     timeRemaining[1][1] = blackTimeRemaining;\r
9368     DrawPosition(FALSE, boards[currentMove]);\r
9369    \r
9370     return TRUE;\r
9371 }\r
9372 \r
9373 \r
9374 void\r
9375 CopyPlayerNameIntoFileName(dest, src)\r
9376      char **dest, *src;\r
9377 {\r
9378     while (*src != NULLCHAR && *src != ',') {\r
9379         if (*src == ' ') {\r
9380             *(*dest)++ = '_';\r
9381             src++;\r
9382         } else {\r
9383             *(*dest)++ = *src++;\r
9384         }\r
9385     }\r
9386 }\r
9387 \r
9388 char *DefaultFileName(ext)\r
9389      char *ext;\r
9390 {\r
9391     static char def[MSG_SIZ];\r
9392     char *p;\r
9393 \r
9394     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {\r
9395         p = def;\r
9396         CopyPlayerNameIntoFileName(&p, gameInfo.white);\r
9397         *p++ = '-';\r
9398         CopyPlayerNameIntoFileName(&p, gameInfo.black);\r
9399         *p++ = '.';\r
9400         strcpy(p, ext);\r
9401     } else {\r
9402         def[0] = NULLCHAR;\r
9403     }\r
9404     return def;\r
9405 }\r
9406 \r
9407 /* Save the current game to the given file */\r
9408 int\r
9409 SaveGameToFile(filename, append)\r
9410      char *filename;\r
9411      int append;\r
9412 {\r
9413     FILE *f;\r
9414     char buf[MSG_SIZ];\r
9415 \r
9416     if (strcmp(filename, "-") == 0) {\r
9417         return SaveGame(stdout, 0, NULL);\r
9418     } else {\r
9419         f = fopen(filename, append ? "a" : "w");\r
9420         if (f == NULL) {\r
9421             sprintf(buf, _("Can't open \"%s\""), filename);\r
9422             DisplayError(buf, errno);\r
9423             return FALSE;\r
9424         } else {\r
9425             return SaveGame(f, 0, NULL);\r
9426         }\r
9427     }\r
9428 }\r
9429 \r
9430 char *\r
9431 SavePart(str)\r
9432      char *str;\r
9433 {\r
9434     static char buf[MSG_SIZ];\r
9435     char *p;\r
9436     \r
9437     p = strchr(str, ' ');\r
9438     if (p == NULL) return str;\r
9439     strncpy(buf, str, p - str);\r
9440     buf[p - str] = NULLCHAR;\r
9441     return buf;\r
9442 }\r
9443 \r
9444 #define PGN_MAX_LINE 75\r
9445 \r
9446 #define PGN_SIDE_WHITE  0\r
9447 #define PGN_SIDE_BLACK  1\r
9448 \r
9449 /* [AS] */\r
9450 static int FindFirstMoveOutOfBook( int side )\r
9451 {\r
9452     int result = -1;\r
9453 \r
9454     if( backwardMostMove == 0 && ! startedFromSetupPosition) {\r
9455         int index = backwardMostMove;\r
9456         int has_book_hit = 0;\r
9457 \r
9458         if( (index % 2) != side ) {\r
9459             index++;\r
9460         }\r
9461 \r
9462         while( index < forwardMostMove ) {\r
9463             /* Check to see if engine is in book */\r
9464             int depth = pvInfoList[index].depth;\r
9465             int score = pvInfoList[index].score;\r
9466             int in_book = 0;\r
9467 \r
9468             if( depth <= 2 ) {\r
9469                 in_book = 1;\r
9470             }\r
9471             else if( score == 0 && depth == 63 ) {\r
9472                 in_book = 1; /* Zappa */\r
9473             }\r
9474             else if( score == 2 && depth == 99 ) {\r
9475                 in_book = 1; /* Abrok */\r
9476             }\r
9477 \r
9478             has_book_hit += in_book;\r
9479 \r
9480             if( ! in_book ) {\r
9481                 result = index;\r
9482 \r
9483                 break;\r
9484             }\r
9485 \r
9486             index += 2;\r
9487         }\r
9488     }\r
9489 \r
9490     return result;\r
9491 }\r
9492 \r
9493 /* [AS] */\r
9494 void GetOutOfBookInfo( char * buf )\r
9495 {\r
9496     int oob[2];\r
9497     int i;\r
9498     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9499 \r
9500     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );\r
9501     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );\r
9502 \r
9503     *buf = '\0';\r
9504 \r
9505     if( oob[0] >= 0 || oob[1] >= 0 ) {\r
9506         for( i=0; i<2; i++ ) {\r
9507             int idx = oob[i];\r
9508 \r
9509             if( idx >= 0 ) {\r
9510                 if( i > 0 && oob[0] >= 0 ) {\r
9511                     strcat( buf, "   " );\r
9512                 }\r
9513 \r
9514                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );\r
9515                 sprintf( buf+strlen(buf), "%s%.2f", \r
9516                     pvInfoList[idx].score >= 0 ? "+" : "",\r
9517                     pvInfoList[idx].score / 100.0 );\r
9518             }\r
9519         }\r
9520     }\r
9521 }\r
9522 \r
9523 /* Save game in PGN style and close the file */\r
9524 int\r
9525 SaveGamePGN(f)\r
9526      FILE *f;\r
9527 {\r
9528     int i, offset, linelen, newblock;\r
9529     time_t tm;\r
9530 //    char *movetext;\r
9531     char numtext[32];\r
9532     int movelen, numlen, blank;\r
9533     char move_buffer[100]; /* [AS] Buffer for move+PV info */\r
9534 \r
9535     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9536     \r
9537     tm = time((time_t *) NULL);\r
9538     \r
9539     PrintPGNTags(f, &gameInfo);\r
9540     \r
9541     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
9542         char *fen = PositionToFEN(backwardMostMove, 1);\r
9543         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);\r
9544         fprintf(f, "\n{--------------\n");\r
9545         PrintPosition(f, backwardMostMove);\r
9546         fprintf(f, "--------------}\n");\r
9547         free(fen);\r
9548     }\r
9549     else {\r
9550         /* [AS] Out of book annotation */\r
9551         if( appData.saveOutOfBookInfo ) {\r
9552             char buf[64];\r
9553 \r
9554             GetOutOfBookInfo( buf );\r
9555 \r
9556             if( buf[0] != '\0' ) {\r
9557                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); \r
9558             }\r
9559         }\r
9560 \r
9561         fprintf(f, "\n");\r
9562     }\r
9563 \r
9564     i = backwardMostMove;\r
9565     linelen = 0;\r
9566     newblock = TRUE;\r
9567 \r
9568     while (i < forwardMostMove) {\r
9569         /* Print comments preceding this move */\r
9570         if (commentList[i] != NULL) {\r
9571             if (linelen > 0) fprintf(f, "\n");\r
9572             fprintf(f, "{\n%s}\n", commentList[i]);\r
9573             linelen = 0;\r
9574             newblock = TRUE;\r
9575         }\r
9576 \r
9577         /* Format move number */\r
9578         if ((i % 2) == 0) {\r
9579             sprintf(numtext, "%d.", (i - offset)/2 + 1);\r
9580         } else {\r
9581             if (newblock) {\r
9582                 sprintf(numtext, "%d...", (i - offset)/2 + 1);\r
9583             } else {\r
9584                 numtext[0] = NULLCHAR;\r
9585             }\r
9586         }\r
9587         numlen = strlen(numtext);\r
9588         newblock = FALSE;\r
9589 \r
9590         /* Print move number */\r
9591         blank = linelen > 0 && numlen > 0;\r
9592         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {\r
9593             fprintf(f, "\n");\r
9594             linelen = 0;\r
9595             blank = 0;\r
9596         }\r
9597         if (blank) {\r
9598             fprintf(f, " ");\r
9599             linelen++;\r
9600         }\r
9601         fprintf(f, numtext);\r
9602         linelen += numlen;\r
9603 \r
9604         /* Get move */\r
9605         movelen = strlen(parseList[i]); /* [HGM] pgn: line-break point before move */\r
9606 \r
9607         /* Print move */\r
9608         blank = linelen > 0 && movelen > 0;\r
9609         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
9610             fprintf(f, "\n");\r
9611             linelen = 0;\r
9612             blank = 0;\r
9613         }\r
9614         if (blank) {\r
9615             fprintf(f, " ");\r
9616             linelen++;\r
9617         }\r
9618         fprintf(f, parseList[i]);\r
9619         linelen += movelen;\r
9620 \r
9621         /* [AS] Add PV info if present */\r
9622         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {\r
9623             /* [HGM] add time */\r
9624             char buf[MSG_SIZ]; int seconds = 0;\r
9625 \r
9626 #if 0\r
9627             if(i >= backwardMostMove) {\r
9628                 if(WhiteOnMove(i))\r
9629                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]\r
9630                                   + GetTimeQuota(i/2) / WhitePlayer()->timeOdds;\r
9631                 else\r
9632                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]\r
9633                                   + GetTimeQuota(i/2) / WhitePlayer()->other->timeOdds;\r
9634             }\r
9635             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest\r
9636 #else\r
9637             seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time\r
9638 #endif\r
9639 \r
9640             if( seconds <= 0) buf[0] = 0; else\r
9641             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {\r
9642                 seconds = (seconds + 4)/10; // round to full seconds\r
9643                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else\r
9644                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);\r
9645             }\r
9646 \r
9647             sprintf( move_buffer, "{%s%.2f/%d%s}", \r
9648                 pvInfoList[i].score >= 0 ? "+" : "",\r
9649                 pvInfoList[i].score / 100.0,\r
9650                 pvInfoList[i].depth,\r
9651                 buf );\r
9652 \r
9653             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */\r
9654 \r
9655             /* Print score/depth */\r
9656             blank = linelen > 0 && movelen > 0;\r
9657             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
9658                 fprintf(f, "\n");\r
9659                 linelen = 0;\r
9660                 blank = 0;\r
9661             }\r
9662             if (blank) {\r
9663                 fprintf(f, " ");\r
9664                 linelen++;\r
9665             }\r
9666             fprintf(f, move_buffer);\r
9667             linelen += movelen;\r
9668         }\r
9669 \r
9670         i++;\r
9671     }\r
9672     \r
9673     /* Start a new line */\r
9674     if (linelen > 0) fprintf(f, "\n");\r
9675 \r
9676     /* Print comments after last move */\r
9677     if (commentList[i] != NULL) {\r
9678         fprintf(f, "{\n%s}\n", commentList[i]);\r
9679     }\r
9680 \r
9681     /* Print result */\r
9682     if (gameInfo.resultDetails != NULL &&\r
9683         gameInfo.resultDetails[0] != NULLCHAR) {\r
9684         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,\r
9685                 PGNResult(gameInfo.result));\r
9686     } else {\r
9687         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
9688     }\r
9689 \r
9690     fclose(f);\r
9691     return TRUE;\r
9692 }\r
9693 \r
9694 /* Save game in old style and close the file */\r
9695 int\r
9696 SaveGameOldStyle(f)\r
9697      FILE *f;\r
9698 {\r
9699     int i, offset;\r
9700     time_t tm;\r
9701     \r
9702     tm = time((time_t *) NULL);\r
9703     \r
9704     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));\r
9705     PrintOpponents(f);\r
9706     \r
9707     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
9708         fprintf(f, "\n[--------------\n");\r
9709         PrintPosition(f, backwardMostMove);\r
9710         fprintf(f, "--------------]\n");\r
9711     } else {\r
9712         fprintf(f, "\n");\r
9713     }\r
9714 \r
9715     i = backwardMostMove;\r
9716     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9717 \r
9718     while (i < forwardMostMove) {\r
9719         if (commentList[i] != NULL) {\r
9720             fprintf(f, "[%s]\n", commentList[i]);\r
9721         }\r
9722 \r
9723         if ((i % 2) == 1) {\r
9724             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);\r
9725             i++;\r
9726         } else {\r
9727             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);\r
9728             i++;\r
9729             if (commentList[i] != NULL) {\r
9730                 fprintf(f, "\n");\r
9731                 continue;\r
9732             }\r
9733             if (i >= forwardMostMove) {\r
9734                 fprintf(f, "\n");\r
9735                 break;\r
9736             }\r
9737             fprintf(f, "%s\n", parseList[i]);\r
9738             i++;\r
9739         }\r
9740     }\r
9741     \r
9742     if (commentList[i] != NULL) {\r
9743         fprintf(f, "[%s]\n", commentList[i]);\r
9744     }\r
9745 \r
9746     /* This isn't really the old style, but it's close enough */\r
9747     if (gameInfo.resultDetails != NULL &&\r
9748         gameInfo.resultDetails[0] != NULLCHAR) {\r
9749         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),\r
9750                 gameInfo.resultDetails);\r
9751     } else {\r
9752         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
9753     }\r
9754 \r
9755     fclose(f);\r
9756     return TRUE;\r
9757 }\r
9758 \r
9759 /* Save the current game to open file f and close the file */\r
9760 int\r
9761 SaveGame(f, dummy, dummy2)\r
9762      FILE *f;\r
9763      int dummy;\r
9764      char *dummy2;\r
9765 {\r
9766     if (gameMode == EditPosition) EditPositionDone();\r
9767     if (appData.oldSaveStyle)\r
9768       return SaveGameOldStyle(f);\r
9769     else\r
9770       return SaveGamePGN(f);\r
9771 }\r
9772 \r
9773 /* Save the current position to the given file */\r
9774 int\r
9775 SavePositionToFile(filename)\r
9776      char *filename;\r
9777 {\r
9778     FILE *f;\r
9779     char buf[MSG_SIZ];\r
9780 \r
9781     if (strcmp(filename, "-") == 0) {\r
9782         return SavePosition(stdout, 0, NULL);\r
9783     } else {\r
9784         f = fopen(filename, "a");\r
9785         if (f == NULL) {\r
9786             sprintf(buf, _("Can't open \"%s\""), filename);\r
9787             DisplayError(buf, errno);\r
9788             return FALSE;\r
9789         } else {\r
9790             SavePosition(f, 0, NULL);\r
9791             return TRUE;\r
9792         }\r
9793     }\r
9794 }\r
9795 \r
9796 /* Save the current position to the given open file and close the file */\r
9797 int\r
9798 SavePosition(f, dummy, dummy2)\r
9799      FILE *f;\r
9800      int dummy;\r
9801      char *dummy2;\r
9802 {\r
9803     time_t tm;\r
9804     char *fen;\r
9805     \r
9806     if (appData.oldSaveStyle) {\r
9807         tm = time((time_t *) NULL);\r
9808     \r
9809         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));\r
9810         PrintOpponents(f);\r
9811         fprintf(f, "[--------------\n");\r
9812         PrintPosition(f, currentMove);\r
9813         fprintf(f, "--------------]\n");\r
9814     } else {\r
9815         fen = PositionToFEN(currentMove, 1);\r
9816         fprintf(f, "%s\n", fen);\r
9817         free(fen);\r
9818     }\r
9819     fclose(f);\r
9820     return TRUE;\r
9821 }\r
9822 \r
9823 void\r
9824 ReloadCmailMsgEvent(unregister)\r
9825      int unregister;\r
9826 {\r
9827 #if !WIN32\r
9828     static char *inFilename = NULL;\r
9829     static char *outFilename;\r
9830     int i;\r
9831     struct stat inbuf, outbuf;\r
9832     int status;\r
9833     \r
9834     /* Any registered moves are unregistered if unregister is set, */\r
9835     /* i.e. invoked by the signal handler */\r
9836     if (unregister) {\r
9837         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
9838             cmailMoveRegistered[i] = FALSE;\r
9839             if (cmailCommentList[i] != NULL) {\r
9840                 free(cmailCommentList[i]);\r
9841                 cmailCommentList[i] = NULL;\r
9842             }\r
9843         }\r
9844         nCmailMovesRegistered = 0;\r
9845     }\r
9846 \r
9847     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
9848         cmailResult[i] = CMAIL_NOT_RESULT;\r
9849     }\r
9850     nCmailResults = 0;\r
9851 \r
9852     if (inFilename == NULL) {\r
9853         /* Because the filenames are static they only get malloced once  */\r
9854         /* and they never get freed                                      */\r
9855         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);\r
9856         sprintf(inFilename, "%s.game.in", appData.cmailGameName);\r
9857 \r
9858         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);\r
9859         sprintf(outFilename, "%s.out", appData.cmailGameName);\r
9860     }\r
9861     \r
9862     status = stat(outFilename, &outbuf);\r
9863     if (status < 0) {\r
9864         cmailMailedMove = FALSE;\r
9865     } else {\r
9866         status = stat(inFilename, &inbuf);\r
9867         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);\r
9868     }\r
9869     \r
9870     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE\r
9871        counts the games, notes how each one terminated, etc.\r
9872        \r
9873        It would be nice to remove this kludge and instead gather all\r
9874        the information while building the game list.  (And to keep it\r
9875        in the game list nodes instead of having a bunch of fixed-size\r
9876        parallel arrays.)  Note this will require getting each game's\r
9877        termination from the PGN tags, as the game list builder does\r
9878        not process the game moves.  --mann\r
9879        */\r
9880     cmailMsgLoaded = TRUE;\r
9881     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);\r
9882     \r
9883     /* Load first game in the file or popup game menu */\r
9884     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);\r
9885 \r
9886 #endif /* !WIN32 */\r
9887     return;\r
9888 }\r
9889 \r
9890 int\r
9891 RegisterMove()\r
9892 {\r
9893     FILE *f;\r
9894     char string[MSG_SIZ];\r
9895 \r
9896     if (   cmailMailedMove\r
9897         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {\r
9898         return TRUE;            /* Allow free viewing  */\r
9899     }\r
9900 \r
9901     /* Unregister move to ensure that we don't leave RegisterMove        */\r
9902     /* with the move registered when the conditions for registering no   */\r
9903     /* longer hold                                                       */\r
9904     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
9905         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
9906         nCmailMovesRegistered --;\r
9907 \r
9908         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) \r
9909           {\r
9910               free(cmailCommentList[lastLoadGameNumber - 1]);\r
9911               cmailCommentList[lastLoadGameNumber - 1] = NULL;\r
9912           }\r
9913     }\r
9914 \r
9915     if (cmailOldMove == -1) {\r
9916         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);\r
9917         return FALSE;\r
9918     }\r
9919 \r
9920     if (currentMove > cmailOldMove + 1) {\r
9921         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);\r
9922         return FALSE;\r
9923     }\r
9924 \r
9925     if (currentMove < cmailOldMove) {\r
9926         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);\r
9927         return FALSE;\r
9928     }\r
9929 \r
9930     if (forwardMostMove > currentMove) {\r
9931         /* Silently truncate extra moves */\r
9932         TruncateGame();\r
9933     }\r
9934 \r
9935     if (   (currentMove == cmailOldMove + 1)\r
9936         || (   (currentMove == cmailOldMove)\r
9937             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)\r
9938                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {\r
9939         if (gameInfo.result != GameUnfinished) {\r
9940             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;\r
9941         }\r
9942 \r
9943         if (commentList[currentMove] != NULL) {\r
9944             cmailCommentList[lastLoadGameNumber - 1]\r
9945               = StrSave(commentList[currentMove]);\r
9946         }\r
9947         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);\r
9948 \r
9949         if (appData.debugMode)\r
9950           fprintf(debugFP, "Saving %s for game %d\n",\r
9951                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
9952 \r
9953         sprintf(string,\r
9954                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);\r
9955         \r
9956         f = fopen(string, "w");\r
9957         if (appData.oldSaveStyle) {\r
9958             SaveGameOldStyle(f); /* also closes the file */\r
9959             \r
9960             sprintf(string, "%s.pos.out", appData.cmailGameName);\r
9961             f = fopen(string, "w");\r
9962             SavePosition(f, 0, NULL); /* also closes the file */\r
9963         } else {\r
9964             fprintf(f, "{--------------\n");\r
9965             PrintPosition(f, currentMove);\r
9966             fprintf(f, "--------------}\n\n");\r
9967             \r
9968             SaveGame(f, 0, NULL); /* also closes the file*/\r
9969         }\r
9970         \r
9971         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;\r
9972         nCmailMovesRegistered ++;\r
9973     } else if (nCmailGames == 1) {\r
9974         DisplayError(_("You have not made a move yet"), 0);\r
9975         return FALSE;\r
9976     }\r
9977 \r
9978     return TRUE;\r
9979 }\r
9980 \r
9981 void\r
9982 MailMoveEvent()\r
9983 {\r
9984 #if !WIN32\r
9985     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";\r
9986     FILE *commandOutput;\r
9987     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];\r
9988     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */\r
9989     int nBuffers;\r
9990     int i;\r
9991     int archived;\r
9992     char *arcDir;\r
9993 \r
9994     if (! cmailMsgLoaded) {\r
9995         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);\r
9996         return;\r
9997     }\r
9998 \r
9999     if (nCmailGames == nCmailResults) {\r
10000         DisplayError(_("No unfinished games"), 0);\r
10001         return;\r
10002     }\r
10003 \r
10004 #if CMAIL_PROHIBIT_REMAIL\r
10005     if (cmailMailedMove) {\r
10006         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
10007         DisplayError(msg, 0);\r
10008         return;\r
10009     }\r
10010 #endif\r
10011 \r
10012     if (! (cmailMailedMove || RegisterMove())) return;\r
10013     \r
10014     if (   cmailMailedMove\r
10015         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {\r
10016         sprintf(string, partCommandString,\r
10017                 appData.debugMode ? " -v" : "", appData.cmailGameName);\r
10018         commandOutput = popen(string, "r");\r
10019 \r
10020         if (commandOutput == NULL) {\r
10021             DisplayError(_("Failed to invoke cmail"), 0);\r
10022         } else {\r
10023             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {\r
10024                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);\r
10025             }\r
10026             if (nBuffers > 1) {\r
10027                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);\r
10028                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);\r
10029                 nBytes = MSG_SIZ - 1;\r
10030             } else {\r
10031                 (void) memcpy(msg, buffer, nBytes);\r
10032             }\r
10033             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/\r
10034 \r
10035             if(StrStr(msg, "Mailed cmail message to ") != NULL) {\r
10036                 cmailMailedMove = TRUE; /* Prevent >1 moves    */\r
10037 \r
10038                 archived = TRUE;\r
10039                 for (i = 0; i < nCmailGames; i ++) {\r
10040                     if (cmailResult[i] == CMAIL_NOT_RESULT) {\r
10041                         archived = FALSE;\r
10042                     }\r
10043                 }\r
10044                 if (   archived\r
10045                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))\r
10046                         != NULL)) {\r
10047                     sprintf(buffer, "%s/%s.%s.archive",\r
10048                             arcDir,\r
10049                             appData.cmailGameName,\r
10050                             gameInfo.date);\r
10051                     LoadGameFromFile(buffer, 1, buffer, FALSE);\r
10052                     cmailMsgLoaded = FALSE;\r
10053                 }\r
10054             }\r
10055 \r
10056             DisplayInformation(msg);\r
10057             pclose(commandOutput);\r
10058         }\r
10059     } else {\r
10060         if ((*cmailMsg) != '\0') {\r
10061             DisplayInformation(cmailMsg);\r
10062         }\r
10063     }\r
10064 \r
10065     return;\r
10066 #endif /* !WIN32 */\r
10067 }\r
10068 \r
10069 char *\r
10070 CmailMsg()\r
10071 {\r
10072 #if WIN32\r
10073     return NULL;\r
10074 #else\r
10075     int  prependComma = 0;\r
10076     char number[5];\r
10077     char string[MSG_SIZ];       /* Space for game-list */\r
10078     int  i;\r
10079     \r
10080     if (!cmailMsgLoaded) return "";\r
10081 \r
10082     if (cmailMailedMove) {\r
10083         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));\r
10084     } else {\r
10085         /* Create a list of games left */\r
10086         sprintf(string, "[");\r
10087         for (i = 0; i < nCmailGames; i ++) {\r
10088             if (! (   cmailMoveRegistered[i]\r
10089                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {\r
10090                 if (prependComma) {\r
10091                     sprintf(number, ",%d", i + 1);\r
10092                 } else {\r
10093                     sprintf(number, "%d", i + 1);\r
10094                     prependComma = 1;\r
10095                 }\r
10096                 \r
10097                 strcat(string, number);\r
10098             }\r
10099         }\r
10100         strcat(string, "]");\r
10101 \r
10102         if (nCmailMovesRegistered + nCmailResults == 0) {\r
10103             switch (nCmailGames) {\r
10104               case 1:\r
10105                 sprintf(cmailMsg,\r
10106                         _("Still need to make move for game\n"));\r
10107                 break;\r
10108                 \r
10109               case 2:\r
10110                 sprintf(cmailMsg,\r
10111                         _("Still need to make moves for both games\n"));\r
10112                 break;\r
10113                 \r
10114               default:\r
10115                 sprintf(cmailMsg,\r
10116                         _("Still need to make moves for all %d games\n"),\r
10117                         nCmailGames);\r
10118                 break;\r
10119             }\r
10120         } else {\r
10121             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {\r
10122               case 1:\r
10123                 sprintf(cmailMsg,\r
10124                         _("Still need to make a move for game %s\n"),\r
10125                         string);\r
10126                 break;\r
10127                 \r
10128               case 0:\r
10129                 if (nCmailResults == nCmailGames) {\r
10130                     sprintf(cmailMsg, _("No unfinished games\n"));\r
10131                 } else {\r
10132                     sprintf(cmailMsg, _("Ready to send mail\n"));\r
10133                 }\r
10134                 break;\r
10135                 \r
10136               default:\r
10137                 sprintf(cmailMsg,\r
10138                         _("Still need to make moves for games %s\n"),\r
10139                         string);\r
10140             }\r
10141         }\r
10142     }\r
10143     return cmailMsg;\r
10144 #endif /* WIN32 */\r
10145 }\r
10146 \r
10147 void\r
10148 ResetGameEvent()\r
10149 {\r
10150     if (gameMode == Training)\r
10151       SetTrainingModeOff();\r
10152 \r
10153     Reset(TRUE, TRUE);\r
10154     cmailMsgLoaded = FALSE;\r
10155     if (appData.icsActive) {\r
10156       SendToICS(ics_prefix);\r
10157       SendToICS("refresh\n");\r
10158     }\r
10159 }\r
10160 \r
10161 void\r
10162 ExitEvent(status)\r
10163      int status;\r
10164 {\r
10165     exiting++;\r
10166     if (exiting > 2) {\r
10167       /* Give up on clean exit */\r
10168       exit(status);\r
10169     }\r
10170     if (exiting > 1) {\r
10171       /* Keep trying for clean exit */\r
10172       return;\r
10173     }\r
10174 \r
10175     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);\r
10176 \r
10177     if (telnetISR != NULL) {\r
10178       RemoveInputSource(telnetISR);\r
10179     }\r
10180     if (icsPR != NoProc) {\r
10181       DestroyChildProcess(icsPR, TRUE);\r
10182     }\r
10183 #if 0\r
10184     /* Save game if resource set and not already saved by GameEnds() */\r
10185     if ((gameInfo.resultDetails == NULL || errorExitFlag )\r
10186                              && forwardMostMove > 0) {\r
10187       if (*appData.saveGameFile != NULLCHAR) {\r
10188         SaveGameToFile(appData.saveGameFile, TRUE);\r
10189       } else if (appData.autoSaveGames) {\r
10190         AutoSaveGame();\r
10191       }\r
10192       if (*appData.savePositionFile != NULLCHAR) {\r
10193         SavePositionToFile(appData.savePositionFile);\r
10194       }\r
10195     }\r
10196     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
10197 #else\r
10198     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */\r
10199     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);\r
10200 #endif\r
10201     /* [HGM] crash: the above GameEnds() is a dud if another one was running */\r
10202     /* make sure this other one finishes before killing it!                  */\r
10203     if(endingGame) { int count = 0;\r
10204         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");\r
10205         while(endingGame && count++ < 10) DoSleep(1);\r
10206         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");\r
10207     }\r
10208 \r
10209     /* Kill off chess programs */\r
10210     if (first.pr != NoProc) {\r
10211         ExitAnalyzeMode();\r
10212         \r
10213         DoSleep( appData.delayBeforeQuit );\r
10214         SendToProgram("quit\n", &first);\r
10215         DoSleep( appData.delayAfterQuit );\r
10216         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );\r
10217     }\r
10218     if (second.pr != NoProc) {\r
10219         DoSleep( appData.delayBeforeQuit );\r
10220         SendToProgram("quit\n", &second);\r
10221         DoSleep( appData.delayAfterQuit );\r
10222         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );\r
10223     }\r
10224     if (first.isr != NULL) {\r
10225         RemoveInputSource(first.isr);\r
10226     }\r
10227     if (second.isr != NULL) {\r
10228         RemoveInputSource(second.isr);\r
10229     }\r
10230 \r
10231     ShutDownFrontEnd();\r
10232     exit(status);\r
10233 }\r
10234 \r
10235 void\r
10236 PauseEvent()\r
10237 {\r
10238     if (appData.debugMode)\r
10239         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);\r
10240     if (pausing) {\r
10241         pausing = FALSE;\r
10242         ModeHighlight();\r
10243         if (gameMode == MachinePlaysWhite ||\r
10244             gameMode == MachinePlaysBlack) {\r
10245             StartClocks();\r
10246         } else {\r
10247             DisplayBothClocks();\r
10248         }\r
10249         if (gameMode == PlayFromGameFile) {\r
10250             if (appData.timeDelay >= 0) \r
10251                 AutoPlayGameLoop();\r
10252         } else if (gameMode == IcsExamining && pauseExamInvalid) {\r
10253             Reset(FALSE, TRUE);\r
10254             SendToICS(ics_prefix);\r
10255             SendToICS("refresh\n");\r
10256         } else if (currentMove < forwardMostMove) {\r
10257             ForwardInner(forwardMostMove);\r
10258         }\r
10259         pauseExamInvalid = FALSE;\r
10260     } else {\r
10261         switch (gameMode) {\r
10262           default:\r
10263             return;\r
10264           case IcsExamining:\r
10265             pauseExamForwardMostMove = forwardMostMove;\r
10266             pauseExamInvalid = FALSE;\r
10267             /* fall through */\r
10268           case IcsObserving:\r
10269           case IcsPlayingWhite:\r
10270           case IcsPlayingBlack:\r
10271             pausing = TRUE;\r
10272             ModeHighlight();\r
10273             return;\r
10274           case PlayFromGameFile:\r
10275             (void) StopLoadGameTimer();\r
10276             pausing = TRUE;\r
10277             ModeHighlight();\r
10278             break;\r
10279           case BeginningOfGame:\r
10280             if (appData.icsActive) return;\r
10281             /* else fall through */\r
10282           case MachinePlaysWhite:\r
10283           case MachinePlaysBlack:\r
10284           case TwoMachinesPlay:\r
10285             if (forwardMostMove == 0)\r
10286               return;           /* don't pause if no one has moved */\r
10287             if ((gameMode == MachinePlaysWhite &&\r
10288                  !WhiteOnMove(forwardMostMove)) ||\r
10289                 (gameMode == MachinePlaysBlack &&\r
10290                  WhiteOnMove(forwardMostMove))) {\r
10291                 StopClocks();\r
10292             }\r
10293             pausing = TRUE;\r
10294             ModeHighlight();\r
10295             break;\r
10296         }\r
10297     }\r
10298 }\r
10299 \r
10300 void\r
10301 EditCommentEvent()\r
10302 {\r
10303     char title[MSG_SIZ];\r
10304 \r
10305     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {\r
10306         strcpy(title, _("Edit comment"));\r
10307     } else {\r
10308         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,\r
10309                 WhiteOnMove(currentMove - 1) ? " " : ".. ",\r
10310                 parseList[currentMove - 1]);\r
10311     }\r
10312 \r
10313     EditCommentPopUp(currentMove, title, commentList[currentMove]);\r
10314 }\r
10315 \r
10316 \r
10317 void\r
10318 EditTagsEvent()\r
10319 {\r
10320     char *tags = PGNTags(&gameInfo);\r
10321     EditTagsPopUp(tags);\r
10322     free(tags);\r
10323 }\r
10324 \r
10325 void\r
10326 AnalyzeModeEvent()\r
10327 {\r
10328     if (appData.noChessProgram || gameMode == AnalyzeMode)\r
10329       return;\r
10330 \r
10331     if (gameMode != AnalyzeFile) {\r
10332         if (!appData.icsEngineAnalyze) {\r
10333                EditGameEvent();\r
10334                if (gameMode != EditGame) return;\r
10335         }\r
10336         ResurrectChessProgram();\r
10337         SendToProgram("analyze\n", &first);\r
10338         first.analyzing = TRUE;\r
10339         /*first.maybeThinking = TRUE;*/\r
10340         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10341         AnalysisPopUp(_("Analysis"),\r
10342                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));\r
10343     }\r
10344     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;\r
10345     pausing = FALSE;\r
10346     ModeHighlight();\r
10347     SetGameInfo();\r
10348 \r
10349     StartAnalysisClock();\r
10350     GetTimeMark(&lastNodeCountTime);\r
10351     lastNodeCount = 0;\r
10352 }\r
10353 \r
10354 void\r
10355 AnalyzeFileEvent()\r
10356 {\r
10357     if (appData.noChessProgram || gameMode == AnalyzeFile)\r
10358       return;\r
10359 \r
10360     if (gameMode != AnalyzeMode) {\r
10361         EditGameEvent();\r
10362         if (gameMode != EditGame) return;\r
10363         ResurrectChessProgram();\r
10364         SendToProgram("analyze\n", &first);\r
10365         first.analyzing = TRUE;\r
10366         /*first.maybeThinking = TRUE;*/\r
10367         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10368         AnalysisPopUp(_("Analysis"),\r
10369                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));\r
10370     }\r
10371     gameMode = AnalyzeFile;\r
10372     pausing = FALSE;\r
10373     ModeHighlight();\r
10374     SetGameInfo();\r
10375 \r
10376     StartAnalysisClock();\r
10377     GetTimeMark(&lastNodeCountTime);\r
10378     lastNodeCount = 0;\r
10379 }\r
10380 \r
10381 void\r
10382 MachineWhiteEvent()\r
10383 {\r
10384     char buf[MSG_SIZ];\r
10385     char *bookHit = NULL;\r
10386 \r
10387     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))\r
10388       return;\r
10389 \r
10390 \r
10391     if (gameMode == PlayFromGameFile || \r
10392         gameMode == TwoMachinesPlay  || \r
10393         gameMode == Training         || \r
10394         gameMode == AnalyzeMode      || \r
10395         gameMode == EndOfGame)\r
10396         EditGameEvent();\r
10397 \r
10398     if (gameMode == EditPosition) \r
10399         EditPositionDone();\r
10400 \r
10401     if (!WhiteOnMove(currentMove)) {\r
10402         DisplayError(_("It is not White's turn"), 0);\r
10403         return;\r
10404     }\r
10405   \r
10406     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
10407       ExitAnalyzeMode();\r
10408 \r
10409     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
10410         gameMode == AnalyzeFile)\r
10411         TruncateGame();\r
10412 \r
10413     ResurrectChessProgram();    /* in case it isn't running */\r
10414     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */\r
10415         gameMode = MachinePlaysWhite;\r
10416         ResetClocks();\r
10417     } else\r
10418     gameMode = MachinePlaysWhite;\r
10419     pausing = FALSE;\r
10420     ModeHighlight();\r
10421     SetGameInfo();\r
10422     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10423     DisplayTitle(buf);\r
10424     if (first.sendName) {\r
10425       sprintf(buf, "name %s\n", gameInfo.black);\r
10426       SendToProgram(buf, &first);\r
10427     }\r
10428     if (first.sendTime) {\r
10429       if (first.useColors) {\r
10430         SendToProgram("black\n", &first); /*gnu kludge*/\r
10431       }\r
10432       SendTimeRemaining(&first, TRUE);\r
10433     }\r
10434     if (first.useColors) {\r
10435       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately\r
10436     }\r
10437     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move\r
10438     SetMachineThinkingEnables();\r
10439     first.maybeThinking = TRUE;\r
10440     StartClocks();\r
10441 \r
10442     if (appData.autoFlipView && !flipView) {\r
10443       flipView = !flipView;\r
10444       DrawPosition(FALSE, NULL);\r
10445       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;\r
10446     }\r
10447 \r
10448     if(bookHit) { // [HGM] book: simulate book reply\r
10449         static char bookMove[MSG_SIZ]; // a bit generous?\r
10450 \r
10451         programStats.nodes = programStats.depth = programStats.time = \r
10452         programStats.score = programStats.got_only_move = 0;\r
10453         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10454 \r
10455         strcpy(bookMove, "move ");\r
10456         strcat(bookMove, bookHit);\r
10457         HandleMachineMove(bookMove, &first);\r
10458     }\r
10459 }\r
10460 \r
10461 void\r
10462 MachineBlackEvent()\r
10463 {\r
10464     char buf[MSG_SIZ];\r
10465    char *bookHit = NULL;\r
10466 \r
10467     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))\r
10468         return;\r
10469 \r
10470 \r
10471     if (gameMode == PlayFromGameFile || \r
10472         gameMode == TwoMachinesPlay  || \r
10473         gameMode == Training         || \r
10474         gameMode == AnalyzeMode      || \r
10475         gameMode == EndOfGame)\r
10476         EditGameEvent();\r
10477 \r
10478     if (gameMode == EditPosition) \r
10479         EditPositionDone();\r
10480 \r
10481     if (WhiteOnMove(currentMove)) {\r
10482         DisplayError(_("It is not Black's turn"), 0);\r
10483         return;\r
10484     }\r
10485     \r
10486     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
10487       ExitAnalyzeMode();\r
10488 \r
10489     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
10490         gameMode == AnalyzeFile)\r
10491         TruncateGame();\r
10492 \r
10493     ResurrectChessProgram();    /* in case it isn't running */\r
10494     gameMode = MachinePlaysBlack;\r
10495     pausing = FALSE;\r
10496     ModeHighlight();\r
10497     SetGameInfo();\r
10498     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10499     DisplayTitle(buf);\r
10500     if (first.sendName) {\r
10501       sprintf(buf, "name %s\n", gameInfo.white);\r
10502       SendToProgram(buf, &first);\r
10503     }\r
10504     if (first.sendTime) {\r
10505       if (first.useColors) {\r
10506         SendToProgram("white\n", &first); /*gnu kludge*/\r
10507       }\r
10508       SendTimeRemaining(&first, FALSE);\r
10509     }\r
10510     if (first.useColors) {\r
10511       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately\r
10512     }\r
10513     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move\r
10514     SetMachineThinkingEnables();\r
10515     first.maybeThinking = TRUE;\r
10516     StartClocks();\r
10517 \r
10518     if (appData.autoFlipView && flipView) {\r
10519       flipView = !flipView;\r
10520       DrawPosition(FALSE, NULL);\r
10521       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;\r
10522     }\r
10523     if(bookHit) { // [HGM] book: simulate book reply\r
10524         static char bookMove[MSG_SIZ]; // a bit generous?\r
10525 \r
10526         programStats.nodes = programStats.depth = programStats.time = \r
10527         programStats.score = programStats.got_only_move = 0;\r
10528         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10529 \r
10530         strcpy(bookMove, "move ");\r
10531         strcat(bookMove, bookHit);\r
10532         HandleMachineMove(bookMove, &first);\r
10533     }\r
10534 }\r
10535 \r
10536 \r
10537 void\r
10538 DisplayTwoMachinesTitle()\r
10539 {\r
10540     char buf[MSG_SIZ];\r
10541     if (appData.matchGames > 0) {\r
10542         if (first.twoMachinesColor[0] == 'w') {\r
10543             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
10544                     gameInfo.white, gameInfo.black,\r
10545                     first.matchWins, second.matchWins,\r
10546                     matchGame - 1 - (first.matchWins + second.matchWins));\r
10547         } else {\r
10548             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
10549                     gameInfo.white, gameInfo.black,\r
10550                     second.matchWins, first.matchWins,\r
10551                     matchGame - 1 - (first.matchWins + second.matchWins));\r
10552         }\r
10553     } else {\r
10554         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10555     }\r
10556     DisplayTitle(buf);\r
10557 }\r
10558 \r
10559 void\r
10560 TwoMachinesEvent P((void))\r
10561 {\r
10562     int i;\r
10563     char buf[MSG_SIZ];\r
10564     ChessProgramState *onmove;\r
10565     char *bookHit = NULL;\r
10566     \r
10567     if (appData.noChessProgram) return;\r
10568 \r
10569     switch (gameMode) {\r
10570       case TwoMachinesPlay:\r
10571         return;\r
10572       case MachinePlaysWhite:\r
10573       case MachinePlaysBlack:\r
10574         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
10575             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);\r
10576             return;\r
10577         }\r
10578         /* fall through */\r
10579       case BeginningOfGame:\r
10580       case PlayFromGameFile:\r
10581       case EndOfGame:\r
10582         EditGameEvent();\r
10583         if (gameMode != EditGame) return;\r
10584         break;\r
10585       case EditPosition:\r
10586         EditPositionDone();\r
10587         break;\r
10588       case AnalyzeMode:\r
10589       case AnalyzeFile:\r
10590         ExitAnalyzeMode();\r
10591         break;\r
10592       case EditGame:\r
10593       default:\r
10594         break;\r
10595     }\r
10596 \r
10597     forwardMostMove = currentMove;\r
10598     ResurrectChessProgram();    /* in case first program isn't running */\r
10599 \r
10600     if (second.pr == NULL) {\r
10601         StartChessProgram(&second);\r
10602         if (second.protocolVersion == 1) {\r
10603           TwoMachinesEventIfReady();\r
10604         } else {\r
10605           /* kludge: allow timeout for initial "feature" command */\r
10606           FreezeUI();\r
10607           DisplayMessage("", _("Starting second chess program"));\r
10608           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);\r
10609         }\r
10610         return;\r
10611     }\r
10612     DisplayMessage("", "");\r
10613     InitChessProgram(&second, FALSE);\r
10614     SendToProgram("force\n", &second);\r
10615     if (startedFromSetupPosition) {\r
10616         SendBoard(&second, backwardMostMove);\r
10617     if (appData.debugMode) {\r
10618         fprintf(debugFP, "Two Machines\n");\r
10619     }\r
10620     }\r
10621     for (i = backwardMostMove; i < forwardMostMove; i++) {\r
10622         SendMoveToProgram(i, &second);\r
10623     }\r
10624 \r
10625     gameMode = TwoMachinesPlay;\r
10626     pausing = FALSE;\r
10627     ModeHighlight();\r
10628     SetGameInfo();\r
10629     DisplayTwoMachinesTitle();\r
10630     firstMove = TRUE;\r
10631     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {\r
10632         onmove = &first;\r
10633     } else {\r
10634         onmove = &second;\r
10635     }\r
10636 \r
10637     SendToProgram(first.computerString, &first);\r
10638     if (first.sendName) {\r
10639       sprintf(buf, "name %s\n", second.tidy);\r
10640       SendToProgram(buf, &first);\r
10641     }\r
10642     SendToProgram(second.computerString, &second);\r
10643     if (second.sendName) {\r
10644       sprintf(buf, "name %s\n", first.tidy);\r
10645       SendToProgram(buf, &second);\r
10646     }\r
10647 \r
10648     ResetClocks();\r
10649     if (!first.sendTime || !second.sendTime) {\r
10650         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
10651         timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
10652     }\r
10653     if (onmove->sendTime) {\r
10654       if (onmove->useColors) {\r
10655         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/\r
10656       }\r
10657       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));\r
10658     }\r
10659     if (onmove->useColors) {\r
10660       SendToProgram(onmove->twoMachinesColor, onmove);\r
10661     }\r
10662     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move\r
10663 //    SendToProgram("go\n", onmove);\r
10664     onmove->maybeThinking = TRUE;\r
10665     SetMachineThinkingEnables();\r
10666 \r
10667     StartClocks();\r
10668 \r
10669     if(bookHit) { // [HGM] book: simulate book reply\r
10670         static char bookMove[MSG_SIZ]; // a bit generous?\r
10671 \r
10672         programStats.nodes = programStats.depth = programStats.time = \r
10673         programStats.score = programStats.got_only_move = 0;\r
10674         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10675 \r
10676         strcpy(bookMove, "move ");\r
10677         strcat(bookMove, bookHit);\r
10678         HandleMachineMove(bookMove, &first);\r
10679     }\r
10680 }\r
10681 \r
10682 void\r
10683 TrainingEvent()\r
10684 {\r
10685     if (gameMode == Training) {\r
10686       SetTrainingModeOff();\r
10687       gameMode = PlayFromGameFile;\r
10688       DisplayMessage("", _("Training mode off"));\r
10689     } else {\r
10690       gameMode = Training;\r
10691       animateTraining = appData.animate;\r
10692 \r
10693       /* make sure we are not already at the end of the game */\r
10694       if (currentMove < forwardMostMove) {\r
10695         SetTrainingModeOn();\r
10696         DisplayMessage("", _("Training mode on"));\r
10697       } else {\r
10698         gameMode = PlayFromGameFile;\r
10699         DisplayError(_("Already at end of game"), 0);\r
10700       }\r
10701     }\r
10702     ModeHighlight();\r
10703 }\r
10704 \r
10705 void\r
10706 IcsClientEvent()\r
10707 {\r
10708     if (!appData.icsActive) return;\r
10709     switch (gameMode) {\r
10710       case IcsPlayingWhite:\r
10711       case IcsPlayingBlack:\r
10712       case IcsObserving:\r
10713       case IcsIdle:\r
10714       case BeginningOfGame:\r
10715       case IcsExamining:\r
10716         return;\r
10717 \r
10718       case EditGame:\r
10719         break;\r
10720 \r
10721       case EditPosition:\r
10722         EditPositionDone();\r
10723         break;\r
10724 \r
10725       case AnalyzeMode:\r
10726       case AnalyzeFile:\r
10727         ExitAnalyzeMode();\r
10728         break;\r
10729         \r
10730       default:\r
10731         EditGameEvent();\r
10732         break;\r
10733     }\r
10734 \r
10735     gameMode = IcsIdle;\r
10736     ModeHighlight();\r
10737     return;\r
10738 }\r
10739 \r
10740 \r
10741 void\r
10742 EditGameEvent()\r
10743 {\r
10744     int i;\r
10745 \r
10746     switch (gameMode) {\r
10747       case Training:\r
10748         SetTrainingModeOff();\r
10749         break;\r
10750       case MachinePlaysWhite:\r
10751       case MachinePlaysBlack:\r
10752       case BeginningOfGame:\r
10753         SendToProgram("force\n", &first);\r
10754         SetUserThinkingEnables();\r
10755         break;\r
10756       case PlayFromGameFile:\r
10757         (void) StopLoadGameTimer();\r
10758         if (gameFileFP != NULL) {\r
10759             gameFileFP = NULL;\r
10760         }\r
10761         break;\r
10762       case EditPosition:\r
10763         EditPositionDone();\r
10764         break;\r
10765       case AnalyzeMode:\r
10766       case AnalyzeFile:\r
10767         ExitAnalyzeMode();\r
10768         SendToProgram("force\n", &first);\r
10769         break;\r
10770       case TwoMachinesPlay:\r
10771         GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
10772         ResurrectChessProgram();\r
10773         SetUserThinkingEnables();\r
10774         break;\r
10775       case EndOfGame:\r
10776         ResurrectChessProgram();\r
10777         break;\r
10778       case IcsPlayingBlack:\r
10779       case IcsPlayingWhite:\r
10780         DisplayError(_("Warning: You are still playing a game"), 0);\r
10781         break;\r
10782       case IcsObserving:\r
10783         DisplayError(_("Warning: You are still observing a game"), 0);\r
10784         break;\r
10785       case IcsExamining:\r
10786         DisplayError(_("Warning: You are still examining a game"), 0);\r
10787         break;\r
10788       case IcsIdle:\r
10789         break;\r
10790       case EditGame:\r
10791       default:\r
10792         return;\r
10793     }\r
10794     \r
10795     pausing = FALSE;\r
10796     StopClocks();\r
10797     first.offeredDraw = second.offeredDraw = 0;\r
10798 \r
10799     if (gameMode == PlayFromGameFile) {\r
10800         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10801         blackTimeRemaining = timeRemaining[1][currentMove];\r
10802         DisplayTitle("");\r
10803     }\r
10804 \r
10805     if (gameMode == MachinePlaysWhite ||\r
10806         gameMode == MachinePlaysBlack ||\r
10807         gameMode == TwoMachinesPlay ||\r
10808         gameMode == EndOfGame) {\r
10809         i = forwardMostMove;\r
10810         while (i > currentMove) {\r
10811             SendToProgram("undo\n", &first);\r
10812             i--;\r
10813         }\r
10814         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10815         blackTimeRemaining = timeRemaining[1][currentMove];\r
10816         DisplayBothClocks();\r
10817         if (whiteFlag || blackFlag) {\r
10818             whiteFlag = blackFlag = 0;\r
10819         }\r
10820         DisplayTitle("");\r
10821     }           \r
10822     \r
10823     gameMode = EditGame;\r
10824     ModeHighlight();\r
10825     SetGameInfo();\r
10826 }\r
10827 \r
10828 \r
10829 void\r
10830 EditPositionEvent()\r
10831 {\r
10832     if (gameMode == EditPosition) {\r
10833         EditGameEvent();\r
10834         return;\r
10835     }\r
10836     \r
10837     EditGameEvent();\r
10838     if (gameMode != EditGame) return;\r
10839     \r
10840     gameMode = EditPosition;\r
10841     ModeHighlight();\r
10842     SetGameInfo();\r
10843     if (currentMove > 0)\r
10844       CopyBoard(boards[0], boards[currentMove]);\r
10845     \r
10846     blackPlaysFirst = !WhiteOnMove(currentMove);\r
10847     ResetClocks();\r
10848     currentMove = forwardMostMove = backwardMostMove = 0;\r
10849     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
10850     DisplayMove(-1);\r
10851 }\r
10852 \r
10853 void\r
10854 ExitAnalyzeMode()\r
10855 {\r
10856     /* [DM] icsEngineAnalyze - possible call from other functions */\r
10857     if (appData.icsEngineAnalyze) {\r
10858         appData.icsEngineAnalyze = FALSE;\r
10859 \r
10860         DisplayMessage("",_("Close ICS engine analyze..."));\r
10861     }\r
10862     if (first.analysisSupport && first.analyzing) {\r
10863       SendToProgram("exit\n", &first);\r
10864       first.analyzing = FALSE;\r
10865     }\r
10866     AnalysisPopDown();\r
10867     thinkOutput[0] = NULLCHAR;\r
10868 }\r
10869 \r
10870 void\r
10871 EditPositionDone()\r
10872 {\r
10873     startedFromSetupPosition = TRUE;\r
10874     InitChessProgram(&first, FALSE);\r
10875     SendToProgram("force\n", &first);\r
10876     if (blackPlaysFirst) {\r
10877         strcpy(moveList[0], "");\r
10878         strcpy(parseList[0], "");\r
10879         currentMove = forwardMostMove = backwardMostMove = 1;\r
10880         CopyBoard(boards[1], boards[0]);\r
10881         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */\r
10882         { int i;\r
10883           epStatus[1] = epStatus[0];\r
10884           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];\r
10885         }\r
10886     } else {\r
10887         currentMove = forwardMostMove = backwardMostMove = 0;\r
10888     }\r
10889     SendBoard(&first, forwardMostMove);\r
10890     if (appData.debugMode) {\r
10891         fprintf(debugFP, "EditPosDone\n");\r
10892     }\r
10893     DisplayTitle("");\r
10894     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
10895     timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
10896     gameMode = EditGame;\r
10897     ModeHighlight();\r
10898     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
10899     ClearHighlights(); /* [AS] */\r
10900 }\r
10901 \r
10902 /* Pause for `ms' milliseconds */\r
10903 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
10904 void\r
10905 TimeDelay(ms)\r
10906      long ms;\r
10907 {\r
10908     TimeMark m1, m2;\r
10909 \r
10910     GetTimeMark(&m1);\r
10911     do {\r
10912         GetTimeMark(&m2);\r
10913     } while (SubtractTimeMarks(&m2, &m1) < ms);\r
10914 }\r
10915 \r
10916 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
10917 void\r
10918 SendMultiLineToICS(buf)\r
10919      char *buf;\r
10920 {\r
10921     char temp[MSG_SIZ+1], *p;\r
10922     int len;\r
10923 \r
10924     len = strlen(buf);\r
10925     if (len > MSG_SIZ)\r
10926       len = MSG_SIZ;\r
10927   \r
10928     strncpy(temp, buf, len);\r
10929     temp[len] = 0;\r
10930 \r
10931     p = temp;\r
10932     while (*p) {\r
10933         if (*p == '\n' || *p == '\r')\r
10934           *p = ' ';\r
10935         ++p;\r
10936     }\r
10937 \r
10938     strcat(temp, "\n");\r
10939     SendToICS(temp);\r
10940     SendToPlayer(temp, strlen(temp));\r
10941 }\r
10942 \r
10943 void\r
10944 SetWhiteToPlayEvent()\r
10945 {\r
10946     if (gameMode == EditPosition) {\r
10947         blackPlaysFirst = FALSE;\r
10948         DisplayBothClocks();    /* works because currentMove is 0 */\r
10949     } else if (gameMode == IcsExamining) {\r
10950         SendToICS(ics_prefix);\r
10951         SendToICS("tomove white\n");\r
10952     }\r
10953 }\r
10954 \r
10955 void\r
10956 SetBlackToPlayEvent()\r
10957 {\r
10958     if (gameMode == EditPosition) {\r
10959         blackPlaysFirst = TRUE;\r
10960         currentMove = 1;        /* kludge */\r
10961         DisplayBothClocks();\r
10962         currentMove = 0;\r
10963     } else if (gameMode == IcsExamining) {\r
10964         SendToICS(ics_prefix);\r
10965         SendToICS("tomove black\n");\r
10966     }\r
10967 }\r
10968 \r
10969 void\r
10970 EditPositionMenuEvent(selection, x, y)\r
10971      ChessSquare selection;\r
10972      int x, y;\r
10973 {\r
10974     char buf[MSG_SIZ];\r
10975     ChessSquare piece = boards[0][y][x];\r
10976 \r
10977     if (gameMode != EditPosition && gameMode != IcsExamining) return;\r
10978 \r
10979     switch (selection) {\r
10980       case ClearBoard:\r
10981         if (gameMode == IcsExamining && ics_type == ICS_FICS) {\r
10982             SendToICS(ics_prefix);\r
10983             SendToICS("bsetup clear\n");\r
10984         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {\r
10985             SendToICS(ics_prefix);\r
10986             SendToICS("clearboard\n");\r
10987         } else {\r
10988             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;\r
10989                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */\r
10990                 for (y = 0; y < BOARD_HEIGHT; y++) {\r
10991                     if (gameMode == IcsExamining) {\r
10992                         if (boards[currentMove][y][x] != EmptySquare) {\r
10993                             sprintf(buf, "%sx@%c%c\n", ics_prefix,\r
10994                                     AAA + x, ONE + y);\r
10995                             SendToICS(buf);\r
10996                         }\r
10997                     } else {\r
10998                         boards[0][y][x] = p;\r
10999                     }\r
11000                 }\r
11001             }\r
11002         }\r
11003         if (gameMode == EditPosition) {\r
11004             DrawPosition(FALSE, boards[0]);\r
11005         }\r
11006         break;\r
11007 \r
11008       case WhitePlay:\r
11009         SetWhiteToPlayEvent();\r
11010         break;\r
11011 \r
11012       case BlackPlay:\r
11013         SetBlackToPlayEvent();\r
11014         break;\r
11015 \r
11016       case EmptySquare:\r
11017         if (gameMode == IcsExamining) {\r
11018             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);\r
11019             SendToICS(buf);\r
11020         } else {\r
11021             boards[0][y][x] = EmptySquare;\r
11022             DrawPosition(FALSE, boards[0]);\r
11023         }\r
11024         break;\r
11025 \r
11026       case PromotePiece:\r
11027         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||\r
11028            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {\r
11029             selection = (ChessSquare) (PROMOTED piece);\r
11030         } else if(piece == EmptySquare) selection = WhiteSilver;\r
11031         else selection = (ChessSquare)((int)piece - 1);\r
11032         goto defaultlabel;\r
11033 \r
11034       case DemotePiece:\r
11035         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||\r
11036            piece > (int)BlackMan && piece <= (int)BlackKing   ) {\r
11037             selection = (ChessSquare) (DEMOTED piece);\r
11038         } else if(piece == EmptySquare) selection = BlackSilver;\r
11039         else selection = (ChessSquare)((int)piece + 1);       \r
11040         goto defaultlabel;\r
11041 \r
11042       case WhiteQueen:\r
11043       case BlackQueen:\r
11044         if(gameInfo.variant == VariantShatranj ||\r
11045            gameInfo.variant == VariantXiangqi  ||\r
11046            gameInfo.variant == VariantCourier    )\r
11047             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);\r
11048         goto defaultlabel;\r
11049 \r
11050       case WhiteKing:\r
11051       case BlackKing:\r
11052         if(gameInfo.variant == VariantXiangqi)\r
11053             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);\r
11054         if(gameInfo.variant == VariantKnightmate)\r
11055             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);\r
11056       default:\r
11057         defaultlabel:\r
11058         if (gameMode == IcsExamining) {\r
11059             sprintf(buf, "%s%c@%c%c\n", ics_prefix,\r
11060                     PieceToChar(selection), AAA + x, ONE + y);\r
11061             SendToICS(buf);\r
11062         } else {\r
11063             boards[0][y][x] = selection;\r
11064             DrawPosition(FALSE, boards[0]);\r
11065         }\r
11066         break;\r
11067     }\r
11068 }\r
11069 \r
11070 \r
11071 void\r
11072 DropMenuEvent(selection, x, y)\r
11073      ChessSquare selection;\r
11074      int x, y;\r
11075 {\r
11076     ChessMove moveType;\r
11077 \r
11078     switch (gameMode) {\r
11079       case IcsPlayingWhite:\r
11080       case MachinePlaysBlack:\r
11081         if (!WhiteOnMove(currentMove)) {\r
11082             DisplayMoveError(_("It is Black's turn"));\r
11083             return;\r
11084         }\r
11085         moveType = WhiteDrop;\r
11086         break;\r
11087       case IcsPlayingBlack:\r
11088       case MachinePlaysWhite:\r
11089         if (WhiteOnMove(currentMove)) {\r
11090             DisplayMoveError(_("It is White's turn"));\r
11091             return;\r
11092         }\r
11093         moveType = BlackDrop;\r
11094         break;\r
11095       case EditGame:\r
11096         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
11097         break;\r
11098       default:\r
11099         return;\r
11100     }\r
11101 \r
11102     if (moveType == BlackDrop && selection < BlackPawn) {\r
11103       selection = (ChessSquare) ((int) selection\r
11104                                  + (int) BlackPawn - (int) WhitePawn);\r
11105     }\r
11106     if (boards[currentMove][y][x] != EmptySquare) {\r
11107         DisplayMoveError(_("That square is occupied"));\r
11108         return;\r
11109     }\r
11110 \r
11111     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);\r
11112 }\r
11113 \r
11114 void\r
11115 AcceptEvent()\r
11116 {\r
11117     /* Accept a pending offer of any kind from opponent */\r
11118     \r
11119     if (appData.icsActive) {\r
11120         SendToICS(ics_prefix);\r
11121         SendToICS("accept\n");\r
11122     } else if (cmailMsgLoaded) {\r
11123         if (currentMove == cmailOldMove &&\r
11124             commentList[cmailOldMove] != NULL &&\r
11125             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11126                    "Black offers a draw" : "White offers a draw")) {\r
11127             TruncateGame();\r
11128             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
11129             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
11130         } else {\r
11131             DisplayError(_("There is no pending offer on this move"), 0);\r
11132             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
11133         }\r
11134     } else {\r
11135         /* Not used for offers from chess program */\r
11136     }\r
11137 }\r
11138 \r
11139 void\r
11140 DeclineEvent()\r
11141 {\r
11142     /* Decline a pending offer of any kind from opponent */\r
11143     \r
11144     if (appData.icsActive) {\r
11145         SendToICS(ics_prefix);\r
11146         SendToICS("decline\n");\r
11147     } else if (cmailMsgLoaded) {\r
11148         if (currentMove == cmailOldMove &&\r
11149             commentList[cmailOldMove] != NULL &&\r
11150             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11151                    "Black offers a draw" : "White offers a draw")) {\r
11152 #ifdef NOTDEF\r
11153             AppendComment(cmailOldMove, "Draw declined");\r
11154             DisplayComment(cmailOldMove - 1, "Draw declined");\r
11155 #endif /*NOTDEF*/\r
11156         } else {\r
11157             DisplayError(_("There is no pending offer on this move"), 0);\r
11158         }\r
11159     } else {\r
11160         /* Not used for offers from chess program */\r
11161     }\r
11162 }\r
11163 \r
11164 void\r
11165 RematchEvent()\r
11166 {\r
11167     /* Issue ICS rematch command */\r
11168     if (appData.icsActive) {\r
11169         SendToICS(ics_prefix);\r
11170         SendToICS("rematch\n");\r
11171     }\r
11172 }\r
11173 \r
11174 void\r
11175 CallFlagEvent()\r
11176 {\r
11177     /* Call your opponent's flag (claim a win on time) */\r
11178     if (appData.icsActive) {\r
11179         SendToICS(ics_prefix);\r
11180         SendToICS("flag\n");\r
11181     } else {\r
11182         switch (gameMode) {\r
11183           default:\r
11184             return;\r
11185           case MachinePlaysWhite:\r
11186             if (whiteFlag) {\r
11187                 if (blackFlag)\r
11188                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
11189                            GE_PLAYER);\r
11190                 else\r
11191                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);\r
11192             } else {\r
11193                 DisplayError(_("Your opponent is not out of time"), 0);\r
11194             }\r
11195             break;\r
11196           case MachinePlaysBlack:\r
11197             if (blackFlag) {\r
11198                 if (whiteFlag)\r
11199                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
11200                            GE_PLAYER);\r
11201                 else\r
11202                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);\r
11203             } else {\r
11204                 DisplayError(_("Your opponent is not out of time"), 0);\r
11205             }\r
11206             break;\r
11207         }\r
11208     }\r
11209 }\r
11210 \r
11211 void\r
11212 DrawEvent()\r
11213 {\r
11214     /* Offer draw or accept pending draw offer from opponent */\r
11215     \r
11216     if (appData.icsActive) {\r
11217         /* Note: tournament rules require draw offers to be\r
11218            made after you make your move but before you punch\r
11219            your clock.  Currently ICS doesn't let you do that;\r
11220            instead, you immediately punch your clock after making\r
11221            a move, but you can offer a draw at any time. */\r
11222         \r
11223         SendToICS(ics_prefix);\r
11224         SendToICS("draw\n");\r
11225     } else if (cmailMsgLoaded) {\r
11226         if (currentMove == cmailOldMove &&\r
11227             commentList[cmailOldMove] != NULL &&\r
11228             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11229                    "Black offers a draw" : "White offers a draw")) {\r
11230             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
11231             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
11232         } else if (currentMove == cmailOldMove + 1) {\r
11233             char *offer = WhiteOnMove(cmailOldMove) ?\r
11234               "White offers a draw" : "Black offers a draw";\r
11235             AppendComment(currentMove, offer);\r
11236             DisplayComment(currentMove - 1, offer);\r
11237             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;\r
11238         } else {\r
11239             DisplayError(_("You must make your move before offering a draw"), 0);\r
11240             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
11241         }\r
11242     } else if (first.offeredDraw) {\r
11243         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
11244     } else {\r
11245         if (first.sendDrawOffers) {\r
11246             SendToProgram("draw\n", &first);\r
11247             userOfferedDraw = TRUE;\r
11248         }\r
11249     }\r
11250 }\r
11251 \r
11252 void\r
11253 AdjournEvent()\r
11254 {\r
11255     /* Offer Adjourn or accept pending Adjourn offer from opponent */\r
11256     \r
11257     if (appData.icsActive) {\r
11258         SendToICS(ics_prefix);\r
11259         SendToICS("adjourn\n");\r
11260     } else {\r
11261         /* Currently GNU Chess doesn't offer or accept Adjourns */\r
11262     }\r
11263 }\r
11264 \r
11265 \r
11266 void\r
11267 AbortEvent()\r
11268 {\r
11269     /* Offer Abort or accept pending Abort offer from opponent */\r
11270     \r
11271     if (appData.icsActive) {\r
11272         SendToICS(ics_prefix);\r
11273         SendToICS("abort\n");\r
11274     } else {\r
11275         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);\r
11276     }\r
11277 }\r
11278 \r
11279 void\r
11280 ResignEvent()\r
11281 {\r
11282     /* Resign.  You can do this even if it's not your turn. */\r
11283     \r
11284     if (appData.icsActive) {\r
11285         SendToICS(ics_prefix);\r
11286         SendToICS("resign\n");\r
11287     } else {\r
11288         switch (gameMode) {\r
11289           case MachinePlaysWhite:\r
11290             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
11291             break;\r
11292           case MachinePlaysBlack:\r
11293             GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
11294             break;\r
11295           case EditGame:\r
11296             if (cmailMsgLoaded) {\r
11297                 TruncateGame();\r
11298                 if (WhiteOnMove(cmailOldMove)) {\r
11299                     GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
11300                 } else {\r
11301                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
11302                 }\r
11303                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;\r
11304             }\r
11305             break;\r
11306           default:\r
11307             break;\r
11308         }\r
11309     }\r
11310 }\r
11311 \r
11312 \r
11313 void\r
11314 StopObservingEvent()\r
11315 {\r
11316     /* Stop observing current games */\r
11317     SendToICS(ics_prefix);\r
11318     SendToICS("unobserve\n");\r
11319 }\r
11320 \r
11321 void\r
11322 StopExaminingEvent()\r
11323 {\r
11324     /* Stop observing current game */\r
11325     SendToICS(ics_prefix);\r
11326     SendToICS("unexamine\n");\r
11327 }\r
11328 \r
11329 void\r
11330 ForwardInner(target)\r
11331      int target;\r
11332 {\r
11333     int limit;\r
11334 \r
11335     if (appData.debugMode)\r
11336         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",\r
11337                 target, currentMove, forwardMostMove);\r
11338 \r
11339     if (gameMode == EditPosition)\r
11340       return;\r
11341 \r
11342     if (gameMode == PlayFromGameFile && !pausing)\r
11343       PauseEvent();\r
11344     \r
11345     if (gameMode == IcsExamining && pausing)\r
11346       limit = pauseExamForwardMostMove;\r
11347     else\r
11348       limit = forwardMostMove;\r
11349     \r
11350     if (target > limit) target = limit;\r
11351 \r
11352     if (target > 0 && moveList[target - 1][0]) {\r
11353         int fromX, fromY, toX, toY;\r
11354         toX = moveList[target - 1][2] - AAA;\r
11355         toY = moveList[target - 1][3] - ONE;\r
11356         if (moveList[target - 1][1] == '@') {\r
11357             if (appData.highlightLastMove) {\r
11358                 SetHighlights(-1, -1, toX, toY);\r
11359             }\r
11360         } else {\r
11361             fromX = moveList[target - 1][0] - AAA;\r
11362             fromY = moveList[target - 1][1] - ONE;\r
11363             if (target == currentMove + 1) {\r
11364                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
11365             }\r
11366             if (appData.highlightLastMove) {\r
11367                 SetHighlights(fromX, fromY, toX, toY);\r
11368             }\r
11369         }\r
11370     }\r
11371     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
11372         gameMode == Training || gameMode == PlayFromGameFile || \r
11373         gameMode == AnalyzeFile) {\r
11374         while (currentMove < target) {\r
11375             SendMoveToProgram(currentMove++, &first);\r
11376         }\r
11377     } else {\r
11378         currentMove = target;\r
11379     }\r
11380     \r
11381     if (gameMode == EditGame || gameMode == EndOfGame) {\r
11382         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11383         blackTimeRemaining = timeRemaining[1][currentMove];\r
11384     }\r
11385     DisplayBothClocks();\r
11386     DisplayMove(currentMove - 1);\r
11387     DrawPosition(FALSE, boards[currentMove]);\r
11388     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
11389     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty\r
11390         DisplayComment(currentMove - 1, commentList[currentMove]);\r
11391     }\r
11392 }\r
11393 \r
11394 \r
11395 void\r
11396 ForwardEvent()\r
11397 {\r
11398     if (gameMode == IcsExamining && !pausing) {\r
11399         SendToICS(ics_prefix);\r
11400         SendToICS("forward\n");\r
11401     } else {\r
11402         ForwardInner(currentMove + 1);\r
11403     }\r
11404 }\r
11405 \r
11406 void\r
11407 ToEndEvent()\r
11408 {\r
11409     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11410         /* to optimze, we temporarily turn off analysis mode while we feed\r
11411          * the remaining moves to the engine. Otherwise we get analysis output\r
11412          * after each move.\r
11413          */ \r
11414         if (first.analysisSupport) {\r
11415           SendToProgram("exit\nforce\n", &first);\r
11416           first.analyzing = FALSE;\r
11417         }\r
11418     }\r
11419         \r
11420     if (gameMode == IcsExamining && !pausing) {\r
11421         SendToICS(ics_prefix);\r
11422         SendToICS("forward 999999\n");\r
11423     } else {\r
11424         ForwardInner(forwardMostMove);\r
11425     }\r
11426 \r
11427     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11428         /* we have fed all the moves, so reactivate analysis mode */\r
11429         SendToProgram("analyze\n", &first);\r
11430         first.analyzing = TRUE;\r
11431         /*first.maybeThinking = TRUE;*/\r
11432         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
11433     }\r
11434 }\r
11435 \r
11436 void\r
11437 BackwardInner(target)\r
11438      int target;\r
11439 {\r
11440     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */\r
11441 \r
11442     if (appData.debugMode)\r
11443         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",\r
11444                 target, currentMove, forwardMostMove);\r
11445 \r
11446     if (gameMode == EditPosition) return;\r
11447     if (currentMove <= backwardMostMove) {\r
11448         ClearHighlights();\r
11449         DrawPosition(full_redraw, boards[currentMove]);\r
11450         return;\r
11451     }\r
11452     if (gameMode == PlayFromGameFile && !pausing)\r
11453       PauseEvent();\r
11454     \r
11455     if (moveList[target][0]) {\r
11456         int fromX, fromY, toX, toY;\r
11457         toX = moveList[target][2] - AAA;\r
11458         toY = moveList[target][3] - ONE;\r
11459         if (moveList[target][1] == '@') {\r
11460             if (appData.highlightLastMove) {\r
11461                 SetHighlights(-1, -1, toX, toY);\r
11462             }\r
11463         } else {\r
11464             fromX = moveList[target][0] - AAA;\r
11465             fromY = moveList[target][1] - ONE;\r
11466             if (target == currentMove - 1) {\r
11467                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);\r
11468             }\r
11469             if (appData.highlightLastMove) {\r
11470                 SetHighlights(fromX, fromY, toX, toY);\r
11471             }\r
11472         }\r
11473     }\r
11474     if (gameMode == EditGame || gameMode==AnalyzeMode ||\r
11475         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
11476         while (currentMove > target) {\r
11477             SendToProgram("undo\n", &first);\r
11478             currentMove--;\r
11479         }\r
11480     } else {\r
11481         currentMove = target;\r
11482     }\r
11483     \r
11484     if (gameMode == EditGame || gameMode == EndOfGame) {\r
11485         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11486         blackTimeRemaining = timeRemaining[1][currentMove];\r
11487     }\r
11488     DisplayBothClocks();\r
11489     DisplayMove(currentMove - 1);\r
11490     DrawPosition(full_redraw, boards[currentMove]);\r
11491     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
11492     // [HGM] PV info: routine tests if comment empty\r
11493     DisplayComment(currentMove - 1, commentList[currentMove]);\r
11494 }\r
11495 \r
11496 void\r
11497 BackwardEvent()\r
11498 {\r
11499     if (gameMode == IcsExamining && !pausing) {\r
11500         SendToICS(ics_prefix);\r
11501         SendToICS("backward\n");\r
11502     } else {\r
11503         BackwardInner(currentMove - 1);\r
11504     }\r
11505 }\r
11506 \r
11507 void\r
11508 ToStartEvent()\r
11509 {\r
11510     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11511         /* to optimze, we temporarily turn off analysis mode while we undo\r
11512          * all the moves. Otherwise we get analysis output after each undo.\r
11513          */ \r
11514         if (first.analysisSupport) {\r
11515           SendToProgram("exit\nforce\n", &first);\r
11516           first.analyzing = FALSE;\r
11517         }\r
11518     }\r
11519 \r
11520     if (gameMode == IcsExamining && !pausing) {\r
11521         SendToICS(ics_prefix);\r
11522         SendToICS("backward 999999\n");\r
11523     } else {\r
11524         BackwardInner(backwardMostMove);\r
11525     }\r
11526 \r
11527     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11528         /* we have fed all the moves, so reactivate analysis mode */\r
11529         SendToProgram("analyze\n", &first);\r
11530         first.analyzing = TRUE;\r
11531         /*first.maybeThinking = TRUE;*/\r
11532         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
11533     }\r
11534 }\r
11535 \r
11536 void\r
11537 ToNrEvent(int to)\r
11538 {\r
11539   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();\r
11540   if (to >= forwardMostMove) to = forwardMostMove;\r
11541   if (to <= backwardMostMove) to = backwardMostMove;\r
11542   if (to < currentMove) {\r
11543     BackwardInner(to);\r
11544   } else {\r
11545     ForwardInner(to);\r
11546   }\r
11547 }\r
11548 \r
11549 void\r
11550 RevertEvent()\r
11551 {\r
11552     if (gameMode != IcsExamining) {\r
11553         DisplayError(_("You are not examining a game"), 0);\r
11554         return;\r
11555     }\r
11556     if (pausing) {\r
11557         DisplayError(_("You can't revert while pausing"), 0);\r
11558         return;\r
11559     }\r
11560     SendToICS(ics_prefix);\r
11561     SendToICS("revert\n");\r
11562 }\r
11563 \r
11564 void\r
11565 RetractMoveEvent()\r
11566 {\r
11567     switch (gameMode) {\r
11568       case MachinePlaysWhite:\r
11569       case MachinePlaysBlack:\r
11570         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
11571             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);\r
11572             return;\r
11573         }\r
11574         if (forwardMostMove < 2) return;\r
11575         currentMove = forwardMostMove = forwardMostMove - 2;\r
11576         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11577         blackTimeRemaining = timeRemaining[1][currentMove];\r
11578         DisplayBothClocks();\r
11579         DisplayMove(currentMove - 1);\r
11580         ClearHighlights();/*!! could figure this out*/\r
11581         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */\r
11582         SendToProgram("remove\n", &first);\r
11583         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */\r
11584         break;\r
11585 \r
11586       case BeginningOfGame:\r
11587       default:\r
11588         break;\r
11589 \r
11590       case IcsPlayingWhite:\r
11591       case IcsPlayingBlack:\r
11592         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {\r
11593             SendToICS(ics_prefix);\r
11594             SendToICS("takeback 2\n");\r
11595         } else {\r
11596             SendToICS(ics_prefix);\r
11597             SendToICS("takeback 1\n");\r
11598         }\r
11599         break;\r
11600     }\r
11601 }\r
11602 \r
11603 void\r
11604 MoveNowEvent()\r
11605 {\r
11606     ChessProgramState *cps;\r
11607 \r
11608     switch (gameMode) {\r
11609       case MachinePlaysWhite:\r
11610         if (!WhiteOnMove(forwardMostMove)) {\r
11611             DisplayError(_("It is your turn"), 0);\r
11612             return;\r
11613         }\r
11614         cps = &first;\r
11615         break;\r
11616       case MachinePlaysBlack:\r
11617         if (WhiteOnMove(forwardMostMove)) {\r
11618             DisplayError(_("It is your turn"), 0);\r
11619             return;\r
11620         }\r
11621         cps = &first;\r
11622         break;\r
11623       case TwoMachinesPlay:\r
11624         if (WhiteOnMove(forwardMostMove) ==\r
11625             (first.twoMachinesColor[0] == 'w')) {\r
11626             cps = &first;\r
11627         } else {\r
11628             cps = &second;\r
11629         }\r
11630         break;\r
11631       case BeginningOfGame:\r
11632       default:\r
11633         return;\r
11634     }\r
11635     SendToProgram("?\n", cps);\r
11636 }\r
11637 \r
11638 void\r
11639 TruncateGameEvent()\r
11640 {\r
11641     EditGameEvent();\r
11642     if (gameMode != EditGame) return;\r
11643     TruncateGame();\r
11644 }\r
11645 \r
11646 void\r
11647 TruncateGame()\r
11648 {\r
11649     if (forwardMostMove > currentMove) {\r
11650         if (gameInfo.resultDetails != NULL) {\r
11651             free(gameInfo.resultDetails);\r
11652             gameInfo.resultDetails = NULL;\r
11653             gameInfo.result = GameUnfinished;\r
11654         }\r
11655         forwardMostMove = currentMove;\r
11656         HistorySet(parseList, backwardMostMove, forwardMostMove,\r
11657                    currentMove-1);\r
11658     }\r
11659 }\r
11660 \r
11661 void\r
11662 HintEvent()\r
11663 {\r
11664     if (appData.noChessProgram) return;\r
11665     switch (gameMode) {\r
11666       case MachinePlaysWhite:\r
11667         if (WhiteOnMove(forwardMostMove)) {\r
11668             DisplayError(_("Wait until your turn"), 0);\r
11669             return;\r
11670         }\r
11671         break;\r
11672       case BeginningOfGame:\r
11673       case MachinePlaysBlack:\r
11674         if (!WhiteOnMove(forwardMostMove)) {\r
11675             DisplayError(_("Wait until your turn"), 0);\r
11676             return;\r
11677         }\r
11678         break;\r
11679       default:\r
11680         DisplayError(_("No hint available"), 0);\r
11681         return;\r
11682     }\r
11683     SendToProgram("hint\n", &first);\r
11684     hintRequested = TRUE;\r
11685 }\r
11686 \r
11687 void\r
11688 BookEvent()\r
11689 {\r
11690     if (appData.noChessProgram) return;\r
11691     switch (gameMode) {\r
11692       case MachinePlaysWhite:\r
11693         if (WhiteOnMove(forwardMostMove)) {\r
11694             DisplayError(_("Wait until your turn"), 0);\r
11695             return;\r
11696         }\r
11697         break;\r
11698       case BeginningOfGame:\r
11699       case MachinePlaysBlack:\r
11700         if (!WhiteOnMove(forwardMostMove)) {\r
11701             DisplayError(_("Wait until your turn"), 0);\r
11702             return;\r
11703         }\r
11704         break;\r
11705       case EditPosition:\r
11706         EditPositionDone();\r
11707         break;\r
11708       case TwoMachinesPlay:\r
11709         return;\r
11710       default:\r
11711         break;\r
11712     }\r
11713     SendToProgram("bk\n", &first);\r
11714     bookOutput[0] = NULLCHAR;\r
11715     bookRequested = TRUE;\r
11716 }\r
11717 \r
11718 void\r
11719 AboutGameEvent()\r
11720 {\r
11721     char *tags = PGNTags(&gameInfo);\r
11722     TagsPopUp(tags, CmailMsg());\r
11723     free(tags);\r
11724 }\r
11725 \r
11726 /* end button procedures */\r
11727 \r
11728 void\r
11729 PrintPosition(fp, move)\r
11730      FILE *fp;\r
11731      int move;\r
11732 {\r
11733     int i, j;\r
11734     \r
11735     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
11736         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
11737             char c = PieceToChar(boards[move][i][j]);\r
11738             fputc(c == 'x' ? '.' : c, fp);\r
11739             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);\r
11740         }\r
11741     }\r
11742     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))\r
11743       fprintf(fp, "white to play\n");\r
11744     else\r
11745       fprintf(fp, "black to play\n");\r
11746 }\r
11747 \r
11748 void\r
11749 PrintOpponents(fp)\r
11750      FILE *fp;\r
11751 {\r
11752     if (gameInfo.white != NULL) {\r
11753         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);\r
11754     } else {\r
11755         fprintf(fp, "\n");\r
11756     }\r
11757 }\r
11758 \r
11759 /* Find last component of program's own name, using some heuristics */\r
11760 void\r
11761 TidyProgramName(prog, host, buf)\r
11762      char *prog, *host, buf[MSG_SIZ];\r
11763 {\r
11764     char *p, *q;\r
11765     int local = (strcmp(host, "localhost") == 0);\r
11766     while (!local && (p = strchr(prog, ';')) != NULL) {\r
11767         p++;\r
11768         while (*p == ' ') p++;\r
11769         prog = p;\r
11770     }\r
11771     if (*prog == '"' || *prog == '\'') {\r
11772         q = strchr(prog + 1, *prog);\r
11773     } else {\r
11774         q = strchr(prog, ' ');\r
11775     }\r
11776     if (q == NULL) q = prog + strlen(prog);\r
11777     p = q;\r
11778     while (p >= prog && *p != '/' && *p != '\\') p--;\r
11779     p++;\r
11780     if(p == prog && *p == '"') p++;\r
11781     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;\r
11782     memcpy(buf, p, q - p);\r
11783     buf[q - p] = NULLCHAR;\r
11784     if (!local) {\r
11785         strcat(buf, "@");\r
11786         strcat(buf, host);\r
11787     }\r
11788 }\r
11789 \r
11790 char *\r
11791 TimeControlTagValue()\r
11792 {\r
11793     char buf[MSG_SIZ];\r
11794     if (!appData.clockMode) {\r
11795         strcpy(buf, "-");\r
11796     } else if (movesPerSession > 0) {\r
11797         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);\r
11798     } else if (timeIncrement == 0) {\r
11799         sprintf(buf, "%ld", timeControl/1000);\r
11800     } else {\r
11801         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);\r
11802     }\r
11803     return StrSave(buf);\r
11804 }\r
11805 \r
11806 void\r
11807 SetGameInfo()\r
11808 {\r
11809     /* This routine is used only for certain modes */\r
11810     VariantClass v = gameInfo.variant;\r
11811     ClearGameInfo(&gameInfo);\r
11812     gameInfo.variant = v;\r
11813 \r
11814     switch (gameMode) {\r
11815       case MachinePlaysWhite:\r
11816         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11817         gameInfo.site = StrSave(HostName());\r
11818         gameInfo.date = PGNDate();\r
11819         gameInfo.round = StrSave("-");\r
11820         gameInfo.white = StrSave(first.tidy);\r
11821         gameInfo.black = StrSave(UserName());\r
11822         gameInfo.timeControl = TimeControlTagValue();\r
11823         break;\r
11824 \r
11825       case MachinePlaysBlack:\r
11826         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11827         gameInfo.site = StrSave(HostName());\r
11828         gameInfo.date = PGNDate();\r
11829         gameInfo.round = StrSave("-");\r
11830         gameInfo.white = StrSave(UserName());\r
11831         gameInfo.black = StrSave(first.tidy);\r
11832         gameInfo.timeControl = TimeControlTagValue();\r
11833         break;\r
11834 \r
11835       case TwoMachinesPlay:\r
11836         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11837         gameInfo.site = StrSave(HostName());\r
11838         gameInfo.date = PGNDate();\r
11839         if (matchGame > 0) {\r
11840             char buf[MSG_SIZ];\r
11841             sprintf(buf, "%d", matchGame);\r
11842             gameInfo.round = StrSave(buf);\r
11843         } else {\r
11844             gameInfo.round = StrSave("-");\r
11845         }\r
11846         if (first.twoMachinesColor[0] == 'w') {\r
11847             gameInfo.white = StrSave(first.tidy);\r
11848             gameInfo.black = StrSave(second.tidy);\r
11849         } else {\r
11850             gameInfo.white = StrSave(second.tidy);\r
11851             gameInfo.black = StrSave(first.tidy);\r
11852         }\r
11853         gameInfo.timeControl = TimeControlTagValue();\r
11854         break;\r
11855 \r
11856       case EditGame:\r
11857         gameInfo.event = StrSave("Edited game");\r
11858         gameInfo.site = StrSave(HostName());\r
11859         gameInfo.date = PGNDate();\r
11860         gameInfo.round = StrSave("-");\r
11861         gameInfo.white = StrSave("-");\r
11862         gameInfo.black = StrSave("-");\r
11863         break;\r
11864 \r
11865       case EditPosition:\r
11866         gameInfo.event = StrSave("Edited position");\r
11867         gameInfo.site = StrSave(HostName());\r
11868         gameInfo.date = PGNDate();\r
11869         gameInfo.round = StrSave("-");\r
11870         gameInfo.white = StrSave("-");\r
11871         gameInfo.black = StrSave("-");\r
11872         break;\r
11873 \r
11874       case IcsPlayingWhite:\r
11875       case IcsPlayingBlack:\r
11876       case IcsObserving:\r
11877       case IcsExamining:\r
11878         break;\r
11879 \r
11880       case PlayFromGameFile:\r
11881         gameInfo.event = StrSave("Game from non-PGN file");\r
11882         gameInfo.site = StrSave(HostName());\r
11883         gameInfo.date = PGNDate();\r
11884         gameInfo.round = StrSave("-");\r
11885         gameInfo.white = StrSave("?");\r
11886         gameInfo.black = StrSave("?");\r
11887         break;\r
11888 \r
11889       default:\r
11890         break;\r
11891     }\r
11892 }\r
11893 \r
11894 void\r
11895 ReplaceComment(index, text)\r
11896      int index;\r
11897      char *text;\r
11898 {\r
11899     int len;\r
11900 \r
11901     while (*text == '\n') text++;\r
11902     len = strlen(text);\r
11903     while (len > 0 && text[len - 1] == '\n') len--;\r
11904 \r
11905     if (commentList[index] != NULL)\r
11906       free(commentList[index]);\r
11907 \r
11908     if (len == 0) {\r
11909         commentList[index] = NULL;\r
11910         return;\r
11911     }\r
11912     commentList[index] = (char *) malloc(len + 2);\r
11913     strncpy(commentList[index], text, len);\r
11914     commentList[index][len] = '\n';\r
11915     commentList[index][len + 1] = NULLCHAR;\r
11916 }\r
11917 \r
11918 void\r
11919 CrushCRs(text)\r
11920      char *text;\r
11921 {\r
11922   char *p = text;\r
11923   char *q = text;\r
11924   char ch;\r
11925 \r
11926   do {\r
11927     ch = *p++;\r
11928     if (ch == '\r') continue;\r
11929     *q++ = ch;\r
11930   } while (ch != '\0');\r
11931 }\r
11932 \r
11933 void\r
11934 AppendComment(index, text)\r
11935      int index;\r
11936      char *text;\r
11937 {\r
11938     int oldlen, len;\r
11939     char *old;\r
11940 \r
11941     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */\r
11942 \r
11943     CrushCRs(text);\r
11944     while (*text == '\n') text++;\r
11945     len = strlen(text);\r
11946     while (len > 0 && text[len - 1] == '\n') len--;\r
11947 \r
11948     if (len == 0) return;\r
11949 \r
11950     if (commentList[index] != NULL) {\r
11951         old = commentList[index];\r
11952         oldlen = strlen(old);\r
11953         commentList[index] = (char *) malloc(oldlen + len + 2);\r
11954         strcpy(commentList[index], old);\r
11955         free(old);\r
11956         strncpy(&commentList[index][oldlen], text, len);\r
11957         commentList[index][oldlen + len] = '\n';\r
11958         commentList[index][oldlen + len + 1] = NULLCHAR;\r
11959     } else {\r
11960         commentList[index] = (char *) malloc(len + 2);\r
11961         strncpy(commentList[index], text, len);\r
11962         commentList[index][len] = '\n';\r
11963         commentList[index][len + 1] = NULLCHAR;\r
11964     }\r
11965 }\r
11966 \r
11967 static char * FindStr( char * text, char * sub_text )\r
11968 {\r
11969     char * result = strstr( text, sub_text );\r
11970 \r
11971     if( result != NULL ) {\r
11972         result += strlen( sub_text );\r
11973     }\r
11974 \r
11975     return result;\r
11976 }\r
11977 \r
11978 /* [AS] Try to extract PV info from PGN comment */\r
11979 /* [HGM] PV time: and then remove it, to prevent it appearing twice */\r
11980 char *GetInfoFromComment( int index, char * text )\r
11981 {\r
11982     char * sep = text;\r
11983 \r
11984     if( text != NULL && index > 0 ) {\r
11985         int score = 0;\r
11986         int depth = 0;\r
11987         int time = -1, sec = 0, deci;\r
11988         char * s_eval = FindStr( text, "[%eval " );\r
11989         char * s_emt = FindStr( text, "[%emt " );\r
11990 \r
11991         if( s_eval != NULL || s_emt != NULL ) {\r
11992             /* New style */\r
11993             char delim;\r
11994 \r
11995             if( s_eval != NULL ) {\r
11996                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {\r
11997                     return text;\r
11998                 }\r
11999 \r
12000                 if( delim != ']' ) {\r
12001                     return text;\r
12002                 }\r
12003             }\r
12004 \r
12005             if( s_emt != NULL ) {\r
12006             }\r
12007         }\r
12008         else {\r
12009             /* We expect something like: [+|-]nnn.nn/dd */\r
12010             int score_lo = 0;\r
12011 \r
12012             sep = strchr( text, '/' );\r
12013             if( sep == NULL || sep < (text+4) ) {\r
12014                 return text;\r
12015             }\r
12016 \r
12017             time = -1; sec = -1; deci = -1;\r
12018             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&\r
12019                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&\r
12020                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&\r
12021                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {\r
12022                 return text;\r
12023             }\r
12024 \r
12025             if( score_lo < 0 || score_lo >= 100 ) {\r
12026                 return text;\r
12027             }\r
12028 \r
12029             if(sec >= 0) time = 600*time + 10*sec; else\r
12030             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec\r
12031 \r
12032             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;\r
12033 \r
12034             /* [HGM] PV time: now locate end of PV info */\r
12035             while( *++sep >= '0' && *sep <= '9'); // strip depth\r
12036             if(time >= 0)\r
12037             while( *++sep >= '0' && *sep <= '9'); // strip time\r
12038             if(sec >= 0)\r
12039             while( *++sep >= '0' && *sep <= '9'); // strip seconds\r
12040             if(deci >= 0)\r
12041             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds\r
12042             while(*sep == ' ') sep++;\r
12043         }\r
12044 \r
12045         if( depth <= 0 ) {\r
12046             return text;\r
12047         }\r
12048 \r
12049         if( time < 0 ) {\r
12050             time = -1;\r
12051         }\r
12052 \r
12053         pvInfoList[index-1].depth = depth;\r
12054         pvInfoList[index-1].score = score;\r
12055         pvInfoList[index-1].time  = 10*time; // centi-sec\r
12056     }\r
12057     return sep;\r
12058 }\r
12059 \r
12060 void\r
12061 SendToProgram(message, cps)\r
12062      char *message;\r
12063      ChessProgramState *cps;\r
12064 {\r
12065     int count, outCount, error;\r
12066     char buf[MSG_SIZ];\r
12067 \r
12068     if (cps->pr == NULL) return;\r
12069     Attention(cps);\r
12070     \r
12071     if (appData.debugMode) {\r
12072         TimeMark now;\r
12073         GetTimeMark(&now);\r
12074         fprintf(debugFP, "%ld >%-6s: %s", \r
12075                 SubtractTimeMarks(&now, &programStartTime),\r
12076                 cps->which, message);\r
12077     }\r
12078     \r
12079     count = strlen(message);\r
12080     outCount = OutputToProcess(cps->pr, message, count, &error);\r
12081     if (outCount < count && !exiting \r
12082                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */\r
12083         sprintf(buf, _("Error writing to %s chess program"), cps->which);\r
12084         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
12085             if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
12086                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
12087                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);\r
12088             } else {\r
12089                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
12090             }\r
12091             gameInfo.resultDetails = buf;\r
12092         }\r
12093         DisplayFatalError(buf, error, 1);\r
12094     }\r
12095 }\r
12096 \r
12097 void\r
12098 ReceiveFromProgram(isr, closure, message, count, error)\r
12099      InputSourceRef isr;\r
12100      VOIDSTAR closure;\r
12101      char *message;\r
12102      int count;\r
12103      int error;\r
12104 {\r
12105     char *end_str;\r
12106     char buf[MSG_SIZ];\r
12107     ChessProgramState *cps = (ChessProgramState *)closure;\r
12108 \r
12109     if (isr != cps->isr) return; /* Killed intentionally */\r
12110     if (count <= 0) {\r
12111         if (count == 0) {\r
12112             sprintf(buf,\r
12113                     _("Error: %s chess program (%s) exited unexpectedly"),\r
12114                     cps->which, cps->program);\r
12115         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
12116                 if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
12117                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
12118                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);\r
12119                 } else {\r
12120                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
12121                 }\r
12122                 gameInfo.resultDetails = buf;\r
12123             }\r
12124             RemoveInputSource(cps->isr);\r
12125             DisplayFatalError(buf, 0, 1);\r
12126         } else {\r
12127             sprintf(buf,\r
12128                     _("Error reading from %s chess program (%s)"),\r
12129                     cps->which, cps->program);\r
12130             RemoveInputSource(cps->isr);\r
12131 \r
12132             /* [AS] Program is misbehaving badly... kill it */\r
12133             if( count == -2 ) {\r
12134                 DestroyChildProcess( cps->pr, 9 );\r
12135                 cps->pr = NoProc;\r
12136             }\r
12137 \r
12138             DisplayFatalError(buf, error, 1);\r
12139         }\r
12140         return;\r
12141     }\r
12142     \r
12143     if ((end_str = strchr(message, '\r')) != NULL)\r
12144       *end_str = NULLCHAR;\r
12145     if ((end_str = strchr(message, '\n')) != NULL)\r
12146       *end_str = NULLCHAR;\r
12147     \r
12148     if (appData.debugMode) {\r
12149         TimeMark now; int print = 1;\r
12150         char *quote = ""; char c; int i;\r
12151 \r
12152         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */\r
12153                 char start = message[0];\r
12154                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing\r
12155                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && \r
12156                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&\r
12157                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&\r
12158                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&\r
12159                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&\r
12160                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')\r
12161                         { quote = "# "; print = (appData.engineComments == 2); }\r
12162                 message[0] = start; // restore original message\r
12163         }\r
12164         if(print) {\r
12165                 GetTimeMark(&now);\r
12166                 fprintf(debugFP, "%ld <%-6s: %s%s\n", \r
12167                         SubtractTimeMarks(&now, &programStartTime), cps->which, \r
12168                         quote,\r
12169                         message);\r
12170         }\r
12171     }\r
12172 \r
12173     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */\r
12174     if (appData.icsEngineAnalyze) {\r
12175         if (strstr(message, "whisper") != NULL ||\r
12176              strstr(message, "kibitz") != NULL || \r
12177             strstr(message, "tellics") != NULL) return;\r
12178     }\r
12179 \r
12180     HandleMachineMove(message, cps);\r
12181 }\r
12182 \r
12183 \r
12184 void\r
12185 SendTimeControl(cps, mps, tc, inc, sd, st)\r
12186      ChessProgramState *cps;\r
12187      int mps, inc, sd, st;\r
12188      long tc;\r
12189 {\r
12190     char buf[MSG_SIZ];\r
12191     int seconds;\r
12192 \r
12193     if( timeControl_2 > 0 ) {\r
12194         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {\r
12195             tc = timeControl_2;\r
12196         }\r
12197     }\r
12198     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */\r
12199     inc /= cps->timeOdds;\r
12200     st  /= cps->timeOdds;\r
12201 \r
12202     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */\r
12203 \r
12204     if (st > 0) {\r
12205       /* Set exact time per move, normally using st command */\r
12206       if (cps->stKludge) {\r
12207         /* GNU Chess 4 has no st command; uses level in a nonstandard way */\r
12208         seconds = st % 60;\r
12209         if (seconds == 0) {\r
12210           sprintf(buf, "level 1 %d\n", st/60);\r
12211         } else {\r
12212           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);\r
12213         }\r
12214       } else {\r
12215         sprintf(buf, "st %d\n", st);\r
12216       }\r
12217     } else {\r
12218       /* Set conventional or incremental time control, using level command */\r
12219       if (seconds == 0) {\r
12220         /* Note old gnuchess bug -- minutes:seconds used to not work.\r
12221            Fixed in later versions, but still avoid :seconds\r
12222            when seconds is 0. */\r
12223         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);\r
12224       } else {\r
12225         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,\r
12226                 seconds, inc/1000);\r
12227       }\r
12228     }\r
12229     SendToProgram(buf, cps);\r
12230 \r
12231     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */\r
12232     /* Orthogonally, limit search to given depth */\r
12233     if (sd > 0) {\r
12234       if (cps->sdKludge) {\r
12235         sprintf(buf, "depth\n%d\n", sd);\r
12236       } else {\r
12237         sprintf(buf, "sd %d\n", sd);\r
12238       }\r
12239       SendToProgram(buf, cps);\r
12240     }\r
12241 \r
12242     if(cps->nps > 0) { /* [HGM] nps */\r
12243         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!\r
12244         else {\r
12245                 sprintf(buf, "nps %d\n", cps->nps);\r
12246               SendToProgram(buf, cps);\r
12247         }\r
12248     }\r
12249 }\r
12250 \r
12251 ChessProgramState *WhitePlayer()\r
12252 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */\r
12253 {\r
12254     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || \r
12255        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)\r
12256         return &second;\r
12257     return &first;\r
12258 }\r
12259 \r
12260 void\r
12261 SendTimeRemaining(cps, machineWhite)\r
12262      ChessProgramState *cps;\r
12263      int /*boolean*/ machineWhite;\r
12264 {\r
12265     char message[MSG_SIZ];\r
12266     long time, otime;\r
12267 \r
12268     /* Note: this routine must be called when the clocks are stopped\r
12269        or when they have *just* been set or switched; otherwise\r
12270        it will be off by the time since the current tick started.\r
12271     */\r
12272     if (machineWhite) {\r
12273         time = whiteTimeRemaining / 10;\r
12274         otime = blackTimeRemaining / 10;\r
12275     } else {\r
12276         time = blackTimeRemaining / 10;\r
12277         otime = whiteTimeRemaining / 10;\r
12278     }\r
12279     /* [HGM] translate opponent's time by time-odds factor */\r
12280     otime = (otime * cps->other->timeOdds) / cps->timeOdds;\r
12281     if (appData.debugMode) {\r
12282         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);\r
12283     }\r
12284 \r
12285     if (time <= 0) time = 1;\r
12286     if (otime <= 0) otime = 1;\r
12287     \r
12288     sprintf(message, "time %ld\n", time);\r
12289     SendToProgram(message, cps);\r
12290 \r
12291     sprintf(message, "otim %ld\n", otime);\r
12292     SendToProgram(message, cps);\r
12293 }\r
12294 \r
12295 int\r
12296 BoolFeature(p, name, loc, cps)\r
12297      char **p;\r
12298      char *name;\r
12299      int *loc;\r
12300      ChessProgramState *cps;\r
12301 {\r
12302   char buf[MSG_SIZ];\r
12303   int len = strlen(name);\r
12304   int val;\r
12305   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
12306     (*p) += len + 1;\r
12307     sscanf(*p, "%d", &val);\r
12308     *loc = (val != 0);\r
12309     while (**p && **p != ' ') (*p)++;\r
12310     sprintf(buf, "accepted %s\n", name);\r
12311     SendToProgram(buf, cps);\r
12312     return TRUE;\r
12313   }\r
12314   return FALSE;\r
12315 }\r
12316 \r
12317 int\r
12318 IntFeature(p, name, loc, cps)\r
12319      char **p;\r
12320      char *name;\r
12321      int *loc;\r
12322      ChessProgramState *cps;\r
12323 {\r
12324   char buf[MSG_SIZ];\r
12325   int len = strlen(name);\r
12326   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
12327     (*p) += len + 1;\r
12328     sscanf(*p, "%d", loc);\r
12329     while (**p && **p != ' ') (*p)++;\r
12330     sprintf(buf, "accepted %s\n", name);\r
12331     SendToProgram(buf, cps);\r
12332     return TRUE;\r
12333   }\r
12334   return FALSE;\r
12335 }\r
12336 \r
12337 int\r
12338 StringFeature(p, name, loc, cps)\r
12339      char **p;\r
12340      char *name;\r
12341      char loc[];\r
12342      ChessProgramState *cps;\r
12343 {\r
12344   char buf[MSG_SIZ];\r
12345   int len = strlen(name);\r
12346   if (strncmp((*p), name, len) == 0\r
12347       && (*p)[len] == '=' && (*p)[len+1] == '\"') {\r
12348     (*p) += len + 2;\r
12349     sscanf(*p, "%[^\"]", loc);\r
12350     while (**p && **p != '\"') (*p)++;\r
12351     if (**p == '\"') (*p)++;\r
12352     sprintf(buf, "accepted %s\n", name);\r
12353     SendToProgram(buf, cps);\r
12354     return TRUE;\r
12355   }\r
12356   return FALSE;\r
12357 }\r
12358 \r
12359 int \r
12360 ParseOption(Option *opt, ChessProgramState *cps)\r
12361 // [HGM] options: process the string that defines an engine option, and determine\r
12362 // name, type, default value, and allowed value range\r
12363 {\r
12364         char *p, *q, buf[MSG_SIZ];\r
12365         int n, min = (-1)<<31, max = 1<<31, def;\r
12366 \r
12367         if(p = strstr(opt->name, " -spin ")) {\r
12368             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;\r
12369             if(max < min) max = min; // enforce consistency\r
12370             if(def < min) def = min;\r
12371             if(def > max) def = max;\r
12372             opt->value = def;\r
12373             opt->min = min;\r
12374             opt->max = max;\r
12375             opt->type = Spin;\r
12376         } else if(p = strstr(opt->name, " -string ")) {\r
12377             opt->textValue = p+9;\r
12378             opt->type = TextBox;\r
12379         } else if(p = strstr(opt->name, " -check ")) {\r
12380             if(sscanf(p, " -check %d", &def) < 1) return FALSE;\r
12381             opt->value = (def != 0);\r
12382             opt->type = CheckBox;\r
12383         } else if(p = strstr(opt->name, " -combo ")) {\r
12384             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type\r
12385             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices\r
12386             opt->value = n = 0;\r
12387             while(q = StrStr(q, " /// ")) {\r
12388                 n++; *q = 0;    // count choices, and null-terminate each of them\r
12389                 q += 5;\r
12390                 if(*q == '*') { // remember default, which is marked with * prefix\r
12391                     q++;\r
12392                     opt->value = n;\r
12393                 }\r
12394                 cps->comboList[cps->comboCnt++] = q;\r
12395             }\r
12396             cps->comboList[cps->comboCnt++] = NULL;\r
12397             opt->max = n + 1;\r
12398             opt->type = ComboBox;\r
12399         } else if(p = strstr(opt->name, " -button")) {\r
12400             opt->type = Button;\r
12401         } else if(p = strstr(opt->name, " -save")) {\r
12402             opt->type = SaveButton;\r
12403         } else return FALSE;\r
12404         *p = 0; // terminate option name\r
12405         // now look if the command-line options define a setting for this engine option.\r
12406         if(cps->optionSettings && cps->optionSettings[0])\r
12407             p = strstr(cps->optionSettings, opt->name); else p = NULL;\r
12408         if(p && (p == cps->optionSettings || p[-1] == ',')) {\r
12409                 sprintf(buf, "option %s", p);\r
12410                 if(p = strstr(buf, ",")) *p = 0;\r
12411                 strcat(buf, "\n");\r
12412                 SendToProgram(buf, cps);\r
12413         }\r
12414         return TRUE;\r
12415 }\r
12416 \r
12417 void\r
12418 FeatureDone(cps, val)\r
12419      ChessProgramState* cps;\r
12420      int val;\r
12421 {\r
12422   DelayedEventCallback cb = GetDelayedEvent();\r
12423   if ((cb == InitBackEnd3 && cps == &first) ||\r
12424       (cb == TwoMachinesEventIfReady && cps == &second)) {\r
12425     CancelDelayedEvent();\r
12426     ScheduleDelayedEvent(cb, val ? 1 : 3600000);\r
12427   }\r
12428   cps->initDone = val;\r
12429 }\r
12430 \r
12431 /* Parse feature command from engine */\r
12432 void\r
12433 ParseFeatures(args, cps)\r
12434      char* args;\r
12435      ChessProgramState *cps;  \r
12436 {\r
12437   char *p = args;\r
12438   char *q;\r
12439   int val;\r
12440   char buf[MSG_SIZ];\r
12441 \r
12442   for (;;) {\r
12443     while (*p == ' ') p++;\r
12444     if (*p == NULLCHAR) return;\r
12445 \r
12446     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;\r
12447     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    \r
12448     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    \r
12449     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    \r
12450     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    \r
12451     if (BoolFeature(&p, "reuse", &val, cps)) {\r
12452       /* Engine can disable reuse, but can't enable it if user said no */\r
12453       if (!val) cps->reuse = FALSE;\r
12454       continue;\r
12455     }\r
12456     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;\r
12457     if (StringFeature(&p, "myname", &cps->tidy, cps)) {\r
12458       if (gameMode == TwoMachinesPlay) {\r
12459         DisplayTwoMachinesTitle();\r
12460       } else {\r
12461         DisplayTitle("");\r
12462       }\r
12463       continue;\r
12464     }\r
12465     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;\r
12466     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;\r
12467     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;\r
12468     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;\r
12469     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;\r
12470     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;\r
12471     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;\r
12472     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;\r
12473     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */\r
12474     if (IntFeature(&p, "done", &val, cps)) {\r
12475       FeatureDone(cps, val);\r
12476       continue;\r
12477     }\r
12478     /* Added by Tord: */\r
12479     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;\r
12480     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;\r
12481     /* End of additions by Tord */\r
12482 \r
12483     /* [HGM] added features: */\r
12484     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;\r
12485     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;\r
12486     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;\r
12487     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;\r
12488     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;\r
12489     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;\r
12490     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {\r
12491         ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature\r
12492         if(cps->nrOptions >= MAX_OPTIONS) {\r
12493             cps->nrOptions--;\r
12494             sprintf(buf, "%s engine has too many options\n", cps->which);\r
12495             DisplayError(buf, 0);\r
12496         }\r
12497         continue;\r
12498     }\r
12499     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;\r
12500     /* End of additions by HGM */\r
12501 \r
12502     /* unknown feature: complain and skip */\r
12503     q = p;\r
12504     while (*q && *q != '=') q++;\r
12505     sprintf(buf, "rejected %.*s\n", q-p, p);\r
12506     SendToProgram(buf, cps);\r
12507     p = q;\r
12508     if (*p == '=') {\r
12509       p++;\r
12510       if (*p == '\"') {\r
12511         p++;\r
12512         while (*p && *p != '\"') p++;\r
12513         if (*p == '\"') p++;\r
12514       } else {\r
12515         while (*p && *p != ' ') p++;\r
12516       }\r
12517     }\r
12518   }\r
12519 \r
12520 }\r
12521 \r
12522 void\r
12523 PeriodicUpdatesEvent(newState)\r
12524      int newState;\r
12525 {\r
12526     if (newState == appData.periodicUpdates)\r
12527       return;\r
12528 \r
12529     appData.periodicUpdates=newState;\r
12530 \r
12531     /* Display type changes, so update it now */\r
12532     DisplayAnalysis();\r
12533 \r
12534     /* Get the ball rolling again... */\r
12535     if (newState) {\r
12536         AnalysisPeriodicEvent(1);\r
12537         StartAnalysisClock();\r
12538     }\r
12539 }\r
12540 \r
12541 void\r
12542 PonderNextMoveEvent(newState)\r
12543      int newState;\r
12544 {\r
12545     if (newState == appData.ponderNextMove) return;\r
12546     if (gameMode == EditPosition) EditPositionDone();\r
12547     if (newState) {\r
12548         SendToProgram("hard\n", &first);\r
12549         if (gameMode == TwoMachinesPlay) {\r
12550             SendToProgram("hard\n", &second);\r
12551         }\r
12552     } else {\r
12553         SendToProgram("easy\n", &first);\r
12554         thinkOutput[0] = NULLCHAR;\r
12555         if (gameMode == TwoMachinesPlay) {\r
12556             SendToProgram("easy\n", &second);\r
12557         }\r
12558     }\r
12559     appData.ponderNextMove = newState;\r
12560 }\r
12561 \r
12562 void\r
12563 NewSettingEvent(option, command, value)\r
12564      char *command;\r
12565      int option, value;\r
12566 {\r
12567     char buf[MSG_SIZ];\r
12568 \r
12569     if (gameMode == EditPosition) EditPositionDone();\r
12570     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);\r
12571     SendToProgram(buf, &first);\r
12572     if (gameMode == TwoMachinesPlay) {\r
12573         SendToProgram(buf, &second);\r
12574     }\r
12575 }\r
12576 \r
12577 void\r
12578 ShowThinkingEvent()\r
12579 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup\r
12580 {\r
12581     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated\r
12582     int newState = appData.showThinking\r
12583         // [HGM] thinking: other features now need thinking output as well\r
12584         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();\r
12585     \r
12586     if (oldState == newState) return;\r
12587     oldState = newState;\r
12588     if (gameMode == EditPosition) EditPositionDone();\r
12589     if (oldState) {\r
12590         SendToProgram("post\n", &first);\r
12591         if (gameMode == TwoMachinesPlay) {\r
12592             SendToProgram("post\n", &second);\r
12593         }\r
12594     } else {\r
12595         SendToProgram("nopost\n", &first);\r
12596         thinkOutput[0] = NULLCHAR;\r
12597         if (gameMode == TwoMachinesPlay) {\r
12598             SendToProgram("nopost\n", &second);\r
12599         }\r
12600     }\r
12601 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!\r
12602 }\r
12603 \r
12604 void\r
12605 AskQuestionEvent(title, question, replyPrefix, which)\r
12606      char *title; char *question; char *replyPrefix; char *which;\r
12607 {\r
12608   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;\r
12609   if (pr == NoProc) return;\r
12610   AskQuestion(title, question, replyPrefix, pr);\r
12611 }\r
12612 \r
12613 void\r
12614 DisplayMove(moveNumber)\r
12615      int moveNumber;\r
12616 {\r
12617     char message[MSG_SIZ];\r
12618     char res[MSG_SIZ];\r
12619     char cpThinkOutput[MSG_SIZ];\r
12620 \r
12621     if(appData.noGUI) return; // [HGM] fast: suppress display of moves\r
12622     \r
12623     if (moveNumber == forwardMostMove - 1 || \r
12624         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
12625 \r
12626         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));\r
12627 \r
12628         if (strchr(cpThinkOutput, '\n')) {\r
12629             *strchr(cpThinkOutput, '\n') = NULLCHAR;\r
12630         }\r
12631     } else {\r
12632         *cpThinkOutput = NULLCHAR;\r
12633     }\r
12634 \r
12635     /* [AS] Hide thinking from human user */\r
12636     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {\r
12637         *cpThinkOutput = NULLCHAR;\r
12638         if( thinkOutput[0] != NULLCHAR ) {\r
12639             int i;\r
12640 \r
12641             for( i=0; i<=hiddenThinkOutputState; i++ ) {\r
12642                 cpThinkOutput[i] = '.';\r
12643             }\r
12644             cpThinkOutput[i] = NULLCHAR;\r
12645             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;\r
12646         }\r
12647     }\r
12648 \r
12649     if (moveNumber == forwardMostMove - 1 &&\r
12650         gameInfo.resultDetails != NULL) {\r
12651         if (gameInfo.resultDetails[0] == NULLCHAR) {\r
12652             sprintf(res, " %s", PGNResult(gameInfo.result));\r
12653         } else {\r
12654             sprintf(res, " {%s} %s",\r
12655                     gameInfo.resultDetails, PGNResult(gameInfo.result));\r
12656         }\r
12657     } else {\r
12658         res[0] = NULLCHAR;\r
12659     }\r
12660 \r
12661     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
12662         DisplayMessage(res, cpThinkOutput);\r
12663     } else {\r
12664         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,\r
12665                 WhiteOnMove(moveNumber) ? " " : ".. ",\r
12666                 parseList[moveNumber], res);\r
12667         DisplayMessage(message, cpThinkOutput);\r
12668     }\r
12669 }\r
12670 \r
12671 void\r
12672 DisplayAnalysisText(text)\r
12673      char *text;\r
12674 {\r
12675     char buf[MSG_SIZ];\r
12676 \r
12677     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile \r
12678                || appData.icsEngineAnalyze) {\r
12679         sprintf(buf, "Analysis (%s)", first.tidy);\r
12680         AnalysisPopUp(buf, text);\r
12681     }\r
12682 }\r
12683 \r
12684 static int\r
12685 only_one_move(str)\r
12686      char *str;\r
12687 {\r
12688     while (*str && isspace(*str)) ++str;\r
12689     while (*str && !isspace(*str)) ++str;\r
12690     if (!*str) return 1;\r
12691     while (*str && isspace(*str)) ++str;\r
12692     if (!*str) return 1;\r
12693     return 0;\r
12694 }\r
12695 \r
12696 void\r
12697 DisplayAnalysis()\r
12698 {\r
12699     char buf[MSG_SIZ];\r
12700     char lst[MSG_SIZ / 2];\r
12701     double nps;\r
12702     static char *xtra[] = { "", " (--)", " (++)" };\r
12703     int h, m, s, cs;\r
12704   \r
12705     if (programStats.time == 0) {\r
12706         programStats.time = 1;\r
12707     }\r
12708   \r
12709     if (programStats.got_only_move) {\r
12710         safeStrCpy(buf, programStats.movelist, sizeof(buf));\r
12711     } else {\r
12712         safeStrCpy( lst, programStats.movelist, sizeof(lst));\r
12713 \r
12714         nps = (u64ToDouble(programStats.nodes) /\r
12715              ((double)programStats.time /100.0));\r
12716 \r
12717         cs = programStats.time % 100;\r
12718         s = programStats.time / 100;\r
12719         h = (s / (60*60));\r
12720         s = s - h*60*60;\r
12721         m = (s/60);\r
12722         s = s - m*60;\r
12723 \r
12724         if (programStats.moves_left > 0 && appData.periodicUpdates) {\r
12725           if (programStats.move_name[0] != NULLCHAR) {\r
12726             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12727                     programStats.depth,\r
12728                     programStats.nr_moves-programStats.moves_left,\r
12729                     programStats.nr_moves, programStats.move_name,\r
12730                     ((float)programStats.score)/100.0, lst,\r
12731                     only_one_move(lst)?\r
12732                     xtra[programStats.got_fail] : "",\r
12733                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12734           } else {\r
12735             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12736                     programStats.depth,\r
12737                     programStats.nr_moves-programStats.moves_left,\r
12738                     programStats.nr_moves, ((float)programStats.score)/100.0,\r
12739                     lst,\r
12740                     only_one_move(lst)?\r
12741                     xtra[programStats.got_fail] : "",\r
12742                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12743           }\r
12744         } else {\r
12745             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12746                     programStats.depth,\r
12747                     ((float)programStats.score)/100.0,\r
12748                     lst,\r
12749                     only_one_move(lst)?\r
12750                     xtra[programStats.got_fail] : "",\r
12751                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12752         }\r
12753     }\r
12754     DisplayAnalysisText(buf);\r
12755 }\r
12756 \r
12757 void\r
12758 DisplayComment(moveNumber, text)\r
12759      int moveNumber;\r
12760      char *text;\r
12761 {\r
12762     char title[MSG_SIZ];\r
12763     char buf[8000]; // comment can be long!\r
12764     int score, depth;\r
12765 \r
12766     if( appData.autoDisplayComment ) {\r
12767         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
12768             strcpy(title, "Comment");\r
12769         } else {\r
12770             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,\r
12771                     WhiteOnMove(moveNumber) ? " " : ".. ",\r
12772                     parseList[moveNumber]);\r
12773         }\r
12774     } else title[0] = 0;\r
12775 \r
12776     // [HGM] PV info: display PV info together with (or as) comment\r
12777     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {\r
12778         if(text == NULL) text = "";                                           \r
12779         score = pvInfoList[moveNumber].score;\r
12780         sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,\r
12781                               depth, (pvInfoList[moveNumber].time+50)/100, text);\r
12782         CommentPopUp(title, buf);\r
12783     } else\r
12784     if (text != NULL)\r
12785         CommentPopUp(title, text);\r
12786 }\r
12787 \r
12788 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it\r
12789  * might be busy thinking or pondering.  It can be omitted if your\r
12790  * gnuchess is configured to stop thinking immediately on any user\r
12791  * input.  However, that gnuchess feature depends on the FIONREAD\r
12792  * ioctl, which does not work properly on some flavors of Unix.\r
12793  */\r
12794 void\r
12795 Attention(cps)\r
12796      ChessProgramState *cps;\r
12797 {\r
12798 #if ATTENTION\r
12799     if (!cps->useSigint) return;\r
12800     if (appData.noChessProgram || (cps->pr == NoProc)) return;\r
12801     switch (gameMode) {\r
12802       case MachinePlaysWhite:\r
12803       case MachinePlaysBlack:\r
12804       case TwoMachinesPlay:\r
12805       case IcsPlayingWhite:\r
12806       case IcsPlayingBlack:\r
12807       case AnalyzeMode:\r
12808       case AnalyzeFile:\r
12809         /* Skip if we know it isn't thinking */\r
12810         if (!cps->maybeThinking) return;\r
12811         if (appData.debugMode)\r
12812           fprintf(debugFP, "Interrupting %s\n", cps->which);\r
12813         InterruptChildProcess(cps->pr);\r
12814         cps->maybeThinking = FALSE;\r
12815         break;\r
12816       default:\r
12817         break;\r
12818     }\r
12819 #endif /*ATTENTION*/\r
12820 }\r
12821 \r
12822 int\r
12823 CheckFlags()\r
12824 {\r
12825     if (whiteTimeRemaining <= 0) {\r
12826         if (!whiteFlag) {\r
12827             whiteFlag = TRUE;\r
12828             if (appData.icsActive) {\r
12829                 if (appData.autoCallFlag &&\r
12830                     gameMode == IcsPlayingBlack && !blackFlag) {\r
12831                   SendToICS(ics_prefix);\r
12832                   SendToICS("flag\n");\r
12833                 }\r
12834             } else {\r
12835                 if (blackFlag) {\r
12836                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));\r
12837                 } else {\r
12838                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));\r
12839                     if (appData.autoCallFlag) {\r
12840                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);\r
12841                         return TRUE;\r
12842                     }\r
12843                 }\r
12844             }\r
12845         }\r
12846     }\r
12847     if (blackTimeRemaining <= 0) {\r
12848         if (!blackFlag) {\r
12849             blackFlag = TRUE;\r
12850             if (appData.icsActive) {\r
12851                 if (appData.autoCallFlag &&\r
12852                     gameMode == IcsPlayingWhite && !whiteFlag) {\r
12853                   SendToICS(ics_prefix);\r
12854                   SendToICS("flag\n");\r
12855                 }\r
12856             } else {\r
12857                 if (whiteFlag) {\r
12858                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));\r
12859                 } else {\r
12860                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));\r
12861                     if (appData.autoCallFlag) {\r
12862                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);\r
12863                         return TRUE;\r
12864                     }\r
12865                 }\r
12866             }\r
12867         }\r
12868     }\r
12869     return FALSE;\r
12870 }\r
12871 \r
12872 void\r
12873 CheckTimeControl()\r
12874 {\r
12875     if (!appData.clockMode || appData.icsActive ||\r
12876         gameMode == PlayFromGameFile || forwardMostMove == 0) return;\r
12877 \r
12878     /*\r
12879      * add time to clocks when time control is achieved ([HGM] now also used for increment)\r
12880      */\r
12881     if ( !WhiteOnMove(forwardMostMove) )\r
12882         /* White made time control */\r
12883         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
12884         /* [HGM] time odds: correct new time quota for time odds! */\r
12885                                             / WhitePlayer()->timeOdds;\r
12886       else\r
12887         /* Black made time control */\r
12888         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
12889                                             / WhitePlayer()->other->timeOdds;\r
12890 }\r
12891 \r
12892 void\r
12893 DisplayBothClocks()\r
12894 {\r
12895     int wom = gameMode == EditPosition ?\r
12896       !blackPlaysFirst : WhiteOnMove(currentMove);\r
12897     DisplayWhiteClock(whiteTimeRemaining, wom);\r
12898     DisplayBlackClock(blackTimeRemaining, !wom);\r
12899 }\r
12900 \r
12901 \r
12902 /* Timekeeping seems to be a portability nightmare.  I think everyone\r
12903    has ftime(), but I'm really not sure, so I'm including some ifdefs\r
12904    to use other calls if you don't.  Clocks will be less accurate if\r
12905    you have neither ftime nor gettimeofday.\r
12906 */\r
12907 \r
12908 /* VS 2008 requires the #include outside of the function */\r
12909 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME\r
12910 #include <sys/timeb.h>\r
12911 #endif\r
12912 \r
12913 /* Get the current time as a TimeMark */\r
12914 void\r
12915 GetTimeMark(tm)\r
12916      TimeMark *tm;\r
12917 {\r
12918 #if HAVE_GETTIMEOFDAY\r
12919 \r
12920     struct timeval timeVal;\r
12921     struct timezone timeZone;\r
12922 \r
12923     gettimeofday(&timeVal, &timeZone);\r
12924     tm->sec = (long) timeVal.tv_sec; \r
12925     tm->ms = (int) (timeVal.tv_usec / 1000L);\r
12926 \r
12927 #else /*!HAVE_GETTIMEOFDAY*/\r
12928 #if HAVE_FTIME\r
12929 \r
12930 // include <sys/timeb.h> / moved to just above start of function\r
12931     struct timeb timeB;\r
12932 \r
12933     ftime(&timeB);\r
12934     tm->sec = (long) timeB.time;\r
12935     tm->ms = (int) timeB.millitm;\r
12936 \r
12937 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/\r
12938     tm->sec = (long) time(NULL);\r
12939     tm->ms = 0;\r
12940 #endif\r
12941 #endif\r
12942 }\r
12943 \r
12944 /* Return the difference in milliseconds between two\r
12945    time marks.  We assume the difference will fit in a long!\r
12946 */\r
12947 long\r
12948 SubtractTimeMarks(tm2, tm1)\r
12949      TimeMark *tm2, *tm1;\r
12950 {\r
12951     return 1000L*(tm2->sec - tm1->sec) +\r
12952            (long) (tm2->ms - tm1->ms);\r
12953 }\r
12954 \r
12955 \r
12956 /*\r
12957  * Code to manage the game clocks.\r
12958  *\r
12959  * In tournament play, black starts the clock and then white makes a move.\r
12960  * We give the human user a slight advantage if he is playing white---the\r
12961  * clocks don't run until he makes his first move, so it takes zero time.\r
12962  * Also, we don't account for network lag, so we could get out of sync\r
12963  * with GNU Chess's clock -- but then, referees are always right.  \r
12964  */\r
12965 \r
12966 static TimeMark tickStartTM;\r
12967 static long intendedTickLength;\r
12968 \r
12969 long\r
12970 NextTickLength(timeRemaining)\r
12971      long timeRemaining;\r
12972 {\r
12973     long nominalTickLength, nextTickLength;\r
12974 \r
12975     if (timeRemaining > 0L && timeRemaining <= 10000L)\r
12976       nominalTickLength = 100L;\r
12977     else\r
12978       nominalTickLength = 1000L;\r
12979     nextTickLength = timeRemaining % nominalTickLength;\r
12980     if (nextTickLength <= 0) nextTickLength += nominalTickLength;\r
12981 \r
12982     return nextTickLength;\r
12983 }\r
12984 \r
12985 /* Adjust clock one minute up or down */\r
12986 void\r
12987 AdjustClock(Boolean which, int dir)\r
12988 {\r
12989     if(which) blackTimeRemaining += 60000*dir;\r
12990     else      whiteTimeRemaining += 60000*dir;\r
12991     DisplayBothClocks();\r
12992 }\r
12993 \r
12994 /* Stop clocks and reset to a fresh time control */\r
12995 void\r
12996 ResetClocks() \r
12997 {\r
12998     (void) StopClockTimer();\r
12999     if (appData.icsActive) {\r
13000         whiteTimeRemaining = blackTimeRemaining = 0;\r
13001     } else { /* [HGM] correct new time quote for time odds */\r
13002         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;\r
13003         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;\r
13004     }\r
13005     if (whiteFlag || blackFlag) {\r
13006         DisplayTitle("");\r
13007         whiteFlag = blackFlag = FALSE;\r
13008     }\r
13009     DisplayBothClocks();\r
13010 }\r
13011 \r
13012 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */\r
13013 \r
13014 /* Decrement running clock by amount of time that has passed */\r
13015 void\r
13016 DecrementClocks()\r
13017 {\r
13018     long timeRemaining;\r
13019     long lastTickLength, fudge;\r
13020     TimeMark now;\r
13021 \r
13022     if (!appData.clockMode) return;\r
13023     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;\r
13024         \r
13025     GetTimeMark(&now);\r
13026 \r
13027     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13028 \r
13029     /* Fudge if we woke up a little too soon */\r
13030     fudge = intendedTickLength - lastTickLength;\r
13031     if (fudge < 0 || fudge > FUDGE) fudge = 0;\r
13032 \r
13033     if (WhiteOnMove(forwardMostMove)) {\r
13034         if(whiteNPS >= 0) lastTickLength = 0;\r
13035         timeRemaining = whiteTimeRemaining -= lastTickLength;\r
13036         DisplayWhiteClock(whiteTimeRemaining - fudge,\r
13037                           WhiteOnMove(currentMove));\r
13038     } else {\r
13039         if(blackNPS >= 0) lastTickLength = 0;\r
13040         timeRemaining = blackTimeRemaining -= lastTickLength;\r
13041         DisplayBlackClock(blackTimeRemaining - fudge,\r
13042                           !WhiteOnMove(currentMove));\r
13043     }\r
13044 \r
13045     if (CheckFlags()) return;\r
13046         \r
13047     tickStartTM = now;\r
13048     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;\r
13049     StartClockTimer(intendedTickLength);\r
13050 \r
13051     /* if the time remaining has fallen below the alarm threshold, sound the\r
13052      * alarm. if the alarm has sounded and (due to a takeback or time control\r
13053      * with increment) the time remaining has increased to a level above the\r
13054      * threshold, reset the alarm so it can sound again. \r
13055      */\r
13056     \r
13057     if (appData.icsActive && appData.icsAlarm) {\r
13058 \r
13059         /* make sure we are dealing with the user's clock */\r
13060         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||\r
13061                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))\r
13062            )) return;\r
13063 \r
13064         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {\r
13065             alarmSounded = FALSE;\r
13066         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { \r
13067             PlayAlarmSound();\r
13068             alarmSounded = TRUE;\r
13069         }\r
13070     }\r
13071 }\r
13072 \r
13073 \r
13074 /* A player has just moved, so stop the previously running\r
13075    clock and (if in clock mode) start the other one.\r
13076    We redisplay both clocks in case we're in ICS mode, because\r
13077    ICS gives us an update to both clocks after every move.\r
13078    Note that this routine is called *after* forwardMostMove\r
13079    is updated, so the last fractional tick must be subtracted\r
13080    from the color that is *not* on move now.\r
13081 */\r
13082 void\r
13083 SwitchClocks()\r
13084 {\r
13085     long lastTickLength;\r
13086     TimeMark now;\r
13087     int flagged = FALSE;\r
13088 \r
13089     GetTimeMark(&now);\r
13090 \r
13091     if (StopClockTimer() && appData.clockMode) {\r
13092         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13093         if (WhiteOnMove(forwardMostMove)) {\r
13094             if(blackNPS >= 0) lastTickLength = 0;\r
13095             blackTimeRemaining -= lastTickLength;\r
13096            /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
13097 //         if(pvInfoList[forwardMostMove-1].time == -1)\r
13098                  pvInfoList[forwardMostMove-1].time =               // use GUI time\r
13099                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;\r
13100         } else {\r
13101            if(whiteNPS >= 0) lastTickLength = 0;\r
13102            whiteTimeRemaining -= lastTickLength;\r
13103            /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
13104 //         if(pvInfoList[forwardMostMove-1].time == -1)\r
13105                  pvInfoList[forwardMostMove-1].time = \r
13106                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;\r
13107         }\r
13108         flagged = CheckFlags();\r
13109     }\r
13110     CheckTimeControl();\r
13111 \r
13112     if (flagged || !appData.clockMode) return;\r
13113 \r
13114     switch (gameMode) {\r
13115       case MachinePlaysBlack:\r
13116       case MachinePlaysWhite:\r
13117       case BeginningOfGame:\r
13118         if (pausing) return;\r
13119         break;\r
13120 \r
13121       case EditGame:\r
13122       case PlayFromGameFile:\r
13123       case IcsExamining:\r
13124         return;\r
13125 \r
13126       default:\r
13127         break;\r
13128     }\r
13129 \r
13130     tickStartTM = now;\r
13131     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
13132       whiteTimeRemaining : blackTimeRemaining);\r
13133     StartClockTimer(intendedTickLength);\r
13134 }\r
13135         \r
13136 \r
13137 /* Stop both clocks */\r
13138 void\r
13139 StopClocks()\r
13140 {       \r
13141     long lastTickLength;\r
13142     TimeMark now;\r
13143 \r
13144     if (!StopClockTimer()) return;\r
13145     if (!appData.clockMode) return;\r
13146 \r
13147     GetTimeMark(&now);\r
13148 \r
13149     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13150     if (WhiteOnMove(forwardMostMove)) {\r
13151         if(whiteNPS >= 0) lastTickLength = 0;\r
13152         whiteTimeRemaining -= lastTickLength;\r
13153         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));\r
13154     } else {\r
13155         if(blackNPS >= 0) lastTickLength = 0;\r
13156         blackTimeRemaining -= lastTickLength;\r
13157         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));\r
13158     }\r
13159     CheckFlags();\r
13160 }\r
13161         \r
13162 /* Start clock of player on move.  Time may have been reset, so\r
13163    if clock is already running, stop and restart it. */\r
13164 void\r
13165 StartClocks()\r
13166 {\r
13167     (void) StopClockTimer(); /* in case it was running already */\r
13168     DisplayBothClocks();\r
13169     if (CheckFlags()) return;\r
13170 \r
13171     if (!appData.clockMode) return;\r
13172     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;\r
13173 \r
13174     GetTimeMark(&tickStartTM);\r
13175     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
13176       whiteTimeRemaining : blackTimeRemaining);\r
13177 \r
13178    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */\r
13179     whiteNPS = blackNPS = -1; \r
13180     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'\r
13181        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white\r
13182         whiteNPS = first.nps;\r
13183     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'\r
13184        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black\r
13185         blackNPS = first.nps;\r
13186     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode\r
13187         whiteNPS = second.nps;\r
13188     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')\r
13189         blackNPS = second.nps;\r
13190     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);\r
13191 \r
13192     StartClockTimer(intendedTickLength);\r
13193 }\r
13194 \r
13195 char *\r
13196 TimeString(ms)\r
13197      long ms;\r
13198 {\r
13199     long second, minute, hour, day;\r
13200     char *sign = "";\r
13201     static char buf[32];\r
13202     \r
13203     if (ms > 0 && ms <= 9900) {\r
13204       /* convert milliseconds to tenths, rounding up */\r
13205       double tenths = floor( ((double)(ms + 99L)) / 100.00 );\r
13206 \r
13207       sprintf(buf, " %03.1f ", tenths/10.0);\r
13208       return buf;\r
13209     }\r
13210 \r
13211     /* convert milliseconds to seconds, rounding up */\r
13212     /* use floating point to avoid strangeness of integer division\r
13213        with negative dividends on many machines */\r
13214     second = (long) floor(((double) (ms + 999L)) / 1000.0);\r
13215 \r
13216     if (second < 0) {\r
13217         sign = "-";\r
13218         second = -second;\r
13219     }\r
13220     \r
13221     day = second / (60 * 60 * 24);\r
13222     second = second % (60 * 60 * 24);\r
13223     hour = second / (60 * 60);\r
13224     second = second % (60 * 60);\r
13225     minute = second / 60;\r
13226     second = second % 60;\r
13227     \r
13228     if (day > 0)\r
13229       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",\r
13230               sign, day, hour, minute, second);\r
13231     else if (hour > 0)\r
13232       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);\r
13233     else\r
13234       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);\r
13235     \r
13236     return buf;\r
13237 }\r
13238 \r
13239 \r
13240 /*\r
13241  * This is necessary because some C libraries aren't ANSI C compliant yet.\r
13242  */\r
13243 char *\r
13244 StrStr(string, match)\r
13245      char *string, *match;\r
13246 {\r
13247     int i, length;\r
13248     \r
13249     length = strlen(match);\r
13250     \r
13251     for (i = strlen(string) - length; i >= 0; i--, string++)\r
13252       if (!strncmp(match, string, length))\r
13253         return string;\r
13254     \r
13255     return NULL;\r
13256 }\r
13257 \r
13258 char *\r
13259 StrCaseStr(string, match)\r
13260      char *string, *match;\r
13261 {\r
13262     int i, j, length;\r
13263     \r
13264     length = strlen(match);\r
13265     \r
13266     for (i = strlen(string) - length; i >= 0; i--, string++) {\r
13267         for (j = 0; j < length; j++) {\r
13268             if (ToLower(match[j]) != ToLower(string[j]))\r
13269               break;\r
13270         }\r
13271         if (j == length) return string;\r
13272     }\r
13273 \r
13274     return NULL;\r
13275 }\r
13276 \r
13277 #ifndef _amigados\r
13278 int\r
13279 StrCaseCmp(s1, s2)\r
13280      char *s1, *s2;\r
13281 {\r
13282     char c1, c2;\r
13283     \r
13284     for (;;) {\r
13285         c1 = ToLower(*s1++);\r
13286         c2 = ToLower(*s2++);\r
13287         if (c1 > c2) return 1;\r
13288         if (c1 < c2) return -1;\r
13289         if (c1 == NULLCHAR) return 0;\r
13290     }\r
13291 }\r
13292 \r
13293 \r
13294 int\r
13295 ToLower(c)\r
13296      int c;\r
13297 {\r
13298     return isupper(c) ? tolower(c) : c;\r
13299 }\r
13300 \r
13301 \r
13302 int\r
13303 ToUpper(c)\r
13304      int c;\r
13305 {\r
13306     return islower(c) ? toupper(c) : c;\r
13307 }\r
13308 #endif /* !_amigados    */\r
13309 \r
13310 char *\r
13311 StrSave(s)\r
13312      char *s;\r
13313 {\r
13314     char *ret;\r
13315 \r
13316     if ((ret = (char *) malloc(strlen(s) + 1))) {\r
13317         strcpy(ret, s);\r
13318     }\r
13319     return ret;\r
13320 }\r
13321 \r
13322 char *\r
13323 StrSavePtr(s, savePtr)\r
13324      char *s, **savePtr;\r
13325 {\r
13326     if (*savePtr) {\r
13327         free(*savePtr);\r
13328     }\r
13329     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {\r
13330         strcpy(*savePtr, s);\r
13331     }\r
13332     return(*savePtr);\r
13333 }\r
13334 \r
13335 char *\r
13336 PGNDate()\r
13337 {\r
13338     time_t clock;\r
13339     struct tm *tm;\r
13340     char buf[MSG_SIZ];\r
13341 \r
13342     clock = time((time_t *)NULL);\r
13343     tm = localtime(&clock);\r
13344     sprintf(buf, "%04d.%02d.%02d",\r
13345             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);\r
13346     return StrSave(buf);\r
13347 }\r
13348 \r
13349 \r
13350 char *\r
13351 PositionToFEN(move, useFEN960)\r
13352      int move;\r
13353      int useFEN960;\r
13354 {\r
13355     int i, j, fromX, fromY, toX, toY;\r
13356     int whiteToPlay;\r
13357     char buf[128];\r
13358     char *p, *q;\r
13359     int emptycount;\r
13360     ChessSquare piece;\r
13361 \r
13362     whiteToPlay = (gameMode == EditPosition) ?\r
13363       !blackPlaysFirst : (move % 2 == 0);\r
13364     p = buf;\r
13365 \r
13366     /* Piece placement data */\r
13367     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
13368         emptycount = 0;\r
13369         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
13370             if (boards[move][i][j] == EmptySquare) {\r
13371                 emptycount++;\r
13372             } else { ChessSquare piece = boards[move][i][j];\r
13373                 if (emptycount > 0) {\r
13374                     if(emptycount<10) /* [HGM] can be >= 10 */\r
13375                         *p++ = '0' + emptycount;\r
13376                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
13377                     emptycount = 0;\r
13378                 }\r
13379                 if(PieceToChar(piece) == '+') {\r
13380                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */\r
13381                     *p++ = '+';\r
13382                     piece = (ChessSquare)(DEMOTED piece);\r
13383                 } \r
13384                 *p++ = PieceToChar(piece);\r
13385                 if(p[-1] == '~') {\r
13386                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */\r
13387                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));\r
13388                     *p++ = '~';\r
13389                 }\r
13390             }\r
13391         }\r
13392         if (emptycount > 0) {\r
13393             if(emptycount<10) /* [HGM] can be >= 10 */\r
13394                 *p++ = '0' + emptycount;\r
13395             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
13396             emptycount = 0;\r
13397         }\r
13398         *p++ = '/';\r
13399     }\r
13400     *(p - 1) = ' ';\r
13401 \r
13402     /* [HGM] print Crazyhouse or Shogi holdings */\r
13403     if( gameInfo.holdingsWidth ) {\r
13404         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */\r
13405         q = p;\r
13406         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */\r
13407             piece = boards[move][i][BOARD_WIDTH-1];\r
13408             if( piece != EmptySquare )\r
13409               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)\r
13410                   *p++ = PieceToChar(piece);\r
13411         }\r
13412         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */\r
13413             piece = boards[move][BOARD_HEIGHT-i-1][0];\r
13414             if( piece != EmptySquare )\r
13415               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)\r
13416                   *p++ = PieceToChar(piece);\r
13417         }\r
13418 \r
13419         if( q == p ) *p++ = '-';\r
13420         *p++ = ']';\r
13421         *p++ = ' ';\r
13422     }\r
13423 \r
13424     /* Active color */\r
13425     *p++ = whiteToPlay ? 'w' : 'b';\r
13426     *p++ = ' ';\r
13427 \r
13428   if(nrCastlingRights) {\r
13429      q = p;\r
13430      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {\r
13431        /* [HGM] write directly from rights */\r
13432            if(castlingRights[move][2] >= 0 &&\r
13433               castlingRights[move][0] >= 0   )\r
13434                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';\r
13435            if(castlingRights[move][2] >= 0 &&\r
13436               castlingRights[move][1] >= 0   )\r
13437                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';\r
13438            if(castlingRights[move][5] >= 0 &&\r
13439               castlingRights[move][3] >= 0   )\r
13440                 *p++ = castlingRights[move][3] + AAA;\r
13441            if(castlingRights[move][5] >= 0 &&\r
13442               castlingRights[move][4] >= 0   )\r
13443                 *p++ = castlingRights[move][4] + AAA;\r
13444      } else {\r
13445 \r
13446         /* [HGM] write true castling rights */\r
13447         if( nrCastlingRights == 6 ) {\r
13448             if(castlingRights[move][0] == BOARD_RGHT-1 &&\r
13449                castlingRights[move][2] >= 0  ) *p++ = 'K';\r
13450             if(castlingRights[move][1] == BOARD_LEFT &&\r
13451                castlingRights[move][2] >= 0  ) *p++ = 'Q';\r
13452             if(castlingRights[move][3] == BOARD_RGHT-1 &&\r
13453                castlingRights[move][5] >= 0  ) *p++ = 'k';\r
13454             if(castlingRights[move][4] == BOARD_LEFT &&\r
13455                castlingRights[move][5] >= 0  ) *p++ = 'q';\r
13456         }\r
13457      }\r
13458      if (q == p) *p++ = '-'; /* No castling rights */\r
13459      *p++ = ' ';\r
13460   }\r
13461 \r
13462   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
13463      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
13464     /* En passant target square */\r
13465     if (move > backwardMostMove) {\r
13466         fromX = moveList[move - 1][0] - AAA;\r
13467         fromY = moveList[move - 1][1] - ONE;\r
13468         toX = moveList[move - 1][2] - AAA;\r
13469         toY = moveList[move - 1][3] - ONE;\r
13470         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&\r
13471             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&\r
13472             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&\r
13473             fromX == toX) {\r
13474             /* 2-square pawn move just happened */\r
13475             *p++ = toX + AAA;\r
13476             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';\r
13477         } else {\r
13478             *p++ = '-';\r
13479         }\r
13480     } else {\r
13481         *p++ = '-';\r
13482     }\r
13483     *p++ = ' ';\r
13484   }\r
13485 \r
13486     /* [HGM] find reversible plies */\r
13487     {   int i = 0, j=move;\r
13488 \r
13489         if (appData.debugMode) { int k;\r
13490             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);\r
13491             for(k=backwardMostMove; k<=forwardMostMove; k++)\r
13492                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);\r
13493 \r
13494         }\r
13495 \r
13496         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;\r
13497         if( j == backwardMostMove ) i += initialRulePlies;\r
13498         sprintf(p, "%d ", i);\r
13499         p += i>=100 ? 4 : i >= 10 ? 3 : 2;\r
13500     }\r
13501     /* Fullmove number */\r
13502     sprintf(p, "%d", (move / 2) + 1);\r
13503     \r
13504     return StrSave(buf);\r
13505 }\r
13506 \r
13507 Boolean\r
13508 ParseFEN(board, blackPlaysFirst, fen)\r
13509     Board board;\r
13510      int *blackPlaysFirst;\r
13511      char *fen;\r
13512 {\r
13513     int i, j;\r
13514     char *p;\r
13515     int emptycount;\r
13516     ChessSquare piece;\r
13517 \r
13518     p = fen;\r
13519 \r
13520     /* [HGM] by default clear Crazyhouse holdings, if present */\r
13521     if(gameInfo.holdingsWidth) {\r
13522        for(i=0; i<BOARD_HEIGHT; i++) {\r
13523            board[i][0]             = EmptySquare; /* black holdings */\r
13524            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */\r
13525            board[i][1]             = (ChessSquare) 0; /* black counts */\r
13526            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */\r
13527        }\r
13528     }\r
13529 \r
13530     /* Piece placement data */\r
13531     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
13532         j = 0;\r
13533         for (;;) {\r
13534             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {\r
13535                 if (*p == '/') p++;\r
13536                 emptycount = gameInfo.boardWidth - j;\r
13537                 while (emptycount--)\r
13538                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13539                 break;\r
13540 #if(BOARD_SIZE >= 10)\r
13541             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */\r
13542                 p++; emptycount=10;\r
13543                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
13544                 while (emptycount--)\r
13545                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13546 #endif\r
13547             } else if (isdigit(*p)) {\r
13548                 emptycount = *p++ - '0';\r
13549                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */\r
13550                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
13551                 while (emptycount--)\r
13552                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13553             } else if (*p == '+' || isalpha(*p)) {\r
13554                 if (j >= gameInfo.boardWidth) return FALSE;\r
13555                 if(*p=='+') {\r
13556                     piece = CharToPiece(*++p);\r
13557                     if(piece == EmptySquare) return FALSE; /* unknown piece */\r
13558                     piece = (ChessSquare) (PROMOTED piece ); p++;\r
13559                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */\r
13560                 } else piece = CharToPiece(*p++);\r
13561 \r
13562                 if(piece==EmptySquare) return FALSE; /* unknown piece */\r
13563                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */\r
13564                     piece = (ChessSquare) (PROMOTED piece);\r
13565                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */\r
13566                     p++;\r
13567                 }\r
13568                 board[i][(j++)+gameInfo.holdingsWidth] = piece;\r
13569             } else {\r
13570                 return FALSE;\r
13571             }\r
13572         }\r
13573     }\r
13574     while (*p == '/' || *p == ' ') p++;\r
13575 \r
13576     /* [HGM] look for Crazyhouse holdings here */\r
13577     while(*p==' ') p++;\r
13578     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {\r
13579         if(*p == '[') p++;\r
13580         if(*p == '-' ) *p++; /* empty holdings */ else {\r
13581             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */\r
13582             /* if we would allow FEN reading to set board size, we would   */\r
13583             /* have to add holdings and shift the board read so far here   */\r
13584             while( (piece = CharToPiece(*p) ) != EmptySquare ) {\r
13585                 *p++;\r
13586                 if((int) piece >= (int) BlackPawn ) {\r
13587                     i = (int)piece - (int)BlackPawn;\r
13588                     i = PieceToNumber((ChessSquare)i);\r
13589                     if( i >= gameInfo.holdingsSize ) return FALSE;\r
13590                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */\r
13591                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */\r
13592                 } else {\r
13593                     i = (int)piece - (int)WhitePawn;\r
13594                     i = PieceToNumber((ChessSquare)i);\r
13595                     if( i >= gameInfo.holdingsSize ) return FALSE;\r
13596                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */\r
13597                     board[i][BOARD_WIDTH-2]++;          /* black holdings */\r
13598                 }\r
13599             }\r
13600         }\r
13601         if(*p == ']') *p++;\r
13602     }\r
13603 \r
13604     while(*p == ' ') p++;\r
13605 \r
13606     /* Active color */\r
13607     switch (*p++) {\r
13608       case 'w':\r
13609         *blackPlaysFirst = FALSE;\r
13610         break;\r
13611       case 'b': \r
13612         *blackPlaysFirst = TRUE;\r
13613         break;\r
13614       default:\r
13615         return FALSE;\r
13616     }\r
13617 \r
13618     /* [HGM] We NO LONGER ignore the rest of the FEN notation */\r
13619     /* return the extra info in global variiables             */\r
13620 \r
13621     /* set defaults in case FEN is incomplete */\r
13622     FENepStatus = EP_UNKNOWN;\r
13623     for(i=0; i<nrCastlingRights; i++ ) {\r
13624         FENcastlingRights[i] =\r
13625             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];\r
13626     }   /* assume possible unless obviously impossible */\r
13627     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;\r
13628     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;\r
13629     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;\r
13630     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;\r
13631     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;\r
13632     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;\r
13633     FENrulePlies = 0;\r
13634 \r
13635     while(*p==' ') p++;\r
13636     if(nrCastlingRights) {\r
13637       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {\r
13638           /* castling indicator present, so default becomes no castlings */\r
13639           for(i=0; i<nrCastlingRights; i++ ) {\r
13640                  FENcastlingRights[i] = -1;\r
13641           }\r
13642       }\r
13643       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||\r
13644              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&\r
13645              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||\r
13646              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {\r
13647         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;\r
13648 \r
13649         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {\r
13650             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;\r
13651             if(board[0             ][i] == WhiteKing) whiteKingFile = i;\r
13652         }\r
13653         switch(c) {\r
13654           case'K':\r
13655               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);\r
13656               FENcastlingRights[0] = i != whiteKingFile ? i : -1;\r
13657               FENcastlingRights[2] = whiteKingFile;\r
13658               break;\r
13659           case'Q':\r
13660               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);\r
13661               FENcastlingRights[1] = i != whiteKingFile ? i : -1;\r
13662               FENcastlingRights[2] = whiteKingFile;\r
13663               break;\r
13664           case'k':\r
13665               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);\r
13666               FENcastlingRights[3] = i != blackKingFile ? i : -1;\r
13667               FENcastlingRights[5] = blackKingFile;\r
13668               break;\r
13669           case'q':\r
13670               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);\r
13671               FENcastlingRights[4] = i != blackKingFile ? i : -1;\r
13672               FENcastlingRights[5] = blackKingFile;\r
13673           case '-':\r
13674               break;\r
13675           default: /* FRC castlings */\r
13676               if(c >= 'a') { /* black rights */\r
13677                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
13678                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;\r
13679                   if(i == BOARD_RGHT) break;\r
13680                   FENcastlingRights[5] = i;\r
13681                   c -= AAA;\r
13682                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||\r
13683                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;\r
13684                   if(c > i)\r
13685                       FENcastlingRights[3] = c;\r
13686                   else\r
13687                       FENcastlingRights[4] = c;\r
13688               } else { /* white rights */\r
13689                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
13690                     if(board[0][i] == WhiteKing) break;\r
13691                   if(i == BOARD_RGHT) break;\r
13692                   FENcastlingRights[2] = i;\r
13693                   c -= AAA - 'a' + 'A';\r
13694                   if(board[0][c] >= WhiteKing) break;\r
13695                   if(c > i)\r
13696                       FENcastlingRights[0] = c;\r
13697                   else\r
13698                       FENcastlingRights[1] = c;\r
13699               }\r
13700         }\r
13701       }\r
13702     if (appData.debugMode) {\r
13703         fprintf(debugFP, "FEN castling rights:");\r
13704         for(i=0; i<nrCastlingRights; i++)\r
13705         fprintf(debugFP, " %d", FENcastlingRights[i]);\r
13706         fprintf(debugFP, "\n");\r
13707     }\r
13708 \r
13709       while(*p==' ') p++;\r
13710     }\r
13711 \r
13712     /* read e.p. field in games that know e.p. capture */\r
13713     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
13714        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
13715       if(*p=='-') {\r
13716         p++; FENepStatus = EP_NONE;\r
13717       } else {\r
13718          char c = *p++ - AAA;\r
13719 \r
13720          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;\r
13721          if(*p >= '0' && *p <='9') *p++;\r
13722          FENepStatus = c;\r
13723       }\r
13724     }\r
13725 \r
13726 \r
13727     if(sscanf(p, "%d", &i) == 1) {\r
13728         FENrulePlies = i; /* 50-move ply counter */\r
13729         /* (The move number is still ignored)    */\r
13730     }\r
13731 \r
13732     return TRUE;\r
13733 }\r
13734       \r
13735 void\r
13736 EditPositionPasteFEN(char *fen)\r
13737 {\r
13738   if (fen != NULL) {\r
13739     Board initial_position;\r
13740 \r
13741     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {\r
13742       DisplayError(_("Bad FEN position in clipboard"), 0);\r
13743       return ;\r
13744     } else {\r
13745       int savedBlackPlaysFirst = blackPlaysFirst;\r
13746       EditPositionEvent();\r
13747       blackPlaysFirst = savedBlackPlaysFirst;\r
13748       CopyBoard(boards[0], initial_position);\r
13749           /* [HGM] copy FEN attributes as well */\r
13750           {   int i;\r
13751               initialRulePlies = FENrulePlies;\r
13752               epStatus[0] = FENepStatus;\r
13753               for( i=0; i<nrCastlingRights; i++ )\r
13754                   castlingRights[0][i] = FENcastlingRights[i];\r
13755           }\r
13756       EditPositionDone();\r
13757       DisplayBothClocks();\r
13758       DrawPosition(FALSE, boards[currentMove]);\r
13759     }\r
13760   }\r
13761 }\r