bugfix and enhancement in autoKibitz mode
[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   case VariantLosers:\r
355     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist\r
356     break;\r
357   case VariantAtomic:\r
358     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;\r
359     break;\r
360   case VariantKriegspiel:\r
361     flags |= F_KRIEGSPIEL_CAPTURE;\r
362     break;\r
363   case VariantCapaRandom: \r
364   case VariantFischeRandom:\r
365     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */\r
366   case VariantNoCastle:\r
367   case VariantShatranj:\r
368   case VariantCourier:\r
369     flags &= ~F_ALL_CASTLE_OK;\r
370     break;\r
371   default:\r
372     break;\r
373   }\r
374   return flags;\r
375 }\r
376 \r
377 FILE *gameFileFP, *debugFP;\r
378 \r
379 /* \r
380     [AS] Note: sometimes, the sscanf() function is used to parse the input\r
381     into a fixed-size buffer. Because of this, we must be prepared to\r
382     receive strings as long as the size of the input buffer, which is currently\r
383     set to 4K for Windows and 8K for the rest.\r
384     So, we must either allocate sufficiently large buffers here, or\r
385     reduce the size of the input buffer in the input reading part.\r
386 */\r
387 \r
388 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];\r
389 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];\r
390 char thinkOutput1[MSG_SIZ*10];\r
391 \r
392 ChessProgramState first, second;\r
393 \r
394 /* premove variables */\r
395 int premoveToX = 0;\r
396 int premoveToY = 0;\r
397 int premoveFromX = 0;\r
398 int premoveFromY = 0;\r
399 int premovePromoChar = 0;\r
400 int gotPremove = 0;\r
401 Boolean alarmSounded;\r
402 /* end premove variables */\r
403 \r
404 char *ics_prefix = "$";\r
405 int ics_type = ICS_GENERIC;\r
406 \r
407 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;\r
408 int pauseExamForwardMostMove = 0;\r
409 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;\r
410 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];\r
411 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;\r
412 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;\r
413 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;\r
414 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;\r
415 int whiteFlag = FALSE, blackFlag = FALSE;\r
416 int userOfferedDraw = FALSE;\r
417 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;\r
418 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;\r
419 int cmailMoveType[CMAIL_MAX_GAMES];\r
420 long ics_clock_paused = 0;\r
421 ProcRef icsPR = NoProc, cmailPR = NoProc;\r
422 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;\r
423 GameMode gameMode = BeginningOfGame;\r
424 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];\r
425 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];\r
426 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */\r
427 int hiddenThinkOutputState = 0; /* [AS] */\r
428 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */\r
429 int adjudicateLossPlies = 6;\r
430 char white_holding[64], black_holding[64];\r
431 TimeMark lastNodeCountTime;\r
432 long lastNodeCount=0;\r
433 int have_sent_ICS_logon = 0;\r
434 int movesPerSession;\r
435 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;\r
436 long timeControl_2; /* [AS] Allow separate time controls */\r
437 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */\r
438 long timeRemaining[2][MAX_MOVES];\r
439 int matchGame = 0;\r
440 TimeMark programStartTime;\r
441 char ics_handle[MSG_SIZ];\r
442 int have_set_title = 0;\r
443 \r
444 /* animateTraining preserves the state of appData.animate\r
445  * when Training mode is activated. This allows the\r
446  * response to be animated when appData.animate == TRUE and\r
447  * appData.animateDragging == TRUE.\r
448  */\r
449 Boolean animateTraining;\r
450 \r
451 GameInfo gameInfo;\r
452 \r
453 AppData appData;\r
454 \r
455 Board boards[MAX_MOVES];\r
456 /* [HGM] Following 7 needed for accurate legality tests: */\r
457 char  epStatus[MAX_MOVES];\r
458 char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1\r
459 char  castlingRank[BOARD_SIZE]; // and corresponding ranks\r
460 char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];\r
461 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status\r
462 int   initialRulePlies, FENrulePlies;\r
463 char  FENepStatus;\r
464 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)\r
465 int loadFlag = 0; \r
466 int shuffleOpenings;\r
467 \r
468 ChessSquare  FIDEArray[2][BOARD_SIZE] = {\r
469     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,\r
470         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },\r
471     { BlackRook, BlackKnight, BlackBishop, BlackQueen,\r
472         BlackKing, BlackBishop, BlackKnight, BlackRook }\r
473 };\r
474 \r
475 ChessSquare twoKingsArray[2][BOARD_SIZE] = {\r
476     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,\r
477         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },\r
478     { BlackRook, BlackKnight, BlackBishop, BlackQueen,\r
479         BlackKing, BlackKing, BlackKnight, BlackRook }\r
480 };\r
481 \r
482 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {\r
483     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,\r
484         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },\r
485     { BlackRook, BlackMan, BlackBishop, BlackQueen,\r
486         BlackUnicorn, BlackBishop, BlackMan, BlackRook }\r
487 };\r
488 \r
489 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */\r
490     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,\r
491         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },\r
492     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,\r
493         BlackKing, BlackBishop, BlackKnight, BlackRook }\r
494 };\r
495 \r
496 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */\r
497     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,\r
498         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },\r
499     { BlackRook, BlackKnight, BlackAlfil, BlackKing,\r
500         BlackFerz, BlackAlfil, BlackKnight, BlackRook }\r
501 };\r
502 \r
503 \r
504 #if (BOARD_SIZE>=10)\r
505 ChessSquare ShogiArray[2][BOARD_SIZE] = {\r
506     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,\r
507         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },\r
508     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,\r
509         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }\r
510 };\r
511 \r
512 ChessSquare XiangqiArray[2][BOARD_SIZE] = {\r
513     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,\r
514         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },\r
515     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,\r
516         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }\r
517 };\r
518 \r
519 ChessSquare CapablancaArray[2][BOARD_SIZE] = {\r
520     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, \r
521         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },\r
522     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, \r
523         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }\r
524 };\r
525 \r
526 ChessSquare GreatArray[2][BOARD_SIZE] = {\r
527     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, \r
528         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },\r
529     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, \r
530         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },\r
531 };\r
532 \r
533 ChessSquare JanusArray[2][BOARD_SIZE] = {\r
534     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, \r
535         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },\r
536     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, \r
537         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }\r
538 };\r
539 \r
540 #ifdef GOTHIC\r
541 ChessSquare GothicArray[2][BOARD_SIZE] = {\r
542     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, \r
543         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },\r
544     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, \r
545         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }\r
546 };\r
547 #else // !GOTHIC\r
548 #define GothicArray CapablancaArray\r
549 #endif // !GOTHIC\r
550 \r
551 #ifdef FALCON\r
552 ChessSquare FalconArray[2][BOARD_SIZE] = {\r
553     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, \r
554         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },\r
555     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, \r
556         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }\r
557 };\r
558 #else // !FALCON\r
559 #define FalconArray CapablancaArray\r
560 #endif // !FALCON\r
561 \r
562 #else // !(BOARD_SIZE>=10)\r
563 #define XiangqiPosition FIDEArray\r
564 #define CapablancaArray FIDEArray\r
565 #define GothicArray FIDEArray\r
566 #define GreatArray FIDEArray\r
567 #endif // !(BOARD_SIZE>=10)\r
568 \r
569 #if (BOARD_SIZE>=12)\r
570 ChessSquare CourierArray[2][BOARD_SIZE] = {\r
571     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,\r
572         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },\r
573     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,\r
574         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }\r
575 };\r
576 #else // !(BOARD_SIZE>=12)\r
577 #define CourierArray CapablancaArray\r
578 #endif // !(BOARD_SIZE>=12)\r
579 \r
580 \r
581 Board initialPosition;\r
582 \r
583 \r
584 /* Convert str to a rating. Checks for special cases of "----",\r
585 \r
586    "++++", etc. Also strips ()'s */\r
587 int\r
588 string_to_rating(str)\r
589   char *str;\r
590 {\r
591   while(*str && !isdigit(*str)) ++str;\r
592   if (!*str)\r
593     return 0;   /* One of the special "no rating" cases */\r
594   else\r
595     return atoi(str);\r
596 }\r
597 \r
598 void\r
599 ClearProgramStats()\r
600 {\r
601     /* Init programStats */\r
602     programStats.movelist[0] = 0;\r
603     programStats.depth = 0;\r
604     programStats.nr_moves = 0;\r
605     programStats.moves_left = 0;\r
606     programStats.nodes = 0;\r
607     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output\r
608     programStats.score = 0;\r
609     programStats.got_only_move = 0;\r
610     programStats.got_fail = 0;\r
611     programStats.line_is_book = 0;\r
612 }\r
613 \r
614 void\r
615 InitBackEnd1()\r
616 {\r
617     int matched, min, sec;\r
618 \r
619     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options\r
620 \r
621     GetTimeMark(&programStartTime);\r
622     srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level\r
623 \r
624     ClearProgramStats();\r
625     programStats.ok_to_send = 1;\r
626     programStats.seen_stat = 0;\r
627 \r
628     /*\r
629      * Initialize game list\r
630      */\r
631     ListNew(&gameList);\r
632 \r
633 \r
634     /*\r
635      * Internet chess server status\r
636      */\r
637     if (appData.icsActive) {\r
638         appData.matchMode = FALSE;\r
639         appData.matchGames = 0;\r
640 #if ZIPPY       \r
641         appData.noChessProgram = !appData.zippyPlay;\r
642 #else\r
643         appData.zippyPlay = FALSE;\r
644         appData.zippyTalk = FALSE;\r
645         appData.noChessProgram = TRUE;\r
646 #endif\r
647         if (*appData.icsHelper != NULLCHAR) {\r
648             appData.useTelnet = TRUE;\r
649             appData.telnetProgram = appData.icsHelper;\r
650         }\r
651     } else {\r
652         appData.zippyTalk = appData.zippyPlay = FALSE;\r
653     }\r
654 \r
655     /* [AS] Initialize pv info list [HGM] and game state */\r
656     {\r
657         int i, j;\r
658 \r
659         for( i=0; i<MAX_MOVES; i++ ) {\r
660             pvInfoList[i].depth = -1;\r
661             epStatus[i]=EP_NONE;\r
662             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
663         }\r
664     }\r
665 \r
666     /*\r
667      * Parse timeControl resource\r
668      */\r
669     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,\r
670                           appData.movesPerSession)) {\r
671         char buf[MSG_SIZ];\r
672         sprintf(buf, _("bad timeControl option %s"), appData.timeControl);\r
673         DisplayFatalError(buf, 0, 2);\r
674     }\r
675 \r
676     /*\r
677      * Parse searchTime resource\r
678      */\r
679     if (*appData.searchTime != NULLCHAR) {\r
680         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);\r
681         if (matched == 1) {\r
682             searchTime = min * 60;\r
683         } else if (matched == 2) {\r
684             searchTime = min * 60 + sec;\r
685         } else {\r
686             char buf[MSG_SIZ];\r
687             sprintf(buf, _("bad searchTime option %s"), appData.searchTime);\r
688             DisplayFatalError(buf, 0, 2);\r
689         }\r
690     }\r
691 \r
692     /* [AS] Adjudication threshold */\r
693     adjudicateLossThreshold = appData.adjudicateLossThreshold;\r
694     \r
695     first.which = "first";\r
696     second.which = "second";\r
697     first.maybeThinking = second.maybeThinking = FALSE;\r
698     first.pr = second.pr = NoProc;\r
699     first.isr = second.isr = NULL;\r
700     first.sendTime = second.sendTime = 2;\r
701     first.sendDrawOffers = 1;\r
702     if (appData.firstPlaysBlack) {\r
703         first.twoMachinesColor = "black\n";\r
704         second.twoMachinesColor = "white\n";\r
705     } else {\r
706         first.twoMachinesColor = "white\n";\r
707         second.twoMachinesColor = "black\n";\r
708     }\r
709     first.program = appData.firstChessProgram;\r
710     second.program = appData.secondChessProgram;\r
711     first.host = appData.firstHost;\r
712     second.host = appData.secondHost;\r
713     first.dir = appData.firstDirectory;\r
714     second.dir = appData.secondDirectory;\r
715     first.other = &second;\r
716     second.other = &first;\r
717     first.initString = appData.initString;\r
718     second.initString = appData.secondInitString;\r
719     first.computerString = appData.firstComputerString;\r
720     second.computerString = appData.secondComputerString;\r
721     first.useSigint = second.useSigint = TRUE;\r
722     first.useSigterm = second.useSigterm = TRUE;\r
723     first.reuse = appData.reuseFirst;\r
724     second.reuse = appData.reuseSecond;\r
725     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second\r
726     second.nps = appData.secondNPS;\r
727     first.useSetboard = second.useSetboard = FALSE;\r
728     first.useSAN = second.useSAN = FALSE;\r
729     first.usePing = second.usePing = FALSE;\r
730     first.lastPing = second.lastPing = 0;\r
731     first.lastPong = second.lastPong = 0;\r
732     first.usePlayother = second.usePlayother = FALSE;\r
733     first.useColors = second.useColors = TRUE;\r
734     first.useUsermove = second.useUsermove = FALSE;\r
735     first.sendICS = second.sendICS = FALSE;\r
736     first.sendName = second.sendName = appData.icsActive;\r
737     first.sdKludge = second.sdKludge = FALSE;\r
738     first.stKludge = second.stKludge = FALSE;\r
739     TidyProgramName(first.program, first.host, first.tidy);\r
740     TidyProgramName(second.program, second.host, second.tidy);\r
741     first.matchWins = second.matchWins = 0;\r
742     strcpy(first.variants, appData.variant);\r
743     strcpy(second.variants, appData.variant);\r
744     first.analysisSupport = second.analysisSupport = 2; /* detect */\r
745     first.analyzing = second.analyzing = FALSE;\r
746     first.initDone = second.initDone = FALSE;\r
747 \r
748     /* New features added by Tord: */\r
749     first.useFEN960 = FALSE; second.useFEN960 = FALSE;\r
750     first.useOOCastle = TRUE; second.useOOCastle = TRUE;\r
751     /* End of new features added by Tord. */\r
752     first.fenOverride  = appData.fenOverride1;\r
753     second.fenOverride = appData.fenOverride2;\r
754 \r
755     /* [HGM] time odds: set factor for each machine */\r
756     first.timeOdds  = appData.firstTimeOdds;\r
757     second.timeOdds = appData.secondTimeOdds;\r
758     { int norm = 1;\r
759         if(appData.timeOddsMode) {\r
760             norm = first.timeOdds;\r
761             if(norm > second.timeOdds) norm = second.timeOdds;\r
762         }\r
763         first.timeOdds /= norm;\r
764         second.timeOdds /= norm;\r
765     }\r
766 \r
767     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/\r
768     first.accumulateTC = appData.firstAccumulateTC;\r
769     second.accumulateTC = appData.secondAccumulateTC;\r
770     first.maxNrOfSessions = second.maxNrOfSessions = 1;\r
771 \r
772     /* [HGM] debug */\r
773     first.debug = second.debug = FALSE;\r
774     first.supportsNPS = second.supportsNPS = UNKNOWN;\r
775 \r
776     /* [HGM] options */\r
777     first.optionSettings  = appData.firstOptions;\r
778     second.optionSettings = appData.secondOptions;\r
779 \r
780     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */\r
781     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */\r
782     first.isUCI = appData.firstIsUCI; /* [AS] */\r
783     second.isUCI = appData.secondIsUCI; /* [AS] */\r
784     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */\r
785     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */\r
786 \r
787     if (appData.firstProtocolVersion > PROTOVER ||\r
788         appData.firstProtocolVersion < 1) {\r
789       char buf[MSG_SIZ];\r
790       sprintf(buf, _("protocol version %d not supported"),\r
791               appData.firstProtocolVersion);\r
792       DisplayFatalError(buf, 0, 2);\r
793     } else {\r
794       first.protocolVersion = appData.firstProtocolVersion;\r
795     }\r
796 \r
797     if (appData.secondProtocolVersion > PROTOVER ||\r
798         appData.secondProtocolVersion < 1) {\r
799       char buf[MSG_SIZ];\r
800       sprintf(buf, _("protocol version %d not supported"),\r
801               appData.secondProtocolVersion);\r
802       DisplayFatalError(buf, 0, 2);\r
803     } else {\r
804       second.protocolVersion = appData.secondProtocolVersion;\r
805     }\r
806 \r
807     if (appData.icsActive) {\r
808         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */\r
809     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {\r
810         appData.clockMode = FALSE;\r
811         first.sendTime = second.sendTime = 0;\r
812     }\r
813     \r
814 #if ZIPPY\r
815     /* Override some settings from environment variables, for backward\r
816        compatibility.  Unfortunately it's not feasible to have the env\r
817        vars just set defaults, at least in xboard.  Ugh.\r
818     */\r
819     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {\r
820       ZippyInit();\r
821     }\r
822 #endif\r
823     \r
824     if (appData.noChessProgram) {\r
825         programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)\r
826                                         + strlen(PATCHLEVEL));\r
827         sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);\r
828     } else {\r
829 #if 0\r
830         char *p, *q;\r
831         q = first.program;\r
832         while (*q != ' ' && *q != NULLCHAR) q++;\r
833         p = q;\r
834         while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] bckslash added */\r
835         programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)\r
836                                         + strlen(PATCHLEVEL) + (q - p));\r
837         sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);\r
838         strncat(programVersion, p, q - p);\r
839 #else\r
840         /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */\r
841         programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)\r
842                                         + strlen(PATCHLEVEL) + strlen(first.tidy));\r
843         sprintf(programVersion, "%s %s.%s + %s", PRODUCT, VERSION, PATCHLEVEL, first.tidy);\r
844 #endif\r
845     }\r
846 \r
847     if (!appData.icsActive) {\r
848       char buf[MSG_SIZ];\r
849       /* Check for variants that are supported only in ICS mode,\r
850          or not at all.  Some that are accepted here nevertheless\r
851          have bugs; see comments below.\r
852       */\r
853       VariantClass variant = StringToVariant(appData.variant);\r
854       switch (variant) {\r
855       case VariantBughouse:     /* need four players and two boards */\r
856       case VariantKriegspiel:   /* need to hide pieces and move details */\r
857       /* case VariantFischeRandom: (Fabien: moved below) */\r
858         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);\r
859         DisplayFatalError(buf, 0, 2);\r
860         return;\r
861 \r
862       case VariantUnknown:\r
863       case VariantLoadable:\r
864       case Variant29:\r
865       case Variant30:\r
866       case Variant31:\r
867       case Variant32:\r
868       case Variant33:\r
869       case Variant34:\r
870       case Variant35:\r
871       case Variant36:\r
872       default:\r
873         sprintf(buf, _("Unknown variant name %s"), appData.variant);\r
874         DisplayFatalError(buf, 0, 2);\r
875         return;\r
876 \r
877       case VariantXiangqi:    /* [HGM] repetition rules not implemented */\r
878       case VariantFairy:      /* [HGM] TestLegality definitely off! */\r
879       case VariantGothic:     /* [HGM] should work */\r
880       case VariantCapablanca: /* [HGM] should work */\r
881       case VariantCourier:    /* [HGM] initial forced moves not implemented */\r
882       case VariantShogi:      /* [HGM] drops not tested for legality */\r
883       case VariantKnightmate: /* [HGM] should work */\r
884       case VariantCylinder:   /* [HGM] untested */\r
885       case VariantFalcon:     /* [HGM] untested */\r
886       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)\r
887                                  offboard interposition not understood */\r
888       case VariantNormal:     /* definitely works! */\r
889       case VariantWildCastle: /* pieces not automatically shuffled */\r
890       case VariantNoCastle:   /* pieces not automatically shuffled */\r
891       case VariantFischeRandom: /* [HGM] works and shuffles pieces */\r
892       case VariantLosers:     /* should work except for win condition,\r
893                                  and doesn't know captures are mandatory */\r
894       case VariantSuicide:    /* should work except for win condition,\r
895                                  and doesn't know captures are mandatory */\r
896       case VariantGiveaway:   /* should work except for win condition,\r
897                                  and doesn't know captures are mandatory */\r
898       case VariantTwoKings:   /* should work */\r
899       case VariantAtomic:     /* should work except for win condition */\r
900       case Variant3Check:     /* should work except for win condition */\r
901       case VariantShatranj:   /* should work except for all win conditions */\r
902       case VariantBerolina:   /* might work if TestLegality is off */\r
903       case VariantCapaRandom: /* should work */\r
904       case VariantJanus:      /* should work */\r
905       case VariantSuper:      /* experimental */\r
906       case VariantGreat:      /* experimental, requires legality testing to be off */\r
907         break;\r
908       }\r
909     }\r
910 \r
911     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard\r
912     InitEngineUCI( installDir, &second );\r
913 }\r
914 \r
915 int NextIntegerFromString( char ** str, long * value )\r
916 {\r
917     int result = -1;\r
918     char * s = *str;\r
919 \r
920     while( *s == ' ' || *s == '\t' ) {\r
921         s++;\r
922     }\r
923 \r
924     *value = 0;\r
925 \r
926     if( *s >= '0' && *s <= '9' ) {\r
927         while( *s >= '0' && *s <= '9' ) {\r
928             *value = *value * 10 + (*s - '0');\r
929             s++;\r
930         }\r
931 \r
932         result = 0;\r
933     }\r
934 \r
935     *str = s;\r
936 \r
937     return result;\r
938 }\r
939 \r
940 int NextTimeControlFromString( char ** str, long * value )\r
941 {\r
942     long temp;\r
943     int result = NextIntegerFromString( str, &temp );\r
944 \r
945     if( result == 0 ) {\r
946         *value = temp * 60; /* Minutes */\r
947         if( **str == ':' ) {\r
948             (*str)++;\r
949             result = NextIntegerFromString( str, &temp );\r
950             *value += temp; /* Seconds */\r
951         }\r
952     }\r
953 \r
954     return result;\r
955 }\r
956 \r
957 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)\r
958 {   /* [HGM] routine added to read '+moves/time' for secondary time control */\r
959     int result = -1; long temp, temp2;\r
960 \r
961     if(**str != '+') return -1; // old params remain in force!\r
962     (*str)++;\r
963     if( NextTimeControlFromString( str, &temp ) ) return -1;\r
964 \r
965     if(**str != '/') {\r
966         /* time only: incremental or sudden-death time control */\r
967         if(**str == '+') { /* increment follows; read it */\r
968             (*str)++;\r
969             if(result = NextIntegerFromString( str, &temp2)) return -1;\r
970             *inc = temp2 * 1000;\r
971         } else *inc = 0;\r
972         *moves = 0; *tc = temp * 1000; \r
973         return 0;\r
974     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */\r
975 \r
976     (*str)++; /* classical time control */\r
977     result = NextTimeControlFromString( str, &temp2);\r
978     if(result == 0) {\r
979         *moves = temp/60;\r
980         *tc    = temp2 * 1000;\r
981         *inc   = 0;\r
982     }\r
983     return result;\r
984 }\r
985 \r
986 int GetTimeQuota(int movenr)\r
987 {   /* [HGM] get time to add from the multi-session time-control string */\r
988     int moves=1; /* kludge to force reading of first session */\r
989     long time, increment;\r
990     char *s = fullTimeControlString;\r
991 \r
992     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);\r
993     do {\r
994         if(moves) NextSessionFromString(&s, &moves, &time, &increment);\r
995         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);\r
996         if(movenr == -1) return time;    /* last move before new session     */\r
997         if(!moves) return increment;     /* current session is incremental   */\r
998         if(movenr >= 0) movenr -= moves; /* we already finished this session */\r
999     } while(movenr >= -1);               /* try again for next session       */\r
1000 \r
1001     return 0; // no new time quota on this move\r
1002 }\r
1003 \r
1004 int\r
1005 ParseTimeControl(tc, ti, mps)\r
1006      char *tc;\r
1007      int ti;\r
1008      int mps;\r
1009 {\r
1010 #if 0\r
1011     int matched, min, sec;\r
1012 \r
1013     matched = sscanf(tc, "%d:%d", &min, &sec);\r
1014     if (matched == 1) {\r
1015         timeControl = min * 60 * 1000;\r
1016     } else if (matched == 2) {\r
1017         timeControl = (min * 60 + sec) * 1000;\r
1018     } else {\r
1019         return FALSE;\r
1020     }\r
1021 #else\r
1022     long tc1;\r
1023     long tc2;\r
1024     char buf[MSG_SIZ];\r
1025 \r
1026     if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;\r
1027     if(ti > 0) {\r
1028         if(mps)\r
1029              sprintf(buf, "+%d/%s+%d", mps, tc, ti);\r
1030         else sprintf(buf, "+%s+%d", tc, ti);\r
1031     } else {\r
1032         if(mps)\r
1033              sprintf(buf, "+%d/%s", mps, tc);\r
1034         else sprintf(buf, "+%s", tc);\r
1035     }\r
1036     fullTimeControlString = StrSave(buf);\r
1037 \r
1038     if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {\r
1039         return FALSE;\r
1040     }\r
1041 \r
1042     if( *tc == '/' ) {\r
1043         /* Parse second time control */\r
1044         tc++;\r
1045 \r
1046         if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {\r
1047             return FALSE;\r
1048         }\r
1049 \r
1050         if( tc2 == 0 ) {\r
1051             return FALSE;\r
1052         }\r
1053 \r
1054         timeControl_2 = tc2 * 1000;\r
1055     }\r
1056     else {\r
1057         timeControl_2 = 0;\r
1058     }\r
1059 \r
1060     if( tc1 == 0 ) {\r
1061         return FALSE;\r
1062     }\r
1063 \r
1064     timeControl = tc1 * 1000;\r
1065 #endif\r
1066 \r
1067     if (ti >= 0) {\r
1068         timeIncrement = ti * 1000;  /* convert to ms */\r
1069         movesPerSession = 0;\r
1070     } else {\r
1071         timeIncrement = 0;\r
1072         movesPerSession = mps;\r
1073     }\r
1074     return TRUE;\r
1075 }\r
1076 \r
1077 void\r
1078 InitBackEnd2()\r
1079 {\r
1080     if (appData.debugMode) {\r
1081         fprintf(debugFP, "%s\n", programVersion);\r
1082     }\r
1083 \r
1084     if (appData.matchGames > 0) {\r
1085         appData.matchMode = TRUE;\r
1086     } else if (appData.matchMode) {\r
1087         appData.matchGames = 1;\r
1088     }\r
1089     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */\r
1090         appData.matchGames = appData.sameColorGames;\r
1091     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */\r
1092         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;\r
1093         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;\r
1094     }\r
1095     Reset(TRUE, FALSE);\r
1096     if (appData.noChessProgram || first.protocolVersion == 1) {\r
1097       InitBackEnd3();\r
1098     } else {\r
1099       /* kludge: allow timeout for initial "feature" commands */\r
1100       FreezeUI();\r
1101       DisplayMessage("", _("Starting chess program"));\r
1102       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);\r
1103     }\r
1104 }\r
1105 \r
1106 void\r
1107 InitBackEnd3 P((void))\r
1108 {\r
1109     GameMode initialMode;\r
1110     char buf[MSG_SIZ];\r
1111     int err;\r
1112 \r
1113     InitChessProgram(&first, startedFromSetupPosition);\r
1114 \r
1115 \r
1116     if (appData.icsActive) {\r
1117 #ifdef WIN32\r
1118         /* [DM] Make a console window if needed [HGM] merged ifs */\r
1119         ConsoleCreate(); \r
1120 #endif\r
1121         err = establish();\r
1122         if (err != 0) {\r
1123             if (*appData.icsCommPort != NULLCHAR) {\r
1124                 sprintf(buf, _("Could not open comm port %s"),  \r
1125                         appData.icsCommPort);\r
1126             } else {\r
1127                 sprintf(buf, _("Could not connect to host %s, port %s"),  \r
1128                         appData.icsHost, appData.icsPort);\r
1129             }\r
1130             DisplayFatalError(buf, err, 1);\r
1131             return;\r
1132         }\r
1133         SetICSMode();\r
1134         telnetISR =\r
1135           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);\r
1136         fromUserISR =\r
1137           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);\r
1138     } else if (appData.noChessProgram) {\r
1139         SetNCPMode();\r
1140     } else {\r
1141         SetGNUMode();\r
1142     }\r
1143 \r
1144     if (*appData.cmailGameName != NULLCHAR) {\r
1145         SetCmailMode();\r
1146         OpenLoopback(&cmailPR);\r
1147         cmailISR =\r
1148           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);\r
1149     }\r
1150     \r
1151     ThawUI();\r
1152     DisplayMessage("", "");\r
1153     if (StrCaseCmp(appData.initialMode, "") == 0) {\r
1154       initialMode = BeginningOfGame;\r
1155     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {\r
1156       initialMode = TwoMachinesPlay;\r
1157     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {\r
1158       initialMode = AnalyzeFile; \r
1159     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {\r
1160       initialMode = AnalyzeMode;\r
1161     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {\r
1162       initialMode = MachinePlaysWhite;\r
1163     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {\r
1164       initialMode = MachinePlaysBlack;\r
1165     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {\r
1166       initialMode = EditGame;\r
1167     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {\r
1168       initialMode = EditPosition;\r
1169     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {\r
1170       initialMode = Training;\r
1171     } else {\r
1172       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);\r
1173       DisplayFatalError(buf, 0, 2);\r
1174       return;\r
1175     }\r
1176 \r
1177     if (appData.matchMode) {\r
1178         /* Set up machine vs. machine match */\r
1179         if (appData.noChessProgram) {\r
1180             DisplayFatalError(_("Can't have a match with no chess programs"),\r
1181                               0, 2);\r
1182             return;\r
1183         }\r
1184         matchMode = TRUE;\r
1185         matchGame = 1;\r
1186         if (*appData.loadGameFile != NULLCHAR) {\r
1187             int index = appData.loadGameIndex; // [HGM] autoinc\r
1188             if(index<0) lastIndex = index = 1;\r
1189             if (!LoadGameFromFile(appData.loadGameFile,\r
1190                                   index,\r
1191                                   appData.loadGameFile, FALSE)) {\r
1192                 DisplayFatalError(_("Bad game file"), 0, 1);\r
1193                 return;\r
1194             }\r
1195         } else if (*appData.loadPositionFile != NULLCHAR) {\r
1196             int index = appData.loadPositionIndex; // [HGM] autoinc\r
1197             if(index<0) lastIndex = index = 1;\r
1198             if (!LoadPositionFromFile(appData.loadPositionFile,\r
1199                                       index,\r
1200                                       appData.loadPositionFile)) {\r
1201                 DisplayFatalError(_("Bad position file"), 0, 1);\r
1202                 return;\r
1203             }\r
1204         }\r
1205         TwoMachinesEvent();\r
1206     } else if (*appData.cmailGameName != NULLCHAR) {\r
1207         /* Set up cmail mode */\r
1208         ReloadCmailMsgEvent(TRUE);\r
1209     } else {\r
1210         /* Set up other modes */\r
1211         if (initialMode == AnalyzeFile) {\r
1212           if (*appData.loadGameFile == NULLCHAR) {\r
1213             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);\r
1214             return;\r
1215           }\r
1216         }\r
1217         if (*appData.loadGameFile != NULLCHAR) {\r
1218             (void) LoadGameFromFile(appData.loadGameFile,\r
1219                                     appData.loadGameIndex,\r
1220                                     appData.loadGameFile, TRUE);\r
1221         } else if (*appData.loadPositionFile != NULLCHAR) {\r
1222             (void) LoadPositionFromFile(appData.loadPositionFile,\r
1223                                         appData.loadPositionIndex,\r
1224                                         appData.loadPositionFile);\r
1225             /* [HGM] try to make self-starting even after FEN load */\r
1226             /* to allow automatic setup of fairy variants with wtm */\r
1227             if(initialMode == BeginningOfGame && !blackPlaysFirst) {\r
1228                 gameMode = BeginningOfGame;\r
1229                 setboardSpoiledMachineBlack = 1;\r
1230             }\r
1231             /* [HGM] loadPos: make that every new game uses the setup */\r
1232             /* from file as long as we do not switch variant          */\r
1233             if(!blackPlaysFirst) { int i;\r
1234                 startedFromPositionFile = TRUE;\r
1235                 CopyBoard(filePosition, boards[0]);\r
1236                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];\r
1237             }\r
1238         }\r
1239         if (initialMode == AnalyzeMode) {\r
1240           if (appData.noChessProgram) {\r
1241             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);\r
1242             return;\r
1243           }\r
1244           if (appData.icsActive) {\r
1245             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);\r
1246             return;\r
1247           }\r
1248           AnalyzeModeEvent();\r
1249         } else if (initialMode == AnalyzeFile) {\r
1250           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent\r
1251           ShowThinkingEvent();\r
1252           AnalyzeFileEvent();\r
1253           AnalysisPeriodicEvent(1);\r
1254         } else if (initialMode == MachinePlaysWhite) {\r
1255           if (appData.noChessProgram) {\r
1256             DisplayFatalError(_("MachineWhite mode requires a chess engine"),\r
1257                               0, 2);\r
1258             return;\r
1259           }\r
1260           if (appData.icsActive) {\r
1261             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),\r
1262                               0, 2);\r
1263             return;\r
1264           }\r
1265           MachineWhiteEvent();\r
1266         } else if (initialMode == MachinePlaysBlack) {\r
1267           if (appData.noChessProgram) {\r
1268             DisplayFatalError(_("MachineBlack mode requires a chess engine"),\r
1269                               0, 2);\r
1270             return;\r
1271           }\r
1272           if (appData.icsActive) {\r
1273             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),\r
1274                               0, 2);\r
1275             return;\r
1276           }\r
1277           MachineBlackEvent();\r
1278         } else if (initialMode == TwoMachinesPlay) {\r
1279           if (appData.noChessProgram) {\r
1280             DisplayFatalError(_("TwoMachines mode requires a chess engine"),\r
1281                               0, 2);\r
1282             return;\r
1283           }\r
1284           if (appData.icsActive) {\r
1285             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),\r
1286                               0, 2);\r
1287             return;\r
1288           }\r
1289           TwoMachinesEvent();\r
1290         } else if (initialMode == EditGame) {\r
1291           EditGameEvent();\r
1292         } else if (initialMode == EditPosition) {\r
1293           EditPositionEvent();\r
1294         } else if (initialMode == Training) {\r
1295           if (*appData.loadGameFile == NULLCHAR) {\r
1296             DisplayFatalError(_("Training mode requires a game file"), 0, 2);\r
1297             return;\r
1298           }\r
1299           TrainingEvent();\r
1300         }\r
1301     }\r
1302 }\r
1303 \r
1304 /*\r
1305  * Establish will establish a contact to a remote host.port.\r
1306  * Sets icsPR to a ProcRef for a process (or pseudo-process)\r
1307  *  used to talk to the host.\r
1308  * Returns 0 if okay, error code if not.\r
1309  */\r
1310 int\r
1311 establish()\r
1312 {\r
1313     char buf[MSG_SIZ];\r
1314 \r
1315     if (*appData.icsCommPort != NULLCHAR) {\r
1316         /* Talk to the host through a serial comm port */\r
1317         return OpenCommPort(appData.icsCommPort, &icsPR);\r
1318 \r
1319     } else if (*appData.gateway != NULLCHAR) {\r
1320         if (*appData.remoteShell == NULLCHAR) {\r
1321             /* Use the rcmd protocol to run telnet program on a gateway host */\r
1322             sprintf(buf, "%s %s %s",\r
1323                     appData.telnetProgram, appData.icsHost, appData.icsPort);\r
1324             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);\r
1325 \r
1326         } else {\r
1327             /* Use the rsh program to run telnet program on a gateway host */\r
1328             if (*appData.remoteUser == NULLCHAR) {\r
1329                 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,\r
1330                         appData.gateway, appData.telnetProgram,\r
1331                         appData.icsHost, appData.icsPort);\r
1332             } else {\r
1333                 sprintf(buf, "%s %s -l %s %s %s %s",\r
1334                         appData.remoteShell, appData.gateway, \r
1335                         appData.remoteUser, appData.telnetProgram,\r
1336                         appData.icsHost, appData.icsPort);\r
1337             }\r
1338             return StartChildProcess(buf, "", &icsPR);\r
1339 \r
1340         }\r
1341     } else if (appData.useTelnet) {\r
1342         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);\r
1343 \r
1344     } else {\r
1345         /* TCP socket interface differs somewhat between\r
1346            Unix and NT; handle details in the front end.\r
1347            */\r
1348         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);\r
1349     }\r
1350 }\r
1351 \r
1352 void\r
1353 show_bytes(fp, buf, count)\r
1354      FILE *fp;\r
1355      char *buf;\r
1356      int count;\r
1357 {\r
1358     while (count--) {\r
1359         if (*buf < 040 || *(unsigned char *) buf > 0177) {\r
1360             fprintf(fp, "\\%03o", *buf & 0xff);\r
1361         } else {\r
1362             putc(*buf, fp);\r
1363         }\r
1364         buf++;\r
1365     }\r
1366     fflush(fp);\r
1367 }\r
1368 \r
1369 /* Returns an errno value */\r
1370 int\r
1371 OutputMaybeTelnet(pr, message, count, outError)\r
1372      ProcRef pr;\r
1373      char *message;\r
1374      int count;\r
1375      int *outError;\r
1376 {\r
1377     char buf[8192], *p, *q, *buflim;\r
1378     int left, newcount, outcount;\r
1379 \r
1380     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||\r
1381         *appData.gateway != NULLCHAR) {\r
1382         if (appData.debugMode) {\r
1383             fprintf(debugFP, ">ICS: ");\r
1384             show_bytes(debugFP, message, count);\r
1385             fprintf(debugFP, "\n");\r
1386         }\r
1387         return OutputToProcess(pr, message, count, outError);\r
1388     }\r
1389 \r
1390     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */\r
1391     p = message;\r
1392     q = buf;\r
1393     left = count;\r
1394     newcount = 0;\r
1395     while (left) {\r
1396         if (q >= buflim) {\r
1397             if (appData.debugMode) {\r
1398                 fprintf(debugFP, ">ICS: ");\r
1399                 show_bytes(debugFP, buf, newcount);\r
1400                 fprintf(debugFP, "\n");\r
1401             }\r
1402             outcount = OutputToProcess(pr, buf, newcount, outError);\r
1403             if (outcount < newcount) return -1; /* to be sure */\r
1404             q = buf;\r
1405             newcount = 0;\r
1406         }\r
1407         if (*p == '\n') {\r
1408             *q++ = '\r';\r
1409             newcount++;\r
1410         } else if (((unsigned char) *p) == TN_IAC) {\r
1411             *q++ = (char) TN_IAC;\r
1412             newcount ++;\r
1413         }\r
1414         *q++ = *p++;\r
1415         newcount++;\r
1416         left--;\r
1417     }\r
1418     if (appData.debugMode) {\r
1419         fprintf(debugFP, ">ICS: ");\r
1420         show_bytes(debugFP, buf, newcount);\r
1421         fprintf(debugFP, "\n");\r
1422     }\r
1423     outcount = OutputToProcess(pr, buf, newcount, outError);\r
1424     if (outcount < newcount) return -1; /* to be sure */\r
1425     return count;\r
1426 }\r
1427 \r
1428 void\r
1429 read_from_player(isr, closure, message, count, error)\r
1430      InputSourceRef isr;\r
1431      VOIDSTAR closure;\r
1432      char *message;\r
1433      int count;\r
1434      int error;\r
1435 {\r
1436     int outError, outCount;\r
1437     static int gotEof = 0;\r
1438 \r
1439     /* Pass data read from player on to ICS */\r
1440     if (count > 0) {\r
1441         gotEof = 0;\r
1442         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);\r
1443         if (outCount < count) {\r
1444             DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1445         }\r
1446     } else if (count < 0) {\r
1447         RemoveInputSource(isr);\r
1448         DisplayFatalError(_("Error reading from keyboard"), error, 1);\r
1449     } else if (gotEof++ > 0) {\r
1450         RemoveInputSource(isr);\r
1451         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);\r
1452     }\r
1453 }\r
1454 \r
1455 void\r
1456 SendToICS(s)\r
1457      char *s;\r
1458 {\r
1459     int count, outCount, outError;\r
1460 \r
1461     if (icsPR == NULL) return;\r
1462 \r
1463     count = strlen(s);\r
1464     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);\r
1465     if (outCount < count) {\r
1466         DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1467     }\r
1468 }\r
1469 \r
1470 /* This is used for sending logon scripts to the ICS. Sending\r
1471    without a delay causes problems when using timestamp on ICC\r
1472    (at least on my machine). */\r
1473 void\r
1474 SendToICSDelayed(s,msdelay)\r
1475      char *s;\r
1476      long msdelay;\r
1477 {\r
1478     int count, outCount, outError;\r
1479 \r
1480     if (icsPR == NULL) return;\r
1481 \r
1482     count = strlen(s);\r
1483     if (appData.debugMode) {\r
1484         fprintf(debugFP, ">ICS: ");\r
1485         show_bytes(debugFP, s, count);\r
1486         fprintf(debugFP, "\n");\r
1487     }\r
1488     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,\r
1489                                       msdelay);\r
1490     if (outCount < count) {\r
1491         DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1492     }\r
1493 }\r
1494 \r
1495 \r
1496 /* Remove all highlighting escape sequences in s\r
1497    Also deletes any suffix starting with '(' \r
1498    */\r
1499 char *\r
1500 StripHighlightAndTitle(s)\r
1501      char *s;\r
1502 {\r
1503     static char retbuf[MSG_SIZ];\r
1504     char *p = retbuf;\r
1505 \r
1506     while (*s != NULLCHAR) {\r
1507         while (*s == '\033') {\r
1508             while (*s != NULLCHAR && !isalpha(*s)) s++;\r
1509             if (*s != NULLCHAR) s++;\r
1510         }\r
1511         while (*s != NULLCHAR && *s != '\033') {\r
1512             if (*s == '(' || *s == '[') {\r
1513                 *p = NULLCHAR;\r
1514                 return retbuf;\r
1515             }\r
1516             *p++ = *s++;\r
1517         }\r
1518     }\r
1519     *p = NULLCHAR;\r
1520     return retbuf;\r
1521 }\r
1522 \r
1523 /* Remove all highlighting escape sequences in s */\r
1524 char *\r
1525 StripHighlight(s)\r
1526      char *s;\r
1527 {\r
1528     static char retbuf[MSG_SIZ];\r
1529     char *p = retbuf;\r
1530 \r
1531     while (*s != NULLCHAR) {\r
1532         while (*s == '\033') {\r
1533             while (*s != NULLCHAR && !isalpha(*s)) s++;\r
1534             if (*s != NULLCHAR) s++;\r
1535         }\r
1536         while (*s != NULLCHAR && *s != '\033') {\r
1537             *p++ = *s++;\r
1538         }\r
1539     }\r
1540     *p = NULLCHAR;\r
1541     return retbuf;\r
1542 }\r
1543 \r
1544 char *variantNames[] = VARIANT_NAMES;\r
1545 char *\r
1546 VariantName(v)\r
1547      VariantClass v;\r
1548 {\r
1549     return variantNames[v];\r
1550 }\r
1551 \r
1552 \r
1553 /* Identify a variant from the strings the chess servers use or the\r
1554    PGN Variant tag names we use. */\r
1555 VariantClass\r
1556 StringToVariant(e)\r
1557      char *e;\r
1558 {\r
1559     char *p;\r
1560     int wnum = -1;\r
1561     VariantClass v = VariantNormal;\r
1562     int i, found = FALSE;\r
1563     char buf[MSG_SIZ];\r
1564 \r
1565     if (!e) return v;\r
1566 \r
1567     /* [HGM] skip over optional board-size prefixes */\r
1568     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||\r
1569         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {\r
1570         while( *e++ != '_');\r
1571     }\r
1572 \r
1573     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {\r
1574       if (StrCaseStr(e, variantNames[i])) {\r
1575         v = (VariantClass) i;\r
1576         found = TRUE;\r
1577         break;\r
1578       }\r
1579     }\r
1580 \r
1581     if (!found) {\r
1582       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))\r
1583           || StrCaseStr(e, "wild/fr") \r
1584           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {\r
1585         v = VariantFischeRandom;\r
1586       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||\r
1587                  (i = 1, p = StrCaseStr(e, "w"))) {\r
1588         p += i;\r
1589         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;\r
1590         if (isdigit(*p)) {\r
1591           wnum = atoi(p);\r
1592         } else {\r
1593           wnum = -1;\r
1594         }\r
1595         switch (wnum) {\r
1596         case 0: /* FICS only, actually */\r
1597         case 1:\r
1598           /* Castling legal even if K starts on d-file */\r
1599           v = VariantWildCastle;\r
1600           break;\r
1601         case 2:\r
1602         case 3:\r
1603         case 4:\r
1604           /* Castling illegal even if K & R happen to start in\r
1605              normal positions. */\r
1606           v = VariantNoCastle;\r
1607           break;\r
1608         case 5:\r
1609         case 7:\r
1610         case 8:\r
1611         case 10:\r
1612         case 11:\r
1613         case 12:\r
1614         case 13:\r
1615         case 14:\r
1616         case 15:\r
1617         case 18:\r
1618         case 19:\r
1619           /* Castling legal iff K & R start in normal positions */\r
1620           v = VariantNormal;\r
1621           break;\r
1622         case 6:\r
1623         case 20:\r
1624         case 21:\r
1625           /* Special wilds for position setup; unclear what to do here */\r
1626           v = VariantLoadable;\r
1627           break;\r
1628         case 9:\r
1629           /* Bizarre ICC game */\r
1630           v = VariantTwoKings;\r
1631           break;\r
1632         case 16:\r
1633           v = VariantKriegspiel;\r
1634           break;\r
1635         case 17:\r
1636           v = VariantLosers;\r
1637           break;\r
1638         case 22:\r
1639           v = VariantFischeRandom;\r
1640           break;\r
1641         case 23:\r
1642           v = VariantCrazyhouse;\r
1643           break;\r
1644         case 24:\r
1645           v = VariantBughouse;\r
1646           break;\r
1647         case 25:\r
1648           v = Variant3Check;\r
1649           break;\r
1650         case 26:\r
1651           /* Not quite the same as FICS suicide! */\r
1652           v = VariantGiveaway;\r
1653           break;\r
1654         case 27:\r
1655           v = VariantAtomic;\r
1656           break;\r
1657         case 28:\r
1658           v = VariantShatranj;\r
1659           break;\r
1660 \r
1661         /* Temporary names for future ICC types.  The name *will* change in \r
1662            the next xboard/WinBoard release after ICC defines it. */\r
1663         case 29:\r
1664           v = Variant29;\r
1665           break;\r
1666         case 30:\r
1667           v = Variant30;\r
1668           break;\r
1669         case 31:\r
1670           v = Variant31;\r
1671           break;\r
1672         case 32:\r
1673           v = Variant32;\r
1674           break;\r
1675         case 33:\r
1676           v = Variant33;\r
1677           break;\r
1678         case 34:\r
1679           v = Variant34;\r
1680           break;\r
1681         case 35:\r
1682           v = Variant35;\r
1683           break;\r
1684         case 36:\r
1685           v = Variant36;\r
1686           break;\r
1687         case 37:\r
1688           v = VariantShogi;\r
1689           break;\r
1690         case 38:\r
1691           v = VariantXiangqi;\r
1692           break;\r
1693         case 39:\r
1694           v = VariantCourier;\r
1695           break;\r
1696         case 40:\r
1697           v = VariantGothic;\r
1698           break;\r
1699         case 41:\r
1700           v = VariantCapablanca;\r
1701           break;\r
1702         case 42:\r
1703           v = VariantKnightmate;\r
1704           break;\r
1705         case 43:\r
1706           v = VariantFairy;\r
1707           break;\r
1708         case 44:\r
1709           v = VariantCylinder;\r
1710           break;\r
1711         case 45:\r
1712           v = VariantFalcon;\r
1713           break;\r
1714         case 46:\r
1715           v = VariantCapaRandom;\r
1716           break;\r
1717         case 47:\r
1718           v = VariantBerolina;\r
1719           break;\r
1720         case 48:\r
1721           v = VariantJanus;\r
1722           break;\r
1723         case 49:\r
1724           v = VariantSuper;\r
1725           break;\r
1726         case 50:\r
1727           v = VariantGreat;\r
1728           break;\r
1729         case -1:\r
1730           /* Found "wild" or "w" in the string but no number;\r
1731              must assume it's normal chess. */\r
1732           v = VariantNormal;\r
1733           break;\r
1734         default:\r
1735           sprintf(buf, _("Unknown wild type %d"), wnum);\r
1736           DisplayError(buf, 0);\r
1737           v = VariantUnknown;\r
1738           break;\r
1739         }\r
1740       }\r
1741     }\r
1742     if (appData.debugMode) {\r
1743       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),\r
1744               e, wnum, VariantName(v));\r
1745     }\r
1746     return v;\r
1747 }\r
1748 \r
1749 static int leftover_start = 0, leftover_len = 0;\r
1750 char star_match[STAR_MATCH_N][MSG_SIZ];\r
1751 \r
1752 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,\r
1753    advance *index beyond it, and set leftover_start to the new value of\r
1754    *index; else return FALSE.  If pattern contains the character '*', it\r
1755    matches any sequence of characters not containing '\r', '\n', or the\r
1756    character following the '*' (if any), and the matched sequence(s) are\r
1757    copied into star_match.\r
1758    */\r
1759 int\r
1760 looking_at(buf, index, pattern)\r
1761      char *buf;\r
1762      int *index;\r
1763      char *pattern;\r
1764 {\r
1765     char *bufp = &buf[*index], *patternp = pattern;\r
1766     int star_count = 0;\r
1767     char *matchp = star_match[0];\r
1768     \r
1769     for (;;) {\r
1770         if (*patternp == NULLCHAR) {\r
1771             *index = leftover_start = bufp - buf;\r
1772             *matchp = NULLCHAR;\r
1773             return TRUE;\r
1774         }\r
1775         if (*bufp == NULLCHAR) return FALSE;\r
1776         if (*patternp == '*') {\r
1777             if (*bufp == *(patternp + 1)) {\r
1778                 *matchp = NULLCHAR;\r
1779                 matchp = star_match[++star_count];\r
1780                 patternp += 2;\r
1781                 bufp++;\r
1782                 continue;\r
1783             } else if (*bufp == '\n' || *bufp == '\r') {\r
1784                 patternp++;\r
1785                 if (*patternp == NULLCHAR)\r
1786                   continue;\r
1787                 else\r
1788                   return FALSE;\r
1789             } else {\r
1790                 *matchp++ = *bufp++;\r
1791                 continue;\r
1792             }\r
1793         }\r
1794         if (*patternp != *bufp) return FALSE;\r
1795         patternp++;\r
1796         bufp++;\r
1797     }\r
1798 }\r
1799 \r
1800 void\r
1801 SendToPlayer(data, length)\r
1802      char *data;\r
1803      int length;\r
1804 {\r
1805     int error, outCount;\r
1806     outCount = OutputToProcess(NoProc, data, length, &error);\r
1807     if (outCount < length) {\r
1808         DisplayFatalError(_("Error writing to display"), error, 1);\r
1809     }\r
1810 }\r
1811 \r
1812 void\r
1813 PackHolding(packed, holding)\r
1814      char packed[];\r
1815      char *holding;\r
1816 {\r
1817     char *p = holding;\r
1818     char *q = packed;\r
1819     int runlength = 0;\r
1820     int curr = 9999;\r
1821     do {\r
1822         if (*p == curr) {\r
1823             runlength++;\r
1824         } else {\r
1825             switch (runlength) {\r
1826               case 0:\r
1827                 break;\r
1828               case 1:\r
1829                 *q++ = curr;\r
1830                 break;\r
1831               case 2:\r
1832                 *q++ = curr;\r
1833                 *q++ = curr;\r
1834                 break;\r
1835               default:\r
1836                 sprintf(q, "%d", runlength);\r
1837                 while (*q) q++;\r
1838                 *q++ = curr;\r
1839                 break;\r
1840             }\r
1841             runlength = 1;\r
1842             curr = *p;\r
1843         }\r
1844     } while (*p++);\r
1845     *q = NULLCHAR;\r
1846 }\r
1847 \r
1848 /* Telnet protocol requests from the front end */\r
1849 void\r
1850 TelnetRequest(ddww, option)\r
1851      unsigned char ddww, option;\r
1852 {\r
1853     unsigned char msg[3];\r
1854     int outCount, outError;\r
1855 \r
1856     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;\r
1857 \r
1858     if (appData.debugMode) {\r
1859         char buf1[8], buf2[8], *ddwwStr, *optionStr;\r
1860         switch (ddww) {\r
1861           case TN_DO:\r
1862             ddwwStr = "DO";\r
1863             break;\r
1864           case TN_DONT:\r
1865             ddwwStr = "DONT";\r
1866             break;\r
1867           case TN_WILL:\r
1868             ddwwStr = "WILL";\r
1869             break;\r
1870           case TN_WONT:\r
1871             ddwwStr = "WONT";\r
1872             break;\r
1873           default:\r
1874             ddwwStr = buf1;\r
1875             sprintf(buf1, "%d", ddww);\r
1876             break;\r
1877         }\r
1878         switch (option) {\r
1879           case TN_ECHO:\r
1880             optionStr = "ECHO";\r
1881             break;\r
1882           default:\r
1883             optionStr = buf2;\r
1884             sprintf(buf2, "%d", option);\r
1885             break;\r
1886         }\r
1887         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);\r
1888     }\r
1889     msg[0] = TN_IAC;\r
1890     msg[1] = ddww;\r
1891     msg[2] = option;\r
1892     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);\r
1893     if (outCount < 3) {\r
1894         DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
1895     }\r
1896 }\r
1897 \r
1898 void\r
1899 DoEcho()\r
1900 {\r
1901     if (!appData.icsActive) return;\r
1902     TelnetRequest(TN_DO, TN_ECHO);\r
1903 }\r
1904 \r
1905 void\r
1906 DontEcho()\r
1907 {\r
1908     if (!appData.icsActive) return;\r
1909     TelnetRequest(TN_DONT, TN_ECHO);\r
1910 }\r
1911 \r
1912 void\r
1913 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)\r
1914 {\r
1915     /* put the holdings sent to us by the server on the board holdings area */\r
1916     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;\r
1917     char p;\r
1918     ChessSquare piece;\r
1919 \r
1920     if(gameInfo.holdingsWidth < 2)  return;\r
1921 \r
1922     if( (int)lowestPiece >= BlackPawn ) {\r
1923         holdingsColumn = 0;\r
1924         countsColumn = 1;\r
1925         holdingsStartRow = BOARD_HEIGHT-1;\r
1926         direction = -1;\r
1927     } else {\r
1928         holdingsColumn = BOARD_WIDTH-1;\r
1929         countsColumn = BOARD_WIDTH-2;\r
1930         holdingsStartRow = 0;\r
1931         direction = 1;\r
1932     }\r
1933 \r
1934     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */\r
1935         board[i][holdingsColumn] = EmptySquare;\r
1936         board[i][countsColumn]   = (ChessSquare) 0;\r
1937     }\r
1938     while( (p=*holdings++) != NULLCHAR ) {\r
1939         piece = CharToPiece( ToUpper(p) );\r
1940         if(piece == EmptySquare) continue;\r
1941         /*j = (int) piece - (int) WhitePawn;*/\r
1942         j = PieceToNumber(piece);\r
1943         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */\r
1944         if(j < 0) continue;               /* should not happen */\r
1945         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );\r
1946         board[holdingsStartRow+j*direction][holdingsColumn] = piece;\r
1947         board[holdingsStartRow+j*direction][countsColumn]++;\r
1948     }\r
1949 \r
1950 }\r
1951 \r
1952 \r
1953 void\r
1954 VariantSwitch(Board board, VariantClass newVariant)\r
1955 {\r
1956    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;\r
1957    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;\r
1958 //   Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;\r
1959 \r
1960    startedFromPositionFile = FALSE;\r
1961    if(gameInfo.variant == newVariant) return;\r
1962 \r
1963    /* [HGM] This routine is called each time an assignment is made to\r
1964     * gameInfo.variant during a game, to make sure the board sizes\r
1965     * are set to match the new variant. If that means adding or deleting\r
1966     * holdings, we shift the playing board accordingly\r
1967     * This kludge is needed because in ICS observe mode, we get boards\r
1968     * of an ongoing game without knowing the variant, and learn about the\r
1969     * latter only later. This can be because of the move list we requested,\r
1970     * in which case the game history is refilled from the beginning anyway,\r
1971     * but also when receiving holdings of a crazyhouse game. In the latter\r
1972     * case we want to add those holdings to the already received position.\r
1973     */\r
1974 \r
1975 \r
1976   if (appData.debugMode) {\r
1977     fprintf(debugFP, "Switch board from %s to %s\n",\r
1978                VariantName(gameInfo.variant), VariantName(newVariant));\r
1979     setbuf(debugFP, NULL);\r
1980   }\r
1981     shuffleOpenings = 0;       /* [HGM] shuffle */\r
1982     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */\r
1983     switch(newVariant) {\r
1984             case VariantShogi:\r
1985               newWidth = 9;  newHeight = 9;\r
1986               gameInfo.holdingsSize = 7;\r
1987             case VariantBughouse:\r
1988             case VariantCrazyhouse:\r
1989               newHoldingsWidth = 2; break;\r
1990             default:\r
1991               newHoldingsWidth = gameInfo.holdingsSize = 0;\r
1992     }\r
1993 \r
1994     if(newWidth  != gameInfo.boardWidth  ||\r
1995        newHeight != gameInfo.boardHeight ||\r
1996        newHoldingsWidth != gameInfo.holdingsWidth ) {\r
1997 \r
1998         /* shift position to new playing area, if needed */\r
1999         if(newHoldingsWidth > gameInfo.holdingsWidth) {\r
2000            for(i=0; i<BOARD_HEIGHT; i++) \r
2001                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)\r
2002                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =\r
2003                                                      board[i][j];\r
2004            for(i=0; i<newHeight; i++) {\r
2005                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;\r
2006                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;\r
2007            }\r
2008         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {\r
2009            for(i=0; i<BOARD_HEIGHT; i++)\r
2010                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
2011                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =\r
2012                                                  board[i][j];\r
2013         }\r
2014 \r
2015         gameInfo.boardWidth  = newWidth;\r
2016         gameInfo.boardHeight = newHeight;\r
2017         gameInfo.holdingsWidth = newHoldingsWidth;\r
2018         gameInfo.variant = newVariant;\r
2019         InitDrawingSizes(-2, 0);\r
2020 \r
2021         /* [HGM] The following should definitely be solved in a better way */\r
2022 #if 0\r
2023         CopyBoard(board, tempBoard); /* save position in case it is board[0] */\r
2024         for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];\r
2025         saveEP = epStatus[0];\r
2026 #endif\r
2027         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */\r
2028 #if 0\r
2029         epStatus[0] = saveEP;\r
2030         for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];\r
2031         CopyBoard(tempBoard, board); /* restore position received from ICS   */\r
2032 #endif\r
2033     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }\r
2034 \r
2035     forwardMostMove = oldForwardMostMove;\r
2036     backwardMostMove = oldBackwardMostMove;\r
2037     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */\r
2038 }\r
2039 \r
2040 static int loggedOn = FALSE;\r
2041 \r
2042 /*-- Game start info cache: --*/\r
2043 int gs_gamenum;\r
2044 char gs_kind[MSG_SIZ];\r
2045 static char player1Name[128] = "";\r
2046 static char player2Name[128] = "";\r
2047 static int player1Rating = -1;\r
2048 static int player2Rating = -1;\r
2049 /*----------------------------*/\r
2050 \r
2051 ColorClass curColor = ColorNormal;\r
2052 int suppressKibitz = 0;\r
2053 \r
2054 void\r
2055 read_from_ics(isr, closure, data, count, error)\r
2056      InputSourceRef isr;\r
2057      VOIDSTAR closure;\r
2058      char *data;\r
2059      int count;\r
2060      int error;\r
2061 {\r
2062 #define BUF_SIZE 8192\r
2063 #define STARTED_NONE 0\r
2064 #define STARTED_MOVES 1\r
2065 #define STARTED_BOARD 2\r
2066 #define STARTED_OBSERVE 3\r
2067 #define STARTED_HOLDINGS 4\r
2068 #define STARTED_CHATTER 5\r
2069 #define STARTED_COMMENT 6\r
2070 #define STARTED_MOVES_NOHIDE 7\r
2071     \r
2072     static int started = STARTED_NONE;\r
2073     static char parse[20000];\r
2074     static int parse_pos = 0;\r
2075     static char buf[BUF_SIZE + 1];\r
2076     static int firstTime = TRUE, intfSet = FALSE;\r
2077     static ColorClass prevColor = ColorNormal;\r
2078     static int savingComment = FALSE;\r
2079     char str[500];\r
2080     int i, oldi;\r
2081     int buf_len;\r
2082     int next_out;\r
2083     int tkind;\r
2084     int backup;    /* [DM] For zippy color lines */\r
2085     char *p;\r
2086 \r
2087     if (appData.debugMode) {\r
2088       if (!error) {\r
2089         fprintf(debugFP, "<ICS: ");\r
2090         show_bytes(debugFP, data, count);\r
2091         fprintf(debugFP, "\n");\r
2092       }\r
2093     }\r
2094 \r
2095     if (appData.debugMode) { int f = forwardMostMove;\r
2096         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,\r
2097                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
2098     }\r
2099     if (count > 0) {\r
2100         /* If last read ended with a partial line that we couldn't parse,\r
2101            prepend it to the new read and try again. */\r
2102         if (leftover_len > 0) {\r
2103             for (i=0; i<leftover_len; i++)\r
2104               buf[i] = buf[leftover_start + i];\r
2105         }\r
2106 \r
2107         /* Copy in new characters, removing nulls and \r's */\r
2108         buf_len = leftover_len;\r
2109         for (i = 0; i < count; i++) {\r
2110             if (data[i] != NULLCHAR && data[i] != '\r')\r
2111               buf[buf_len++] = data[i];\r
2112             if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && \r
2113                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') \r
2114                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous\r
2115         }\r
2116 \r
2117         buf[buf_len] = NULLCHAR;\r
2118         next_out = leftover_len;\r
2119         leftover_start = 0;\r
2120         \r
2121         i = 0;\r
2122         while (i < buf_len) {\r
2123             /* Deal with part of the TELNET option negotiation\r
2124                protocol.  We refuse to do anything beyond the\r
2125                defaults, except that we allow the WILL ECHO option,\r
2126                which ICS uses to turn off password echoing when we are\r
2127                directly connected to it.  We reject this option\r
2128                if localLineEditing mode is on (always on in xboard)\r
2129                and we are talking to port 23, which might be a real\r
2130                telnet server that will try to keep WILL ECHO on permanently.\r
2131              */\r
2132             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {\r
2133                 static int remoteEchoOption = FALSE; /* telnet ECHO option */\r
2134                 unsigned char option;\r
2135                 oldi = i;\r
2136                 switch ((unsigned char) buf[++i]) {\r
2137                   case TN_WILL:\r
2138                     if (appData.debugMode)\r
2139                       fprintf(debugFP, "\n<WILL ");\r
2140                     switch (option = (unsigned char) buf[++i]) {\r
2141                       case TN_ECHO:\r
2142                         if (appData.debugMode)\r
2143                           fprintf(debugFP, "ECHO ");\r
2144                         /* Reply only if this is a change, according\r
2145                            to the protocol rules. */\r
2146                         if (remoteEchoOption) break;\r
2147                         if (appData.localLineEditing &&\r
2148                             atoi(appData.icsPort) == TN_PORT) {\r
2149                             TelnetRequest(TN_DONT, TN_ECHO);\r
2150                         } else {\r
2151                             EchoOff();\r
2152                             TelnetRequest(TN_DO, TN_ECHO);\r
2153                             remoteEchoOption = TRUE;\r
2154                         }\r
2155                         break;\r
2156                       default:\r
2157                         if (appData.debugMode)\r
2158                           fprintf(debugFP, "%d ", option);\r
2159                         /* Whatever this is, we don't want it. */\r
2160                         TelnetRequest(TN_DONT, option);\r
2161                         break;\r
2162                     }\r
2163                     break;\r
2164                   case TN_WONT:\r
2165                     if (appData.debugMode)\r
2166                       fprintf(debugFP, "\n<WONT ");\r
2167                     switch (option = (unsigned char) buf[++i]) {\r
2168                       case TN_ECHO:\r
2169                         if (appData.debugMode)\r
2170                           fprintf(debugFP, "ECHO ");\r
2171                         /* Reply only if this is a change, according\r
2172                            to the protocol rules. */\r
2173                         if (!remoteEchoOption) break;\r
2174                         EchoOn();\r
2175                         TelnetRequest(TN_DONT, TN_ECHO);\r
2176                         remoteEchoOption = FALSE;\r
2177                         break;\r
2178                       default:\r
2179                         if (appData.debugMode)\r
2180                           fprintf(debugFP, "%d ", (unsigned char) option);\r
2181                         /* Whatever this is, it must already be turned\r
2182                            off, because we never agree to turn on\r
2183                            anything non-default, so according to the\r
2184                            protocol rules, we don't reply. */\r
2185                         break;\r
2186                     }\r
2187                     break;\r
2188                   case TN_DO:\r
2189                     if (appData.debugMode)\r
2190                       fprintf(debugFP, "\n<DO ");\r
2191                     switch (option = (unsigned char) buf[++i]) {\r
2192                       default:\r
2193                         /* Whatever this is, we refuse to do it. */\r
2194                         if (appData.debugMode)\r
2195                           fprintf(debugFP, "%d ", option);\r
2196                         TelnetRequest(TN_WONT, option);\r
2197                         break;\r
2198                     }\r
2199                     break;\r
2200                   case TN_DONT:\r
2201                     if (appData.debugMode)\r
2202                       fprintf(debugFP, "\n<DONT ");\r
2203                     switch (option = (unsigned char) buf[++i]) {\r
2204                       default:\r
2205                         if (appData.debugMode)\r
2206                           fprintf(debugFP, "%d ", option);\r
2207                         /* Whatever this is, we are already not doing\r
2208                            it, because we never agree to do anything\r
2209                            non-default, so according to the protocol\r
2210                            rules, we don't reply. */\r
2211                         break;\r
2212                     }\r
2213                     break;\r
2214                   case TN_IAC:\r
2215                     if (appData.debugMode)\r
2216                       fprintf(debugFP, "\n<IAC ");\r
2217                     /* Doubled IAC; pass it through */\r
2218                     i--;\r
2219                     break;\r
2220                   default:\r
2221                     if (appData.debugMode)\r
2222                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);\r
2223                     /* Drop all other telnet commands on the floor */\r
2224                     break;\r
2225                 }\r
2226                 if (oldi > next_out)\r
2227                   SendToPlayer(&buf[next_out], oldi - next_out);\r
2228                 if (++i > next_out)\r
2229                   next_out = i;\r
2230                 continue;\r
2231             }\r
2232                 \r
2233             /* OK, this at least will *usually* work */\r
2234             if (!loggedOn && looking_at(buf, &i, "ics%")) {\r
2235                 loggedOn = TRUE;\r
2236             }\r
2237             \r
2238             if (loggedOn && !intfSet) {\r
2239                 if (ics_type == ICS_ICC) {\r
2240                   sprintf(str,\r
2241                           "/set-quietly interface %s\n/set-quietly style 12\n",\r
2242                           programVersion);\r
2243 \r
2244                 } else if (ics_type == ICS_CHESSNET) {\r
2245                   sprintf(str, "/style 12\n");\r
2246                 } else {\r
2247                   strcpy(str, "alias $ @\n$set interface ");\r
2248                   strcat(str, programVersion);\r
2249                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");\r
2250 #ifdef WIN32\r
2251                   strcat(str, "$iset nohighlight 1\n");\r
2252 #endif\r
2253                   strcat(str, "$iset lock 1\n$style 12\n");\r
2254                 }\r
2255                 SendToICS(str);\r
2256                 intfSet = TRUE;\r
2257             }\r
2258 \r
2259             if (started == STARTED_COMMENT) {\r
2260                 /* Accumulate characters in comment */\r
2261                 parse[parse_pos++] = buf[i];\r
2262                 if (buf[i] == '\n') {\r
2263                     parse[parse_pos] = NULLCHAR;\r
2264                     if(!suppressKibitz) // [HGM] kibitz\r
2265                         AppendComment(forwardMostMove, StripHighlight(parse));\r
2266                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window\r
2267                         int nrDigit = 0, nrAlph = 0, i;\r
2268                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input\r
2269                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }\r
2270                         parse[parse_pos] = NULLCHAR;\r
2271                         // try to be smart: if it does not look like search info, it should go to\r
2272                         // ICS interaction window after all, not to engine-output window.\r
2273                         for(i=0; i<parse_pos; i++) { // count letters and digits\r
2274                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');\r
2275                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');\r
2276                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');\r
2277                         }\r
2278                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info\r
2279                             int depth=0; float score;\r
2280                             if(sscanf(parse, "%f/%d", &score, &depth) == 2 && depth>0) {\r
2281                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph\r
2282                                 pvInfoList[forwardMostMove-1].depth = depth;\r
2283                                 pvInfoList[forwardMostMove-1].score = 100*score;\r
2284                             }\r
2285                             OutputKibitz(suppressKibitz, parse);\r
2286                         } else {\r
2287                             char tmp[MSG_SIZ];\r
2288                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);\r
2289                             SendToPlayer(tmp, strlen(tmp));\r
2290                         }\r
2291                     }\r
2292                     started = STARTED_NONE;\r
2293                 } else {\r
2294                     /* Don't match patterns against characters in chatter */\r
2295                     i++;\r
2296                     continue;\r
2297                 }\r
2298             }\r
2299             if (started == STARTED_CHATTER) {\r
2300                 if (buf[i] != '\n') {\r
2301                     /* Don't match patterns against characters in chatter */\r
2302                     i++;\r
2303                     continue;\r
2304                 }\r
2305                 started = STARTED_NONE;\r
2306             }\r
2307 \r
2308             /* Kludge to deal with rcmd protocol */\r
2309             if (firstTime && looking_at(buf, &i, "\001*")) {\r
2310                 DisplayFatalError(&buf[1], 0, 1);\r
2311                 continue;\r
2312             } else {\r
2313                 firstTime = FALSE;\r
2314             }\r
2315 \r
2316             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {\r
2317                 ics_type = ICS_ICC;\r
2318                 ics_prefix = "/";\r
2319                 if (appData.debugMode)\r
2320                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2321                 continue;\r
2322             }\r
2323             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {\r
2324                 ics_type = ICS_FICS;\r
2325                 ics_prefix = "$";\r
2326                 if (appData.debugMode)\r
2327                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2328                 continue;\r
2329             }\r
2330             if (!loggedOn && looking_at(buf, &i, "chess.net")) {\r
2331                 ics_type = ICS_CHESSNET;\r
2332                 ics_prefix = "/";\r
2333                 if (appData.debugMode)\r
2334                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2335                 continue;\r
2336             }\r
2337 \r
2338             if (!loggedOn &&\r
2339                 (looking_at(buf, &i, "\"*\" is *a registered name") ||\r
2340                  looking_at(buf, &i, "Logging you in as \"*\"") ||\r
2341                  looking_at(buf, &i, "will be \"*\""))) {\r
2342               strcpy(ics_handle, star_match[0]);\r
2343               continue;\r
2344             }\r
2345 \r
2346             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {\r
2347               char buf[MSG_SIZ];\r
2348               sprintf(buf, "%s@%s", ics_handle, appData.icsHost);\r
2349               DisplayIcsInteractionTitle(buf);\r
2350               have_set_title = TRUE;\r
2351             }\r
2352 \r
2353             /* skip finger notes */\r
2354             if (started == STARTED_NONE &&\r
2355                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||\r
2356                  (buf[i] == '1' && buf[i+1] == '0')) &&\r
2357                 buf[i+2] == ':' && buf[i+3] == ' ') {\r
2358               started = STARTED_CHATTER;\r
2359               i += 3;\r
2360               continue;\r
2361             }\r
2362 \r
2363             /* skip formula vars */\r
2364             if (started == STARTED_NONE &&\r
2365                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {\r
2366               started = STARTED_CHATTER;\r
2367               i += 3;\r
2368               continue;\r
2369             }\r
2370 \r
2371             oldi = i;\r
2372             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window\r
2373             if (appData.autoKibitz && started == STARTED_NONE && \r
2374                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze\r
2375                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {\r
2376                 if(looking_at(buf, &i, "* kibitzes: ") &&\r
2377                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || \r
2378                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent\r
2379                         suppressKibitz = TRUE;\r
2380                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]\r
2381                                 && (gameMode == IcsPlayingWhite)) ||\r
2382                            (StrStr(star_match[0], gameInfo.black) == star_match[0]\r
2383                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz\r
2384                             started = STARTED_CHATTER; // own kibitz we simply discard\r
2385                         else {\r
2386                             started = STARTED_COMMENT; // make sure it will be collected in parse[]\r
2387                             parse_pos = 0; parse[0] = NULLCHAR;\r
2388                             savingComment = TRUE;\r
2389                             suppressKibitz = gameMode != IcsObserving ? 2 :\r
2390                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;\r
2391                         } \r
2392                         continue;\r
2393                 } else\r
2394                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz\r
2395                     started = STARTED_CHATTER;\r
2396                     suppressKibitz = TRUE;\r
2397                 }\r
2398             } // [HGM] kibitz: end of patch\r
2399 \r
2400             if (appData.zippyTalk || appData.zippyPlay) {\r
2401                 /* [DM] Backup address for color zippy lines */\r
2402                 backup = i;\r
2403 #if ZIPPY\r
2404        #ifdef WIN32\r
2405                if (loggedOn == TRUE)\r
2406                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||\r
2407                           (appData.zippyPlay && ZippyMatch(buf, &backup)));\r
2408        #else\r
2409                 if (ZippyControl(buf, &i) ||\r
2410                     ZippyConverse(buf, &i) ||\r
2411                     (appData.zippyPlay && ZippyMatch(buf, &i))) {\r
2412                       loggedOn = TRUE;\r
2413                       if (!appData.colorize) continue;\r
2414                 }\r
2415        #endif\r
2416 #endif\r
2417             } // [DM] 'else { ' deleted\r
2418                 if (/* Don't color "message" or "messages" output */\r
2419                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||\r
2420                     looking_at(buf, &i, "*. * at *:*: ") ||\r
2421                     looking_at(buf, &i, "--* (*:*): ") ||\r
2422                     /* Regular tells and says */\r
2423                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||\r
2424                     looking_at(buf, &i, "* (your partner) tells you: ") ||\r
2425                     looking_at(buf, &i, "* says: ") ||\r
2426                     /* Message notifications (same color as tells) */\r
2427                     looking_at(buf, &i, "* has left a message ") ||\r
2428                     looking_at(buf, &i, "* just sent you a message:\n") ||\r
2429                     /* Whispers and kibitzes */\r
2430                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||\r
2431                     looking_at(buf, &i, "* kibitzes: ") ||\r
2432                     /* Channel tells */\r
2433                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {\r
2434 \r
2435                   if (tkind == 1 && strchr(star_match[0], ':')) {\r
2436                       /* Avoid "tells you:" spoofs in channels */\r
2437                      tkind = 3;\r
2438                   }\r
2439                   if (star_match[0][0] == NULLCHAR ||\r
2440                       strchr(star_match[0], ' ') ||\r
2441                       (tkind == 3 && strchr(star_match[1], ' '))) {\r
2442                     /* Reject bogus matches */\r
2443                     i = oldi;\r
2444                   } else {\r
2445                     if (appData.colorize) {\r
2446                       if (oldi > next_out) {\r
2447                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2448                         next_out = oldi;\r
2449                       }\r
2450                       switch (tkind) {\r
2451                       case 1:\r
2452                         Colorize(ColorTell, FALSE);\r
2453                         curColor = ColorTell;\r
2454                         break;\r
2455                       case 2:\r
2456                         Colorize(ColorKibitz, FALSE);\r
2457                         curColor = ColorKibitz;\r
2458                         break;\r
2459                       case 3:\r
2460                         p = strrchr(star_match[1], '(');\r
2461                         if (p == NULL) {\r
2462                           p = star_match[1];\r
2463                         } else {\r
2464                           p++;\r
2465                         }\r
2466                         if (atoi(p) == 1) {\r
2467                           Colorize(ColorChannel1, FALSE);\r
2468                           curColor = ColorChannel1;\r
2469                         } else {\r
2470                           Colorize(ColorChannel, FALSE);\r
2471                           curColor = ColorChannel;\r
2472                         }\r
2473                         break;\r
2474                       case 5:\r
2475                         curColor = ColorNormal;\r
2476                         break;\r
2477                       }\r
2478                     }\r
2479                     if (started == STARTED_NONE && appData.autoComment &&\r
2480                         (gameMode == IcsObserving ||\r
2481                          gameMode == IcsPlayingWhite ||\r
2482                          gameMode == IcsPlayingBlack)) {\r
2483                       parse_pos = i - oldi;\r
2484                       memcpy(parse, &buf[oldi], parse_pos);\r
2485                       parse[parse_pos] = NULLCHAR;\r
2486                       started = STARTED_COMMENT;\r
2487                       savingComment = TRUE;\r
2488                     } else {\r
2489                       started = STARTED_CHATTER;\r
2490                       savingComment = FALSE;\r
2491                     }\r
2492                     loggedOn = TRUE;\r
2493                     continue;\r
2494                   }\r
2495                 }\r
2496 \r
2497                 if (looking_at(buf, &i, "* s-shouts: ") ||\r
2498                     looking_at(buf, &i, "* c-shouts: ")) {\r
2499                     if (appData.colorize) {\r
2500                         if (oldi > next_out) {\r
2501                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2502                             next_out = oldi;\r
2503                         }\r
2504                         Colorize(ColorSShout, FALSE);\r
2505                         curColor = ColorSShout;\r
2506                     }\r
2507                     loggedOn = TRUE;\r
2508                     started = STARTED_CHATTER;\r
2509                     continue;\r
2510                 }\r
2511 \r
2512                 if (looking_at(buf, &i, "--->")) {\r
2513                     loggedOn = TRUE;\r
2514                     continue;\r
2515                 }\r
2516 \r
2517                 if (looking_at(buf, &i, "* shouts: ") ||\r
2518                     looking_at(buf, &i, "--> ")) {\r
2519                     if (appData.colorize) {\r
2520                         if (oldi > next_out) {\r
2521                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2522                             next_out = oldi;\r
2523                         }\r
2524                         Colorize(ColorShout, FALSE);\r
2525                         curColor = ColorShout;\r
2526                     }\r
2527                     loggedOn = TRUE;\r
2528                     started = STARTED_CHATTER;\r
2529                     continue;\r
2530                 }\r
2531 \r
2532                 if (looking_at( buf, &i, "Challenge:")) {\r
2533                     if (appData.colorize) {\r
2534                         if (oldi > next_out) {\r
2535                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2536                             next_out = oldi;\r
2537                         }\r
2538                         Colorize(ColorChallenge, FALSE);\r
2539                         curColor = ColorChallenge;\r
2540                     }\r
2541                     loggedOn = TRUE;\r
2542                     continue;\r
2543                 }\r
2544 \r
2545                 if (looking_at(buf, &i, "* offers you") ||\r
2546                     looking_at(buf, &i, "* offers to be") ||\r
2547                     looking_at(buf, &i, "* would like to") ||\r
2548                     looking_at(buf, &i, "* requests to") ||\r
2549                     looking_at(buf, &i, "Your opponent offers") ||\r
2550                     looking_at(buf, &i, "Your opponent requests")) {\r
2551 \r
2552                     if (appData.colorize) {\r
2553                         if (oldi > next_out) {\r
2554                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2555                             next_out = oldi;\r
2556                         }\r
2557                         Colorize(ColorRequest, FALSE);\r
2558                         curColor = ColorRequest;\r
2559                     }\r
2560                     continue;\r
2561                 }\r
2562 \r
2563                 if (looking_at(buf, &i, "* (*) seeking")) {\r
2564                     if (appData.colorize) {\r
2565                         if (oldi > next_out) {\r
2566                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2567                             next_out = oldi;\r
2568                         }\r
2569                         Colorize(ColorSeek, FALSE);\r
2570                         curColor = ColorSeek;\r
2571                     }\r
2572                     continue;\r
2573             }\r
2574 \r
2575             if (looking_at(buf, &i, "\\   ")) {\r
2576                 if (prevColor != ColorNormal) {\r
2577                     if (oldi > next_out) {\r
2578                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2579                         next_out = oldi;\r
2580                     }\r
2581                     Colorize(prevColor, TRUE);\r
2582                     curColor = prevColor;\r
2583                 }\r
2584                 if (savingComment) {\r
2585                     parse_pos = i - oldi;\r
2586                     memcpy(parse, &buf[oldi], parse_pos);\r
2587                     parse[parse_pos] = NULLCHAR;\r
2588                     started = STARTED_COMMENT;\r
2589                 } else {\r
2590                     started = STARTED_CHATTER;\r
2591                 }\r
2592                 continue;\r
2593             }\r
2594 \r
2595             if (looking_at(buf, &i, "Black Strength :") ||\r
2596                 looking_at(buf, &i, "<<< style 10 board >>>") ||\r
2597                 looking_at(buf, &i, "<10>") ||\r
2598                 looking_at(buf, &i, "#@#")) {\r
2599                 /* Wrong board style */\r
2600                 loggedOn = TRUE;\r
2601                 SendToICS(ics_prefix);\r
2602                 SendToICS("set style 12\n");\r
2603                 SendToICS(ics_prefix);\r
2604                 SendToICS("refresh\n");\r
2605                 continue;\r
2606             }\r
2607             \r
2608             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {\r
2609                 ICSInitScript();\r
2610                 have_sent_ICS_logon = 1;\r
2611                 continue;\r
2612             }\r
2613               \r
2614             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && \r
2615                 (looking_at(buf, &i, "\n<12> ") ||\r
2616                  looking_at(buf, &i, "<12> "))) {\r
2617                 loggedOn = TRUE;\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_BOARD;\r
2623                 parse_pos = 0;\r
2624                 continue;\r
2625             }\r
2626 \r
2627             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||\r
2628                 looking_at(buf, &i, "<b1> ")) {\r
2629                 if (oldi > next_out) {\r
2630                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2631                 }\r
2632                 next_out = i;\r
2633                 started = STARTED_HOLDINGS;\r
2634                 parse_pos = 0;\r
2635                 continue;\r
2636             }\r
2637 \r
2638             if (looking_at(buf, &i, "* *vs. * *--- *")) {\r
2639                 loggedOn = TRUE;\r
2640                 /* Header for a move list -- first line */\r
2641 \r
2642                 switch (ics_getting_history) {\r
2643                   case H_FALSE:\r
2644                     switch (gameMode) {\r
2645                       case IcsIdle:\r
2646                       case BeginningOfGame:\r
2647                         /* User typed "moves" or "oldmoves" while we\r
2648                            were idle.  Pretend we asked for these\r
2649                            moves and soak them up so user can step\r
2650                            through them and/or save them.\r
2651                            */\r
2652                         Reset(FALSE, TRUE);\r
2653                         gameMode = IcsObserving;\r
2654                         ModeHighlight();\r
2655                         ics_gamenum = -1;\r
2656                         ics_getting_history = H_GOT_UNREQ_HEADER;\r
2657                         break;\r
2658                       case EditGame: /*?*/\r
2659                       case EditPosition: /*?*/\r
2660                         /* Should above feature work in these modes too? */\r
2661                         /* For now it doesn't */\r
2662                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2663                         break;\r
2664                       default:\r
2665                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2666                         break;\r
2667                     }\r
2668                     break;\r
2669                   case H_REQUESTED:\r
2670                     /* Is this the right one? */\r
2671                     if (gameInfo.white && gameInfo.black &&\r
2672                         strcmp(gameInfo.white, star_match[0]) == 0 &&\r
2673                         strcmp(gameInfo.black, star_match[2]) == 0) {\r
2674                         /* All is well */\r
2675                         ics_getting_history = H_GOT_REQ_HEADER;\r
2676                     }\r
2677                     break;\r
2678                   case H_GOT_REQ_HEADER:\r
2679                   case H_GOT_UNREQ_HEADER:\r
2680                   case H_GOT_UNWANTED_HEADER:\r
2681                   case H_GETTING_MOVES:\r
2682                     /* Should not happen */\r
2683                     DisplayError(_("Error gathering move list: two headers"), 0);\r
2684                     ics_getting_history = H_FALSE;\r
2685                     break;\r
2686                 }\r
2687 \r
2688                 /* Save player ratings into gameInfo if needed */\r
2689                 if ((ics_getting_history == H_GOT_REQ_HEADER ||\r
2690                      ics_getting_history == H_GOT_UNREQ_HEADER) &&\r
2691                     (gameInfo.whiteRating == -1 ||\r
2692                      gameInfo.blackRating == -1)) {\r
2693 \r
2694                     gameInfo.whiteRating = string_to_rating(star_match[1]);\r
2695                     gameInfo.blackRating = string_to_rating(star_match[3]);\r
2696                     if (appData.debugMode)\r
2697                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), \r
2698                               gameInfo.whiteRating, gameInfo.blackRating);\r
2699                 }\r
2700                 continue;\r
2701             }\r
2702 \r
2703             if (looking_at(buf, &i,\r
2704               "* * match, initial time: * minute*, increment: * second")) {\r
2705                 /* Header for a move list -- second line */\r
2706                 /* Initial board will follow if this is a wild game */\r
2707                 if (gameInfo.event != NULL) free(gameInfo.event);\r
2708                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);\r
2709                 gameInfo.event = StrSave(str);\r
2710                 /* [HGM] we switched variant. Translate boards if needed. */\r
2711                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));\r
2712                 continue;\r
2713             }\r
2714 \r
2715             if (looking_at(buf, &i, "Move  ")) {\r
2716                 /* Beginning of a move list */\r
2717                 switch (ics_getting_history) {\r
2718                   case H_FALSE:\r
2719                     /* Normally should not happen */\r
2720                     /* Maybe user hit reset while we were parsing */\r
2721                     break;\r
2722                   case H_REQUESTED:\r
2723                     /* Happens if we are ignoring a move list that is not\r
2724                      * the one we just requested.  Common if the user\r
2725                      * tries to observe two games without turning off\r
2726                      * getMoveList */\r
2727                     break;\r
2728                   case H_GETTING_MOVES:\r
2729                     /* Should not happen */\r
2730                     DisplayError(_("Error gathering move list: nested"), 0);\r
2731                     ics_getting_history = H_FALSE;\r
2732                     break;\r
2733                   case H_GOT_REQ_HEADER:\r
2734                     ics_getting_history = H_GETTING_MOVES;\r
2735                     started = STARTED_MOVES;\r
2736                     parse_pos = 0;\r
2737                     if (oldi > next_out) {\r
2738                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2739                     }\r
2740                     break;\r
2741                   case H_GOT_UNREQ_HEADER:\r
2742                     ics_getting_history = H_GETTING_MOVES;\r
2743                     started = STARTED_MOVES_NOHIDE;\r
2744                     parse_pos = 0;\r
2745                     break;\r
2746                   case H_GOT_UNWANTED_HEADER:\r
2747                     ics_getting_history = H_FALSE;\r
2748                     break;\r
2749                 }\r
2750                 continue;\r
2751             }                           \r
2752             \r
2753             if (looking_at(buf, &i, "% ") ||\r
2754                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)\r
2755                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book\r
2756                 savingComment = FALSE;\r
2757                 switch (started) {\r
2758                   case STARTED_MOVES:\r
2759                   case STARTED_MOVES_NOHIDE:\r
2760                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);\r
2761                     parse[parse_pos + i - oldi] = NULLCHAR;\r
2762                     ParseGameHistory(parse);\r
2763 #if ZIPPY\r
2764                     if (appData.zippyPlay && first.initDone) {\r
2765                         FeedMovesToProgram(&first, forwardMostMove);\r
2766                         if (gameMode == IcsPlayingWhite) {\r
2767                             if (WhiteOnMove(forwardMostMove)) {\r
2768                                 if (first.sendTime) {\r
2769                                   if (first.useColors) {\r
2770                                     SendToProgram("black\n", &first); \r
2771                                   }\r
2772                                   SendTimeRemaining(&first, TRUE);\r
2773                                 }\r
2774 #if 0\r
2775                                 if (first.useColors) {\r
2776                                   SendToProgram("white\ngo\n", &first);\r
2777                                 } else {\r
2778                                   SendToProgram("go\n", &first);\r
2779                                 }\r
2780 #else\r
2781                                 if (first.useColors) {\r
2782                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent\r
2783                                 }\r
2784                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos\r
2785 #endif\r
2786                                 first.maybeThinking = TRUE;\r
2787                             } else {\r
2788                                 if (first.usePlayother) {\r
2789                                   if (first.sendTime) {\r
2790                                     SendTimeRemaining(&first, TRUE);\r
2791                                   }\r
2792                                   SendToProgram("playother\n", &first);\r
2793                                   firstMove = FALSE;\r
2794                                 } else {\r
2795                                   firstMove = TRUE;\r
2796                                 }\r
2797                             }\r
2798                         } else if (gameMode == IcsPlayingBlack) {\r
2799                             if (!WhiteOnMove(forwardMostMove)) {\r
2800                                 if (first.sendTime) {\r
2801                                   if (first.useColors) {\r
2802                                     SendToProgram("white\n", &first);\r
2803                                   }\r
2804                                   SendTimeRemaining(&first, FALSE);\r
2805                                 }\r
2806 #if 0\r
2807                                 if (first.useColors) {\r
2808                                   SendToProgram("black\ngo\n", &first);\r
2809                                 } else {\r
2810                                   SendToProgram("go\n", &first);\r
2811                                 }\r
2812 #else\r
2813                                 if (first.useColors) {\r
2814                                   SendToProgram("black\n", &first);\r
2815                                 }\r
2816                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);\r
2817 #endif\r
2818                                 first.maybeThinking = TRUE;\r
2819                             } else {\r
2820                                 if (first.usePlayother) {\r
2821                                   if (first.sendTime) {\r
2822                                     SendTimeRemaining(&first, FALSE);\r
2823                                   }\r
2824                                   SendToProgram("playother\n", &first);\r
2825                                   firstMove = FALSE;\r
2826                                 } else {\r
2827                                   firstMove = TRUE;\r
2828                                 }\r
2829                             }\r
2830                         }                       \r
2831                     }\r
2832 #endif\r
2833                     if (gameMode == IcsObserving && ics_gamenum == -1) {\r
2834                         /* Moves came from oldmoves or moves command\r
2835                            while we weren't doing anything else.\r
2836                            */\r
2837                         currentMove = forwardMostMove;\r
2838                         ClearHighlights();/*!!could figure this out*/\r
2839                         flipView = appData.flipView;\r
2840                         DrawPosition(FALSE, boards[currentMove]);\r
2841                         DisplayBothClocks();\r
2842                         sprintf(str, "%s vs. %s",\r
2843                                 gameInfo.white, gameInfo.black);\r
2844                         DisplayTitle(str);\r
2845                         gameMode = IcsIdle;\r
2846                     } else {\r
2847                         /* Moves were history of an active game */\r
2848                         if (gameInfo.resultDetails != NULL) {\r
2849                             free(gameInfo.resultDetails);\r
2850                             gameInfo.resultDetails = NULL;\r
2851                         }\r
2852                     }\r
2853                     HistorySet(parseList, backwardMostMove,\r
2854                                forwardMostMove, currentMove-1);\r
2855                     DisplayMove(currentMove - 1);\r
2856                     if (started == STARTED_MOVES) next_out = i;\r
2857                     started = STARTED_NONE;\r
2858                     ics_getting_history = H_FALSE;\r
2859                     break;\r
2860 \r
2861                   case STARTED_OBSERVE:\r
2862                     started = STARTED_NONE;\r
2863                     SendToICS(ics_prefix);\r
2864                     SendToICS("refresh\n");\r
2865                     break;\r
2866 \r
2867                   default:\r
2868                     break;\r
2869                 }\r
2870                 if(bookHit) { // [HGM] book: simulate book reply\r
2871                     static char bookMove[MSG_SIZ]; // a bit generous?\r
2872 \r
2873                     programStats.nodes = programStats.depth = programStats.time = \r
2874                     programStats.score = programStats.got_only_move = 0;\r
2875                     sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
2876 \r
2877                     strcpy(bookMove, "move ");\r
2878                     strcat(bookMove, bookHit);\r
2879                     HandleMachineMove(bookMove, &first);\r
2880                 }\r
2881                 continue;\r
2882             }\r
2883             \r
2884             if ((started == STARTED_MOVES || started == STARTED_BOARD ||\r
2885                  started == STARTED_HOLDINGS ||\r
2886                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {\r
2887                 /* Accumulate characters in move list or board */\r
2888                 parse[parse_pos++] = buf[i];\r
2889             }\r
2890             \r
2891             /* Start of game messages.  Mostly we detect start of game\r
2892                when the first board image arrives.  On some versions\r
2893                of the ICS, though, we need to do a "refresh" after starting\r
2894                to observe in order to get the current board right away. */\r
2895             if (looking_at(buf, &i, "Adding game * to observation list")) {\r
2896                 started = STARTED_OBSERVE;\r
2897                 continue;\r
2898             }\r
2899 \r
2900             /* Handle auto-observe */\r
2901             if (appData.autoObserve &&\r
2902                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&\r
2903                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {\r
2904                 char *player;\r
2905                 /* Choose the player that was highlighted, if any. */\r
2906                 if (star_match[0][0] == '\033' ||\r
2907                     star_match[1][0] != '\033') {\r
2908                     player = star_match[0];\r
2909                 } else {\r
2910                     player = star_match[2];\r
2911                 }\r
2912                 sprintf(str, "%sobserve %s\n",\r
2913                         ics_prefix, StripHighlightAndTitle(player));\r
2914                 SendToICS(str);\r
2915 \r
2916                 /* Save ratings from notify string */\r
2917                 strcpy(player1Name, star_match[0]);\r
2918                 player1Rating = string_to_rating(star_match[1]);\r
2919                 strcpy(player2Name, star_match[2]);\r
2920                 player2Rating = string_to_rating(star_match[3]);\r
2921 \r
2922                 if (appData.debugMode)\r
2923                   fprintf(debugFP, \r
2924                           "Ratings from 'Game notification:' %s %d, %s %d\n",\r
2925                           player1Name, player1Rating,\r
2926                           player2Name, player2Rating);\r
2927 \r
2928                 continue;\r
2929             }\r
2930 \r
2931             /* Deal with automatic examine mode after a game,\r
2932                and with IcsObserving -> IcsExamining transition */\r
2933             if (looking_at(buf, &i, "Entering examine mode for game *") ||\r
2934                 looking_at(buf, &i, "has made you an examiner of game *")) {\r
2935 \r
2936                 int gamenum = atoi(star_match[0]);\r
2937                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&\r
2938                     gamenum == ics_gamenum) {\r
2939                     /* We were already playing or observing this game;\r
2940                        no need to refetch history */\r
2941                     gameMode = IcsExamining;\r
2942                     if (pausing) {\r
2943                         pauseExamForwardMostMove = forwardMostMove;\r
2944                     } else if (currentMove < forwardMostMove) {\r
2945                         ForwardInner(forwardMostMove);\r
2946                     }\r
2947                 } else {\r
2948                     /* I don't think this case really can happen */\r
2949                     SendToICS(ics_prefix);\r
2950                     SendToICS("refresh\n");\r
2951                 }\r
2952                 continue;\r
2953             }    \r
2954             \r
2955             /* Error messages */\r
2956             if (ics_user_moved) {\r
2957                 if (looking_at(buf, &i, "Illegal move") ||\r
2958                     looking_at(buf, &i, "Not a legal move") ||\r
2959                     looking_at(buf, &i, "Your king is in check") ||\r
2960                     looking_at(buf, &i, "It isn't your turn") ||\r
2961                     looking_at(buf, &i, "It is not your move")) {\r
2962                     /* Illegal move */\r
2963                     ics_user_moved = 0;\r
2964                     if (forwardMostMove > backwardMostMove) {\r
2965                         currentMove = --forwardMostMove;\r
2966                         DisplayMove(currentMove - 1); /* before DMError */\r
2967                         DisplayMoveError(_("Illegal move (rejected by ICS)"));\r
2968                         DrawPosition(FALSE, boards[currentMove]);\r
2969                         SwitchClocks();\r
2970                         DisplayBothClocks();\r
2971                     }\r
2972                     continue;\r
2973                 }\r
2974             }\r
2975 \r
2976             if (looking_at(buf, &i, "still have time") ||\r
2977                 looking_at(buf, &i, "not out of time") ||\r
2978                 looking_at(buf, &i, "either player is out of time") ||\r
2979                 looking_at(buf, &i, "has timeseal; checking")) {\r
2980                 /* We must have called his flag a little too soon */\r
2981                 whiteFlag = blackFlag = FALSE;\r
2982                 continue;\r
2983             }\r
2984 \r
2985             if (looking_at(buf, &i, "added * seconds to") ||\r
2986                 looking_at(buf, &i, "seconds were added to")) {\r
2987                 /* Update the clocks */\r
2988                 SendToICS(ics_prefix);\r
2989                 SendToICS("refresh\n");\r
2990                 continue;\r
2991             }\r
2992 \r
2993             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {\r
2994                 ics_clock_paused = TRUE;\r
2995                 StopClocks();\r
2996                 continue;\r
2997             }\r
2998 \r
2999             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {\r
3000                 ics_clock_paused = FALSE;\r
3001                 StartClocks();\r
3002                 continue;\r
3003             }\r
3004 \r
3005             /* Grab player ratings from the Creating: message.\r
3006                Note we have to check for the special case when\r
3007                the ICS inserts things like [white] or [black]. */\r
3008             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||\r
3009                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {\r
3010                 /* star_matches:\r
3011                    0    player 1 name (not necessarily white)\r
3012                    1    player 1 rating\r
3013                    2    empty, white, or black (IGNORED)\r
3014                    3    player 2 name (not necessarily black)\r
3015                    4    player 2 rating\r
3016                    \r
3017                    The names/ratings are sorted out when the game\r
3018                    actually starts (below).\r
3019                 */\r
3020                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));\r
3021                 player1Rating = string_to_rating(star_match[1]);\r
3022                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));\r
3023                 player2Rating = string_to_rating(star_match[4]);\r
3024 \r
3025                 if (appData.debugMode)\r
3026                   fprintf(debugFP, \r
3027                           "Ratings from 'Creating:' %s %d, %s %d\n",\r
3028                           player1Name, player1Rating,\r
3029                           player2Name, player2Rating);\r
3030 \r
3031                 continue;\r
3032             }\r
3033             \r
3034             /* Improved generic start/end-of-game messages */\r
3035             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||\r
3036                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){\r
3037                 /* If tkind == 0: */\r
3038                 /* star_match[0] is the game number */\r
3039                 /*           [1] is the white player's name */\r
3040                 /*           [2] is the black player's name */\r
3041                 /* For end-of-game: */\r
3042                 /*           [3] is the reason for the game end */\r
3043                 /*           [4] is a PGN end game-token, preceded by " " */\r
3044                 /* For start-of-game: */\r
3045                 /*           [3] begins with "Creating" or "Continuing" */\r
3046                 /*           [4] is " *" or empty (don't care). */\r
3047                 int gamenum = atoi(star_match[0]);\r
3048                 char *whitename, *blackname, *why, *endtoken;\r
3049                 ChessMove endtype = (ChessMove) 0;\r
3050 \r
3051                 if (tkind == 0) {\r
3052                   whitename = star_match[1];\r
3053                   blackname = star_match[2];\r
3054                   why = star_match[3];\r
3055                   endtoken = star_match[4];\r
3056                 } else {\r
3057                   whitename = star_match[1];\r
3058                   blackname = star_match[3];\r
3059                   why = star_match[5];\r
3060                   endtoken = star_match[6];\r
3061                 }\r
3062 \r
3063                 /* Game start messages */\r
3064                 if (strncmp(why, "Creating ", 9) == 0 ||\r
3065                     strncmp(why, "Continuing ", 11) == 0) {\r
3066                     gs_gamenum = gamenum;\r
3067                     strcpy(gs_kind, strchr(why, ' ') + 1);\r
3068 #if ZIPPY\r
3069                     if (appData.zippyPlay) {\r
3070                         ZippyGameStart(whitename, blackname);\r
3071                     }\r
3072 #endif /*ZIPPY*/\r
3073                     continue;\r
3074                 }\r
3075 \r
3076                 /* Game end messages */\r
3077                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||\r
3078                     ics_gamenum != gamenum) {\r
3079                     continue;\r
3080                 }\r
3081                 while (endtoken[0] == ' ') endtoken++;\r
3082                 switch (endtoken[0]) {\r
3083                   case '*':\r
3084                   default:\r
3085                     endtype = GameUnfinished;\r
3086                     break;\r
3087                   case '0':\r
3088                     endtype = BlackWins;\r
3089                     break;\r
3090                   case '1':\r
3091                     if (endtoken[1] == '/')\r
3092                       endtype = GameIsDrawn;\r
3093                     else\r
3094                       endtype = WhiteWins;\r
3095                     break;\r
3096                 }\r
3097                 GameEnds(endtype, why, GE_ICS);\r
3098 #if ZIPPY\r
3099                 if (appData.zippyPlay && first.initDone) {\r
3100                     ZippyGameEnd(endtype, why);\r
3101                     if (first.pr == NULL) {\r
3102                       /* Start the next process early so that we'll\r
3103                          be ready for the next challenge */\r
3104                       StartChessProgram(&first);\r
3105                     }\r
3106                     /* Send "new" early, in case this command takes\r
3107                        a long time to finish, so that we'll be ready\r
3108                        for the next challenge. */\r
3109                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'\r
3110                     Reset(TRUE, TRUE);\r
3111                 }\r
3112 #endif /*ZIPPY*/\r
3113                 continue;\r
3114             }\r
3115 \r
3116             if (looking_at(buf, &i, "Removing game * from observation") ||\r
3117                 looking_at(buf, &i, "no longer observing game *") ||\r
3118                 looking_at(buf, &i, "Game * (*) has no examiners")) {\r
3119                 if (gameMode == IcsObserving &&\r
3120                     atoi(star_match[0]) == ics_gamenum)\r
3121                   {\r
3122                       /* icsEngineAnalyze */\r
3123                       if (appData.icsEngineAnalyze) {\r
3124                             ExitAnalyzeMode();\r
3125                             ModeHighlight();\r
3126                       }\r
3127                       StopClocks();\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             if (looking_at(buf, &i, "no longer examining game *")) {\r
3136                 if (gameMode == IcsExamining &&\r
3137                     atoi(star_match[0]) == ics_gamenum)\r
3138                   {\r
3139                       gameMode = IcsIdle;\r
3140                       ics_gamenum = -1;\r
3141                       ics_user_moved = FALSE;\r
3142                   }\r
3143                 continue;\r
3144             }\r
3145 \r
3146             /* Advance leftover_start past any newlines we find,\r
3147                so only partial lines can get reparsed */\r
3148             if (looking_at(buf, &i, "\n")) {\r
3149                 prevColor = curColor;\r
3150                 if (curColor != ColorNormal) {\r
3151                     if (oldi > next_out) {\r
3152                         SendToPlayer(&buf[next_out], oldi - next_out);\r
3153                         next_out = oldi;\r
3154                     }\r
3155                     Colorize(ColorNormal, FALSE);\r
3156                     curColor = ColorNormal;\r
3157                 }\r
3158                 if (started == STARTED_BOARD) {\r
3159                     started = STARTED_NONE;\r
3160                     parse[parse_pos] = NULLCHAR;\r
3161                     ParseBoard12(parse);\r
3162                     ics_user_moved = 0;\r
3163 \r
3164                     /* Send premove here */\r
3165                     if (appData.premove) {\r
3166                       char str[MSG_SIZ];\r
3167                       if (currentMove == 0 &&\r
3168                           gameMode == IcsPlayingWhite &&\r
3169                           appData.premoveWhite) {\r
3170                         sprintf(str, "%s%s\n", ics_prefix,\r
3171                                 appData.premoveWhiteText);\r
3172                         if (appData.debugMode)\r
3173                           fprintf(debugFP, "Sending premove:\n");\r
3174                         SendToICS(str);\r
3175                       } else if (currentMove == 1 &&\r
3176                                  gameMode == IcsPlayingBlack &&\r
3177                                  appData.premoveBlack) {\r
3178                         sprintf(str, "%s%s\n", ics_prefix,\r
3179                                 appData.premoveBlackText);\r
3180                         if (appData.debugMode)\r
3181                           fprintf(debugFP, "Sending premove:\n");\r
3182                         SendToICS(str);\r
3183                       } else if (gotPremove) {\r
3184                         gotPremove = 0;\r
3185                         ClearPremoveHighlights();\r
3186                         if (appData.debugMode)\r
3187                           fprintf(debugFP, "Sending premove:\n");\r
3188                           UserMoveEvent(premoveFromX, premoveFromY, \r
3189                                         premoveToX, premoveToY, \r
3190                                         premovePromoChar);\r
3191                       }\r
3192                     }\r
3193 \r
3194                     /* Usually suppress following prompt */\r
3195                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {\r
3196                         if (looking_at(buf, &i, "*% ")) {\r
3197                             savingComment = FALSE;\r
3198                         }\r
3199                     }\r
3200                     next_out = i;\r
3201                 } else if (started == STARTED_HOLDINGS) {\r
3202                     int gamenum;\r
3203                     char new_piece[MSG_SIZ];\r
3204                     started = STARTED_NONE;\r
3205                     parse[parse_pos] = NULLCHAR;\r
3206                     if (appData.debugMode)\r
3207                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",\r
3208                                                         parse, currentMove);\r
3209                     if (sscanf(parse, " game %d", &gamenum) == 1 &&\r
3210                         gamenum == ics_gamenum) {\r
3211                         if (gameInfo.variant == VariantNormal) {\r
3212                           /* [HGM] We seem to switch variant during a game!\r
3213                            * Presumably no holdings were displayed, so we have\r
3214                            * to move the position two files to the right to\r
3215                            * create room for them!\r
3216                            */\r
3217                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */\r
3218                           /* Get a move list just to see the header, which\r
3219                              will tell us whether this is really bug or zh */\r
3220                           if (ics_getting_history == H_FALSE) {\r
3221                             ics_getting_history = H_REQUESTED;\r
3222                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3223                             SendToICS(str);\r
3224                           }\r
3225                         }\r
3226                         new_piece[0] = NULLCHAR;\r
3227                         sscanf(parse, "game %d white [%s black [%s <- %s",\r
3228                                &gamenum, white_holding, black_holding,\r
3229                                new_piece);\r
3230                         white_holding[strlen(white_holding)-1] = NULLCHAR;\r
3231                         black_holding[strlen(black_holding)-1] = NULLCHAR;\r
3232                         /* [HGM] copy holdings to board holdings area */\r
3233                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);\r
3234                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);\r
3235 #if ZIPPY\r
3236                         if (appData.zippyPlay && first.initDone) {\r
3237                             ZippyHoldings(white_holding, black_holding,\r
3238                                           new_piece);\r
3239                         }\r
3240 #endif /*ZIPPY*/\r
3241                         if (tinyLayout || smallLayout) {\r
3242                             char wh[16], bh[16];\r
3243                             PackHolding(wh, white_holding);\r
3244                             PackHolding(bh, black_holding);\r
3245                             sprintf(str, "[%s-%s] %s-%s", wh, bh,\r
3246                                     gameInfo.white, gameInfo.black);\r
3247                         } else {\r
3248                             sprintf(str, "%s [%s] vs. %s [%s]",\r
3249                                     gameInfo.white, white_holding,\r
3250                                     gameInfo.black, black_holding);\r
3251                         }\r
3252 \r
3253                         DrawPosition(FALSE, boards[currentMove]);\r
3254                         DisplayTitle(str);\r
3255                     }\r
3256                     /* Suppress following prompt */\r
3257                     if (looking_at(buf, &i, "*% ")) {\r
3258                         savingComment = FALSE;\r
3259                     }\r
3260                     next_out = i;\r
3261                 }\r
3262                 continue;\r
3263             }\r
3264 \r
3265             i++;                /* skip unparsed character and loop back */\r
3266         }\r
3267         \r
3268         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window\r
3269             started != STARTED_HOLDINGS && i > next_out) {\r
3270             SendToPlayer(&buf[next_out], i - next_out);\r
3271             next_out = i;\r
3272         }\r
3273         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above\r
3274         \r
3275         leftover_len = buf_len - leftover_start;\r
3276         /* if buffer ends with something we couldn't parse,\r
3277            reparse it after appending the next read */\r
3278         \r
3279     } else if (count == 0) {\r
3280         RemoveInputSource(isr);\r
3281         DisplayFatalError(_("Connection closed by ICS"), 0, 0);\r
3282     } else {\r
3283         DisplayFatalError(_("Error reading from ICS"), error, 1);\r
3284     }\r
3285 }\r
3286 \r
3287 \r
3288 /* Board style 12 looks like this:\r
3289    \r
3290    <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
3291    \r
3292  * The "<12> " is stripped before it gets to this routine.  The two\r
3293  * trailing 0's (flip state and clock ticking) are later addition, and\r
3294  * some chess servers may not have them, or may have only the first.\r
3295  * Additional trailing fields may be added in the future.  \r
3296  */\r
3297 \r
3298 #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
3299 \r
3300 #define RELATION_OBSERVING_PLAYED    0\r
3301 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */\r
3302 #define RELATION_PLAYING_MYMOVE      1\r
3303 #define RELATION_PLAYING_NOTMYMOVE  -1\r
3304 #define RELATION_EXAMINING           2\r
3305 #define RELATION_ISOLATED_BOARD     -3\r
3306 #define RELATION_STARTING_POSITION  -4   /* FICS only */\r
3307 \r
3308 void\r
3309 ParseBoard12(string)\r
3310      char *string;\r
3311\r
3312     GameMode newGameMode;\r
3313     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;\r
3314     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;\r
3315     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;\r
3316     char to_play, board_chars[200];\r
3317     char move_str[500], str[500], elapsed_time[500];\r
3318     char black[32], white[32];\r
3319     Board board;\r
3320     int prevMove = currentMove;\r
3321     int ticking = 2;\r
3322     ChessMove moveType;\r
3323     int fromX, fromY, toX, toY;\r
3324     char promoChar;\r
3325     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */\r
3326     char *bookHit = NULL; // [HGM] book\r
3327 \r
3328     fromX = fromY = toX = toY = -1;\r
3329     \r
3330     newGame = FALSE;\r
3331 \r
3332     if (appData.debugMode)\r
3333       fprintf(debugFP, _("Parsing board: %s\n"), string);\r
3334 \r
3335     move_str[0] = NULLCHAR;\r
3336     elapsed_time[0] = NULLCHAR;\r
3337     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */\r
3338         int  i = 0, j;\r
3339         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {\r
3340             if(string[i] == ' ') { ranks++; files = 0; }\r
3341             else files++;\r
3342             i++;\r
3343         }\r
3344         for(j = 0; j <i; j++) board_chars[j] = string[j];\r
3345         board_chars[i] = '\0';\r
3346         string += i + 1;\r
3347     }\r
3348     n = sscanf(string, PATTERN, &to_play, &double_push,\r
3349                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,\r
3350                &gamenum, white, black, &relation, &basetime, &increment,\r
3351                &white_stren, &black_stren, &white_time, &black_time,\r
3352                &moveNum, str, elapsed_time, move_str, &ics_flip,\r
3353                &ticking);\r
3354 \r
3355     if (n < 21) {\r
3356         sprintf(str, _("Failed to parse board string:\n\"%s\""), string);\r
3357         DisplayError(str, 0);\r
3358         return;\r
3359     }\r
3360 \r
3361     /* Convert the move number to internal form */\r
3362     moveNum = (moveNum - 1) * 2;\r
3363     if (to_play == 'B') moveNum++;\r
3364     if (moveNum >= MAX_MOVES) {\r
3365       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),\r
3366                         0, 1);\r
3367       return;\r
3368     }\r
3369     \r
3370     switch (relation) {\r
3371       case RELATION_OBSERVING_PLAYED:\r
3372       case RELATION_OBSERVING_STATIC:\r
3373         if (gamenum == -1) {\r
3374             /* Old ICC buglet */\r
3375             relation = RELATION_OBSERVING_STATIC;\r
3376         }\r
3377         newGameMode = IcsObserving;\r
3378         break;\r
3379       case RELATION_PLAYING_MYMOVE:\r
3380       case RELATION_PLAYING_NOTMYMOVE:\r
3381         newGameMode =\r
3382           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?\r
3383             IcsPlayingWhite : IcsPlayingBlack;\r
3384         break;\r
3385       case RELATION_EXAMINING:\r
3386         newGameMode = IcsExamining;\r
3387         break;\r
3388       case RELATION_ISOLATED_BOARD:\r
3389       default:\r
3390         /* Just display this board.  If user was doing something else,\r
3391            we will forget about it until the next board comes. */ \r
3392         newGameMode = IcsIdle;\r
3393         break;\r
3394       case RELATION_STARTING_POSITION:\r
3395         newGameMode = gameMode;\r
3396         break;\r
3397     }\r
3398     \r
3399     /* Modify behavior for initial board display on move listing\r
3400        of wild games.\r
3401        */\r
3402     switch (ics_getting_history) {\r
3403       case H_FALSE:\r
3404       case H_REQUESTED:\r
3405         break;\r
3406       case H_GOT_REQ_HEADER:\r
3407       case H_GOT_UNREQ_HEADER:\r
3408         /* This is the initial position of the current game */\r
3409         gamenum = ics_gamenum;\r
3410         moveNum = 0;            /* old ICS bug workaround */\r
3411         if (to_play == 'B') {\r
3412           startedFromSetupPosition = TRUE;\r
3413           blackPlaysFirst = TRUE;\r
3414           moveNum = 1;\r
3415           if (forwardMostMove == 0) forwardMostMove = 1;\r
3416           if (backwardMostMove == 0) backwardMostMove = 1;\r
3417           if (currentMove == 0) currentMove = 1;\r
3418         }\r
3419         newGameMode = gameMode;\r
3420         relation = RELATION_STARTING_POSITION; /* ICC needs this */\r
3421         break;\r
3422       case H_GOT_UNWANTED_HEADER:\r
3423         /* This is an initial board that we don't want */\r
3424         return;\r
3425       case H_GETTING_MOVES:\r
3426         /* Should not happen */\r
3427         DisplayError(_("Error gathering move list: extra board"), 0);\r
3428         ics_getting_history = H_FALSE;\r
3429         return;\r
3430     }\r
3431     \r
3432     /* Take action if this is the first board of a new game, or of a\r
3433        different game than is currently being displayed.  */\r
3434     if (gamenum != ics_gamenum || newGameMode != gameMode ||\r
3435         relation == RELATION_ISOLATED_BOARD) {\r
3436         \r
3437         /* Forget the old game and get the history (if any) of the new one */\r
3438         if (gameMode != BeginningOfGame) {\r
3439           Reset(FALSE, TRUE);\r
3440         }\r
3441         newGame = TRUE;\r
3442         if (appData.autoRaiseBoard) BoardToTop();\r
3443         prevMove = -3;\r
3444         if (gamenum == -1) {\r
3445             newGameMode = IcsIdle;\r
3446         } else if (moveNum > 0 && newGameMode != IcsIdle &&\r
3447                    appData.getMoveList) {\r
3448             /* Need to get game history */\r
3449             ics_getting_history = H_REQUESTED;\r
3450             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3451             SendToICS(str);\r
3452         }\r
3453         \r
3454         /* Initially flip the board to have black on the bottom if playing\r
3455            black or if the ICS flip flag is set, but let the user change\r
3456            it with the Flip View button. */\r
3457         flipView = appData.autoFlipView ? \r
3458           (newGameMode == IcsPlayingBlack) || ics_flip :\r
3459           appData.flipView;\r
3460         \r
3461         /* Done with values from previous mode; copy in new ones */\r
3462         gameMode = newGameMode;\r
3463         ModeHighlight();\r
3464         ics_gamenum = gamenum;\r
3465         if (gamenum == gs_gamenum) {\r
3466             int klen = strlen(gs_kind);\r
3467             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;\r
3468             sprintf(str, "ICS %s", gs_kind);\r
3469             gameInfo.event = StrSave(str);\r
3470         } else {\r
3471             gameInfo.event = StrSave("ICS game");\r
3472         }\r
3473         gameInfo.site = StrSave(appData.icsHost);\r
3474         gameInfo.date = PGNDate();\r
3475         gameInfo.round = StrSave("-");\r
3476         gameInfo.white = StrSave(white);\r
3477         gameInfo.black = StrSave(black);\r
3478         timeControl = basetime * 60 * 1000;\r
3479         timeControl_2 = 0;\r
3480         timeIncrement = increment * 1000;\r
3481         movesPerSession = 0;\r
3482         gameInfo.timeControl = TimeControlTagValue();\r
3483         VariantSwitch(board, StringToVariant(gameInfo.event) );\r
3484   if (appData.debugMode) {\r
3485     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);\r
3486     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));\r
3487     setbuf(debugFP, NULL);\r
3488   }\r
3489 \r
3490         gameInfo.outOfBook = NULL;\r
3491         \r
3492         /* Do we have the ratings? */\r
3493         if (strcmp(player1Name, white) == 0 &&\r
3494             strcmp(player2Name, black) == 0) {\r
3495             if (appData.debugMode)\r
3496               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3497                       player1Rating, player2Rating);\r
3498             gameInfo.whiteRating = player1Rating;\r
3499             gameInfo.blackRating = player2Rating;\r
3500         } else if (strcmp(player2Name, white) == 0 &&\r
3501                    strcmp(player1Name, black) == 0) {\r
3502             if (appData.debugMode)\r
3503               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3504                       player2Rating, player1Rating);\r
3505             gameInfo.whiteRating = player2Rating;\r
3506             gameInfo.blackRating = player1Rating;\r
3507         }\r
3508         player1Name[0] = player2Name[0] = NULLCHAR;\r
3509 \r
3510         /* Silence shouts if requested */\r
3511         if (appData.quietPlay &&\r
3512             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {\r
3513             SendToICS(ics_prefix);\r
3514             SendToICS("set shout 0\n");\r
3515         }\r
3516     }\r
3517     \r
3518     /* Deal with midgame name changes */\r
3519     if (!newGame) {\r
3520         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {\r
3521             if (gameInfo.white) free(gameInfo.white);\r
3522             gameInfo.white = StrSave(white);\r
3523         }\r
3524         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {\r
3525             if (gameInfo.black) free(gameInfo.black);\r
3526             gameInfo.black = StrSave(black);\r
3527         }\r
3528     }\r
3529     \r
3530     /* Throw away game result if anything actually changes in examine mode */\r
3531     if (gameMode == IcsExamining && !newGame) {\r
3532         gameInfo.result = GameUnfinished;\r
3533         if (gameInfo.resultDetails != NULL) {\r
3534             free(gameInfo.resultDetails);\r
3535             gameInfo.resultDetails = NULL;\r
3536         }\r
3537     }\r
3538     \r
3539     /* In pausing && IcsExamining mode, we ignore boards coming\r
3540        in if they are in a different variation than we are. */\r
3541     if (pauseExamInvalid) return;\r
3542     if (pausing && gameMode == IcsExamining) {\r
3543         if (moveNum <= pauseExamForwardMostMove) {\r
3544             pauseExamInvalid = TRUE;\r
3545             forwardMostMove = pauseExamForwardMostMove;\r
3546             return;\r
3547         }\r
3548     }\r
3549     \r
3550   if (appData.debugMode) {\r
3551     fprintf(debugFP, "load %dx%d board\n", files, ranks);\r
3552   }\r
3553     /* Parse the board */\r
3554     for (k = 0; k < ranks; k++) {\r
3555       for (j = 0; j < files; j++)\r
3556         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);\r
3557       if(gameInfo.holdingsWidth > 1) {\r
3558            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;\r
3559            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;\r
3560       }\r
3561     }\r
3562     CopyBoard(boards[moveNum], board);\r
3563     if (moveNum == 0) {\r
3564         startedFromSetupPosition =\r
3565           !CompareBoards(board, initialPosition);\r
3566         if(startedFromSetupPosition)\r
3567             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */\r
3568     }\r
3569 \r
3570     /* [HGM] Set castling rights. Take the outermost Rooks,\r
3571        to make it also work for FRC opening positions. Note that board12\r
3572        is really defective for later FRC positions, as it has no way to\r
3573        indicate which Rook can castle if they are on the same side of King.\r
3574        For the initial position we grant rights to the outermost Rooks,\r
3575        and remember thos rights, and we then copy them on positions\r
3576        later in an FRC game. This means WB might not recognize castlings with\r
3577        Rooks that have moved back to their original position as illegal,\r
3578        but in ICS mode that is not its job anyway.\r
3579     */\r
3580     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)\r
3581     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;\r
3582 \r
3583         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)\r
3584             if(board[0][i] == WhiteRook) j = i;\r
3585         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3586         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)\r
3587             if(board[0][i] == WhiteRook) j = i;\r
3588         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3589         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)\r
3590             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;\r
3591         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3592         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)\r
3593             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;\r
3594         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3595 \r
3596         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }\r
3597         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)\r
3598             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;\r
3599         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)\r
3600             if(board[BOARD_HEIGHT-1][k] == bKing)\r
3601                 initialRights[5] = castlingRights[moveNum][5] = k;\r
3602     } else { int r;\r
3603         r = castlingRights[moveNum][0] = initialRights[0];\r
3604         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;\r
3605         r = castlingRights[moveNum][1] = initialRights[1];\r
3606         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;\r
3607         r = castlingRights[moveNum][3] = initialRights[3];\r
3608         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;\r
3609         r = castlingRights[moveNum][4] = initialRights[4];\r
3610         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;\r
3611         /* wildcastle kludge: always assume King has rights */\r
3612         r = castlingRights[moveNum][2] = initialRights[2];\r
3613         r = castlingRights[moveNum][5] = initialRights[5];\r
3614     }\r
3615     /* [HGM] e.p. rights. Assume that ICS sends file number here? */\r
3616     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;\r
3617 \r
3618     \r
3619     if (ics_getting_history == H_GOT_REQ_HEADER ||\r
3620         ics_getting_history == H_GOT_UNREQ_HEADER) {\r
3621         /* This was an initial position from a move list, not\r
3622            the current position */\r
3623         return;\r
3624     }\r
3625     \r
3626     /* Update currentMove and known move number limits */\r
3627     newMove = newGame || moveNum > forwardMostMove;\r
3628 \r
3629     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */\r
3630     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {\r
3631         takeback = forwardMostMove - moveNum;\r
3632         for (i = 0; i < takeback; i++) {\r
3633              if (appData.debugMode) fprintf(debugFP, "take back move\n");\r
3634              SendToProgram("undo\n", &first);\r
3635         }\r
3636     }\r
3637 \r
3638     if (newGame) {\r
3639         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3640         if (gameMode == IcsExamining && moveNum == 0) {\r
3641           /* Workaround for ICS limitation: we are not told the wild\r
3642              type when starting to examine a game.  But if we ask for\r
3643              the move list, the move list header will tell us */\r
3644             ics_getting_history = H_REQUESTED;\r
3645             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3646             SendToICS(str);\r
3647         }\r
3648     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove\r
3649                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {\r
3650         forwardMostMove = moveNum;\r
3651         if (!pausing || currentMove > forwardMostMove)\r
3652           currentMove = forwardMostMove;\r
3653     } else {\r
3654         /* New part of history that is not contiguous with old part */ \r
3655         if (pausing && gameMode == IcsExamining) {\r
3656             pauseExamInvalid = TRUE;\r
3657             forwardMostMove = pauseExamForwardMostMove;\r
3658             return;\r
3659         }\r
3660         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3661         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {\r
3662             ics_getting_history = H_REQUESTED;\r
3663             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3664             SendToICS(str);\r
3665         }\r
3666     }\r
3667     \r
3668     /* Update the clocks */\r
3669     if (strchr(elapsed_time, '.')) {\r
3670       /* Time is in ms */\r
3671       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;\r
3672       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;\r
3673     } else {\r
3674       /* Time is in seconds */\r
3675       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;\r
3676       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;\r
3677     }\r
3678       \r
3679 \r
3680 #if ZIPPY\r
3681     if (appData.zippyPlay && newGame &&\r
3682         gameMode != IcsObserving && gameMode != IcsIdle &&\r
3683         gameMode != IcsExamining)\r
3684       ZippyFirstBoard(moveNum, basetime, increment);\r
3685 #endif\r
3686     \r
3687     /* Put the move on the move list, first converting\r
3688        to canonical algebraic form. */\r
3689     if (moveNum > 0) {\r
3690   if (appData.debugMode) {\r
3691     if (appData.debugMode) { int f = forwardMostMove;\r
3692         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,\r
3693                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
3694     }\r
3695     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);\r
3696     fprintf(debugFP, "moveNum = %d\n", moveNum);\r
3697     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);\r
3698     setbuf(debugFP, NULL);\r
3699   }\r
3700         if (moveNum <= backwardMostMove) {\r
3701             /* We don't know what the board looked like before\r
3702                this move.  Punt. */\r
3703             strcpy(parseList[moveNum - 1], move_str);\r
3704             strcat(parseList[moveNum - 1], " ");\r
3705             strcat(parseList[moveNum - 1], elapsed_time);\r
3706             moveList[moveNum - 1][0] = NULLCHAR;\r
3707         } else if (strcmp(move_str, "none") == 0) {\r
3708             // [HGM] long SAN: swapped order; test for 'none' before parsing move\r
3709             /* Again, we don't know what the board looked like;\r
3710                this is really the start of the game. */\r
3711             parseList[moveNum - 1][0] = NULLCHAR;\r
3712             moveList[moveNum - 1][0] = NULLCHAR;\r
3713             backwardMostMove = moveNum;\r
3714             startedFromSetupPosition = TRUE;\r
3715             fromX = fromY = toX = toY = -1;\r
3716         } else {\r
3717           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. \r
3718           //                 So we parse the long-algebraic move string in stead of the SAN move\r
3719           int valid; char buf[MSG_SIZ], *prom;\r
3720 \r
3721           // str looks something like "Q/a1-a2"; kill the slash\r
3722           if(str[1] == '/') \r
3723                 sprintf(buf, "%c%s", str[0], str+2);\r
3724           else  strcpy(buf, str); // might be castling\r
3725           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) \r
3726                 strcat(buf, prom); // long move lacks promo specification!\r
3727           if(!appData.testLegality) {\r
3728                 if(appData.debugMode) \r
3729                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);\r
3730                 strcpy(move_str, buf);\r
3731           }\r
3732           valid = ParseOneMove(move_str, moveNum - 1, &moveType,\r
3733                                 &fromX, &fromY, &toX, &toY, &promoChar)\r
3734                || ParseOneMove(buf, moveNum - 1, &moveType,\r
3735                                 &fromX, &fromY, &toX, &toY, &promoChar);\r
3736           // end of long SAN patch\r
3737           if (valid) {\r
3738             (void) CoordsToAlgebraic(boards[moveNum - 1],\r
3739                                      PosFlags(moveNum - 1), EP_UNKNOWN,\r
3740                                      fromY, fromX, toY, toX, promoChar,\r
3741                                      parseList[moveNum-1]);\r
3742             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,\r
3743                              castlingRights[moveNum]) ) {\r
3744               case MT_NONE:\r
3745               case MT_STALEMATE:\r
3746               default:\r
3747                 break;\r
3748               case MT_CHECK:\r
3749                 if(gameInfo.variant != VariantShogi)\r
3750                     strcat(parseList[moveNum - 1], "+");\r
3751                 break;\r
3752               case MT_CHECKMATE:\r
3753                 strcat(parseList[moveNum - 1], "#");\r
3754                 break;\r
3755             }\r
3756             strcat(parseList[moveNum - 1], " ");\r
3757             strcat(parseList[moveNum - 1], elapsed_time);\r
3758             /* currentMoveString is set as a side-effect of ParseOneMove */\r
3759             strcpy(moveList[moveNum - 1], currentMoveString);\r
3760             strcat(moveList[moveNum - 1], "\n");\r
3761           } else {\r
3762             /* Move from ICS was illegal!?  Punt. */\r
3763   if (appData.debugMode) {\r
3764     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);\r
3765     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
3766   }\r
3767 #if 0\r
3768             if (appData.testLegality && appData.debugMode) {\r
3769                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);\r
3770                 DisplayError(str, 0);\r
3771             }\r
3772 #endif\r
3773             strcpy(parseList[moveNum - 1], move_str);\r
3774             strcat(parseList[moveNum - 1], " ");\r
3775             strcat(parseList[moveNum - 1], elapsed_time);\r
3776             moveList[moveNum - 1][0] = NULLCHAR;\r
3777             fromX = fromY = toX = toY = -1;\r
3778           }\r
3779         }\r
3780   if (appData.debugMode) {\r
3781     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);\r
3782     setbuf(debugFP, NULL);\r
3783   }\r
3784 \r
3785 #if ZIPPY\r
3786         /* Send move to chess program (BEFORE animating it). */\r
3787         if (appData.zippyPlay && !newGame && newMove && \r
3788            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {\r
3789 \r
3790             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||\r
3791                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {\r
3792                 if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3793                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),\r
3794                             move_str);\r
3795                     DisplayError(str, 0);\r
3796                 } else {\r
3797                     if (first.sendTime) {\r
3798                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);\r
3799                     }\r
3800                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book\r
3801                     if (firstMove && !bookHit) {\r
3802                         firstMove = FALSE;\r
3803                         if (first.useColors) {\r
3804                           SendToProgram(gameMode == IcsPlayingWhite ?\r
3805                                         "white\ngo\n" :\r
3806                                         "black\ngo\n", &first);\r
3807                         } else {\r
3808                           SendToProgram("go\n", &first);\r
3809                         }\r
3810                         first.maybeThinking = TRUE;\r
3811                     }\r
3812                 }\r
3813             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {\r
3814               if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3815                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);\r
3816                 DisplayError(str, 0);\r
3817               } else {\r
3818                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!\r
3819                 SendMoveToProgram(moveNum - 1, &first);\r
3820               }\r
3821             }\r
3822         }\r
3823 #endif\r
3824     }\r
3825 \r
3826     if (moveNum > 0 && !gotPremove) {\r
3827         /* If move comes from a remote source, animate it.  If it\r
3828            isn't remote, it will have already been animated. */\r
3829         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {\r
3830             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);\r
3831         }\r
3832         if (!pausing && appData.highlightLastMove) {\r
3833             SetHighlights(fromX, fromY, toX, toY);\r
3834         }\r
3835     }\r
3836     \r
3837     /* Start the clocks */\r
3838     whiteFlag = blackFlag = FALSE;\r
3839     appData.clockMode = !(basetime == 0 && increment == 0);\r
3840     if (ticking == 0) {\r
3841       ics_clock_paused = TRUE;\r
3842       StopClocks();\r
3843     } else if (ticking == 1) {\r
3844       ics_clock_paused = FALSE;\r
3845     }\r
3846     if (gameMode == IcsIdle ||\r
3847         relation == RELATION_OBSERVING_STATIC ||\r
3848         relation == RELATION_EXAMINING ||\r
3849         ics_clock_paused)\r
3850       DisplayBothClocks();\r
3851     else\r
3852       StartClocks();\r
3853     \r
3854     /* Display opponents and material strengths */\r
3855     if (gameInfo.variant != VariantBughouse &&\r
3856         gameInfo.variant != VariantCrazyhouse) {\r
3857         if (tinyLayout || smallLayout) {\r
3858             if(gameInfo.variant == VariantNormal)\r
3859                 sprintf(str, "%s(%d) %s(%d) {%d %d}", \r
3860                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3861                     basetime, increment);\r
3862             else\r
3863                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", \r
3864                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3865                     basetime, increment, (int) gameInfo.variant);\r
3866         } else {\r
3867             if(gameInfo.variant == VariantNormal)\r
3868                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", \r
3869                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3870                     basetime, increment);\r
3871             else\r
3872                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", \r
3873                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3874                     basetime, increment, VariantName(gameInfo.variant));\r
3875         }\r
3876         DisplayTitle(str);\r
3877   if (appData.debugMode) {\r
3878     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);\r
3879   }\r
3880     }\r
3881 \r
3882    \r
3883     /* Display the board */\r
3884     if (!pausing) {\r
3885       \r
3886       if (appData.premove)\r
3887           if (!gotPremove || \r
3888              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||\r
3889              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))\r
3890               ClearPremoveHighlights();\r
3891 \r
3892       DrawPosition(FALSE, boards[currentMove]);\r
3893       DisplayMove(moveNum - 1);\r
3894       if (appData.ringBellAfterMoves && !ics_user_moved)\r
3895         RingBell();\r
3896     }\r
3897 \r
3898     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
3899 #if ZIPPY\r
3900     if(bookHit) { // [HGM] book: simulate book reply\r
3901         static char bookMove[MSG_SIZ]; // a bit generous?\r
3902 \r
3903         programStats.nodes = programStats.depth = programStats.time = \r
3904         programStats.score = programStats.got_only_move = 0;\r
3905         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
3906 \r
3907         strcpy(bookMove, "move ");\r
3908         strcat(bookMove, bookHit);\r
3909         HandleMachineMove(bookMove, &first);\r
3910     }\r
3911 #endif\r
3912 }\r
3913 \r
3914 void\r
3915 GetMoveListEvent()\r
3916 {\r
3917     char buf[MSG_SIZ];\r
3918     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {\r
3919         ics_getting_history = H_REQUESTED;\r
3920         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);\r
3921         SendToICS(buf);\r
3922     }\r
3923 }\r
3924 \r
3925 void\r
3926 AnalysisPeriodicEvent(force)\r
3927      int force;\r
3928 {\r
3929     if (((programStats.ok_to_send == 0 || programStats.line_is_book)\r
3930          && !force) || !appData.periodicUpdates)\r
3931       return;\r
3932 \r
3933     /* Send . command to Crafty to collect stats */\r
3934     SendToProgram(".\n", &first);\r
3935 \r
3936     /* Don't send another until we get a response (this makes\r
3937        us stop sending to old Crafty's which don't understand\r
3938        the "." command (sending illegal cmds resets node count & time,\r
3939        which looks bad)) */\r
3940     programStats.ok_to_send = 0;\r
3941 }\r
3942 \r
3943 void\r
3944 SendMoveToProgram(moveNum, cps)\r
3945      int moveNum;\r
3946      ChessProgramState *cps;\r
3947 {\r
3948     char buf[MSG_SIZ];\r
3949 \r
3950     if (cps->useUsermove) {\r
3951       SendToProgram("usermove ", cps);\r
3952     }\r
3953     if (cps->useSAN) {\r
3954       char *space;\r
3955       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {\r
3956         int len = space - parseList[moveNum];\r
3957         memcpy(buf, parseList[moveNum], len);\r
3958         buf[len++] = '\n';\r
3959         buf[len] = NULLCHAR;\r
3960       } else {\r
3961         sprintf(buf, "%s\n", parseList[moveNum]);\r
3962       }\r
3963       SendToProgram(buf, cps);\r
3964     } else {\r
3965       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */\r
3966         AlphaRank(moveList[moveNum], 4);\r
3967         SendToProgram(moveList[moveNum], cps);\r
3968         AlphaRank(moveList[moveNum], 4); // and back\r
3969       } else\r
3970       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by\r
3971        * the engine. It would be nice to have a better way to identify castle \r
3972        * moves here. */\r
3973       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)\r
3974                                                                          && cps->useOOCastle) {\r
3975         int fromX = moveList[moveNum][0] - AAA; \r
3976         int fromY = moveList[moveNum][1] - ONE;\r
3977         int toX = moveList[moveNum][2] - AAA; \r
3978         int toY = moveList[moveNum][3] - ONE;\r
3979         if((boards[moveNum][fromY][fromX] == WhiteKing \r
3980             && boards[moveNum][toY][toX] == WhiteRook)\r
3981            || (boards[moveNum][fromY][fromX] == BlackKing \r
3982                && boards[moveNum][toY][toX] == BlackRook)) {\r
3983           if(toX > fromX) SendToProgram("O-O\n", cps);\r
3984           else SendToProgram("O-O-O\n", cps);\r
3985         }\r
3986         else SendToProgram(moveList[moveNum], cps);\r
3987       }\r
3988       else SendToProgram(moveList[moveNum], cps);\r
3989       /* End of additions by Tord */\r
3990     }\r
3991 \r
3992     /* [HGM] setting up the opening has brought engine in force mode! */\r
3993     /*       Send 'go' if we are in a mode where machine should play. */\r
3994     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&\r
3995         (gameMode == TwoMachinesPlay   ||\r
3996 #ifdef ZIPPY\r
3997          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||\r
3998 #endif\r
3999          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {\r
4000         SendToProgram("go\n", cps);\r
4001   if (appData.debugMode) {\r
4002     fprintf(debugFP, "(extra)\n");\r
4003   }\r
4004     }\r
4005     setboardSpoiledMachineBlack = 0;\r
4006 }\r
4007 \r
4008 void\r
4009 SendMoveToICS(moveType, fromX, fromY, toX, toY)\r
4010      ChessMove moveType;\r
4011      int fromX, fromY, toX, toY;\r
4012 {\r
4013     char user_move[MSG_SIZ];\r
4014 \r
4015     switch (moveType) {\r
4016       default:\r
4017         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),\r
4018                 (int)moveType, fromX, fromY, toX, toY);\r
4019         DisplayError(user_move + strlen("say "), 0);\r
4020         break;\r
4021       case WhiteKingSideCastle:\r
4022       case BlackKingSideCastle:\r
4023       case WhiteQueenSideCastleWild:\r
4024       case BlackQueenSideCastleWild:\r
4025       /* PUSH Fabien */\r
4026       case WhiteHSideCastleFR:\r
4027       case BlackHSideCastleFR:\r
4028       /* POP Fabien */\r
4029         sprintf(user_move, "o-o\n");\r
4030         break;\r
4031       case WhiteQueenSideCastle:\r
4032       case BlackQueenSideCastle:\r
4033       case WhiteKingSideCastleWild:\r
4034       case BlackKingSideCastleWild:\r
4035       /* PUSH Fabien */\r
4036       case WhiteASideCastleFR:\r
4037       case BlackASideCastleFR:\r
4038       /* POP Fabien */\r
4039         sprintf(user_move, "o-o-o\n");\r
4040         break;\r
4041       case WhitePromotionQueen:\r
4042       case BlackPromotionQueen:\r
4043       case WhitePromotionRook:\r
4044       case BlackPromotionRook:\r
4045       case WhitePromotionBishop:\r
4046       case BlackPromotionBishop:\r
4047       case WhitePromotionKnight:\r
4048       case BlackPromotionKnight:\r
4049       case WhitePromotionKing:\r
4050       case BlackPromotionKing:\r
4051       case WhitePromotionChancellor:\r
4052       case BlackPromotionChancellor:\r
4053       case WhitePromotionArchbishop:\r
4054       case BlackPromotionArchbishop:\r
4055         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)\r
4056             sprintf(user_move, "%c%c%c%c=%c\n",\r
4057                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4058                 PieceToChar(WhiteFerz));\r
4059         else if(gameInfo.variant == VariantGreat)\r
4060             sprintf(user_move, "%c%c%c%c=%c\n",\r
4061                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4062                 PieceToChar(WhiteMan));\r
4063         else\r
4064             sprintf(user_move, "%c%c%c%c=%c\n",\r
4065                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4066                 PieceToChar(PromoPiece(moveType)));\r
4067         break;\r
4068       case WhiteDrop:\r
4069       case BlackDrop:\r
4070         sprintf(user_move, "%c@%c%c\n",\r
4071                 ToUpper(PieceToChar((ChessSquare) fromX)),\r
4072                 AAA + toX, ONE + toY);\r
4073         break;\r
4074       case NormalMove:\r
4075       case WhiteCapturesEnPassant:\r
4076       case BlackCapturesEnPassant:\r
4077       case IllegalMove:  /* could be a variant we don't quite understand */\r
4078         sprintf(user_move, "%c%c%c%c\n",\r
4079                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);\r
4080         break;\r
4081     }\r
4082     SendToICS(user_move);\r
4083 }\r
4084 \r
4085 void\r
4086 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)\r
4087      int rf, ff, rt, ft;\r
4088      char promoChar;\r
4089      char move[7];\r
4090 {\r
4091     if (rf == DROP_RANK) {\r
4092         sprintf(move, "%c@%c%c\n",\r
4093                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);\r
4094     } else {\r
4095         if (promoChar == 'x' || promoChar == NULLCHAR) {\r
4096             sprintf(move, "%c%c%c%c\n",\r
4097                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);\r
4098         } else {\r
4099             sprintf(move, "%c%c%c%c%c\n",\r
4100                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);\r
4101         }\r
4102     }\r
4103 }\r
4104 \r
4105 void\r
4106 ProcessICSInitScript(f)\r
4107      FILE *f;\r
4108 {\r
4109     char buf[MSG_SIZ];\r
4110 \r
4111     while (fgets(buf, MSG_SIZ, f)) {\r
4112         SendToICSDelayed(buf,(long)appData.msLoginDelay);\r
4113     }\r
4114 \r
4115     fclose(f);\r
4116 }\r
4117 \r
4118 \r
4119 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */\r
4120 void\r
4121 AlphaRank(char *move, int n)\r
4122 {\r
4123 //    char *p = move, c; int x, y;\r
4124 \r
4125     if (appData.debugMode) {\r
4126         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);\r
4127     }\r
4128 \r
4129     if(move[1]=='*' && \r
4130        move[2]>='0' && move[2]<='9' &&\r
4131        move[3]>='a' && move[3]<='x'    ) {\r
4132         move[1] = '@';\r
4133         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;\r
4134         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;\r
4135     } else\r
4136     if(move[0]>='0' && move[0]<='9' &&\r
4137        move[1]>='a' && move[1]<='x' &&\r
4138        move[2]>='0' && move[2]<='9' &&\r
4139        move[3]>='a' && move[3]<='x'    ) {\r
4140         /* input move, Shogi -> normal */\r
4141         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;\r
4142         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;\r
4143         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;\r
4144         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;\r
4145     } else\r
4146     if(move[1]=='@' &&\r
4147        move[3]>='0' && move[3]<='9' &&\r
4148        move[2]>='a' && move[2]<='x'    ) {\r
4149         move[1] = '*';\r
4150         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';\r
4151         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';\r
4152     } else\r
4153     if(\r
4154        move[0]>='a' && move[0]<='x' &&\r
4155        move[3]>='0' && move[3]<='9' &&\r
4156        move[2]>='a' && move[2]<='x'    ) {\r
4157          /* output move, normal -> Shogi */\r
4158         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';\r
4159         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';\r
4160         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';\r
4161         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';\r
4162         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';\r
4163     }\r
4164     if (appData.debugMode) {\r
4165         fprintf(debugFP, "   out = '%s'\n", move);\r
4166     }\r
4167 }\r
4168 \r
4169 /* Parser for moves from gnuchess, ICS, or user typein box */\r
4170 Boolean\r
4171 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)\r
4172      char *move;\r
4173      int moveNum;\r
4174      ChessMove *moveType;\r
4175      int *fromX, *fromY, *toX, *toY;\r
4176      char *promoChar;\r
4177 {       \r
4178     if (appData.debugMode) {\r
4179         fprintf(debugFP, "move to parse: %s\n", move);\r
4180     }\r
4181     *moveType = yylexstr(moveNum, move);\r
4182 \r
4183     switch (*moveType) {\r
4184       case WhitePromotionChancellor:\r
4185       case BlackPromotionChancellor:\r
4186       case WhitePromotionArchbishop:\r
4187       case BlackPromotionArchbishop:\r
4188       case WhitePromotionQueen:\r
4189       case BlackPromotionQueen:\r
4190       case WhitePromotionRook:\r
4191       case BlackPromotionRook:\r
4192       case WhitePromotionBishop:\r
4193       case BlackPromotionBishop:\r
4194       case WhitePromotionKnight:\r
4195       case BlackPromotionKnight:\r
4196       case WhitePromotionKing:\r
4197       case BlackPromotionKing:\r
4198       case NormalMove:\r
4199       case WhiteCapturesEnPassant:\r
4200       case BlackCapturesEnPassant:\r
4201       case WhiteKingSideCastle:\r
4202       case WhiteQueenSideCastle:\r
4203       case BlackKingSideCastle:\r
4204       case BlackQueenSideCastle:\r
4205       case WhiteKingSideCastleWild:\r
4206       case WhiteQueenSideCastleWild:\r
4207       case BlackKingSideCastleWild:\r
4208       case BlackQueenSideCastleWild:\r
4209       /* Code added by Tord: */\r
4210       case WhiteHSideCastleFR:\r
4211       case WhiteASideCastleFR:\r
4212       case BlackHSideCastleFR:\r
4213       case BlackASideCastleFR:\r
4214       /* End of code added by Tord */\r
4215       case IllegalMove:         /* bug or odd chess variant */\r
4216         *fromX = currentMoveString[0] - AAA;\r
4217         *fromY = currentMoveString[1] - ONE;\r
4218         *toX = currentMoveString[2] - AAA;\r
4219         *toY = currentMoveString[3] - ONE;\r
4220         *promoChar = currentMoveString[4];\r
4221         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||\r
4222             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {\r
4223     if (appData.debugMode) {\r
4224         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);\r
4225     }\r
4226             *fromX = *fromY = *toX = *toY = 0;\r
4227             return FALSE;\r
4228         }\r
4229         if (appData.testLegality) {\r
4230           return (*moveType != IllegalMove);\r
4231         } else {\r
4232           return !(fromX == fromY && toX == toY);\r
4233         }\r
4234 \r
4235       case WhiteDrop:\r
4236       case BlackDrop:\r
4237         *fromX = *moveType == WhiteDrop ?\r
4238           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
4239           (int) CharToPiece(ToLower(currentMoveString[0]));\r
4240         *fromY = DROP_RANK;\r
4241         *toX = currentMoveString[2] - AAA;\r
4242         *toY = currentMoveString[3] - ONE;\r
4243         *promoChar = NULLCHAR;\r
4244         return TRUE;\r
4245 \r
4246       case AmbiguousMove:\r
4247       case ImpossibleMove:\r
4248       case (ChessMove) 0:       /* end of file */\r
4249       case ElapsedTime:\r
4250       case Comment:\r
4251       case PGNTag:\r
4252       case NAG:\r
4253       case WhiteWins:\r
4254       case BlackWins:\r
4255       case GameIsDrawn:\r
4256       default:\r
4257     if (appData.debugMode) {\r
4258         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);\r
4259     }\r
4260         /* bug? */\r
4261         *fromX = *fromY = *toX = *toY = 0;\r
4262         *promoChar = NULLCHAR;\r
4263         return FALSE;\r
4264     }\r
4265 }\r
4266 \r
4267 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.\r
4268 // All positions will have equal probability, but the current method will not provide a unique\r
4269 // numbering scheme for arrays that contain 3 or more pieces of the same kind.\r
4270 #define DARK 1\r
4271 #define LITE 2\r
4272 #define ANY 3\r
4273 \r
4274 int squaresLeft[4];\r
4275 int piecesLeft[(int)BlackPawn];\r
4276 int seed, nrOfShuffles;\r
4277 \r
4278 void GetPositionNumber()\r
4279 {       // sets global variable seed\r
4280         int i;\r
4281 \r
4282         seed = appData.defaultFrcPosition;\r
4283         if(seed < 0) { // randomize based on time for negative FRC position numbers\r
4284                 for(i=0; i<50; i++) seed += random();\r
4285                 seed = random() ^ random() >> 8 ^ random() << 8;\r
4286                 if(seed<0) seed = -seed;\r
4287         }\r
4288 }\r
4289 \r
4290 int put(Board board, int pieceType, int rank, int n, int shade)\r
4291 // put the piece on the (n-1)-th empty squares of the given shade\r
4292 {\r
4293         int i;\r
4294 \r
4295         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {\r
4296                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {\r
4297                         board[rank][i] = (ChessSquare) pieceType;\r
4298                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;\r
4299                         squaresLeft[ANY]--;\r
4300                         piecesLeft[pieceType]--; \r
4301                         return i;\r
4302                 }\r
4303         }\r
4304         return -1;\r
4305 }\r
4306 \r
4307 \r
4308 void AddOnePiece(Board board, int pieceType, int rank, int shade)\r
4309 // calculate where the next piece goes, (any empty square), and put it there\r
4310 {\r
4311         int i;\r
4312 \r
4313         i = seed % squaresLeft[shade];\r
4314         nrOfShuffles *= squaresLeft[shade];\r
4315         seed /= squaresLeft[shade];\r
4316         put(board, pieceType, rank, i, shade);\r
4317 }\r
4318 \r
4319 void AddTwoPieces(Board board, int pieceType, int rank)\r
4320 // calculate where the next 2 identical pieces go, (any empty square), and put it there\r
4321 {\r
4322         int i, n=squaresLeft[ANY], j=n-1, k;\r
4323 \r
4324         k = n*(n-1)/2; // nr of possibilities, not counting permutations\r
4325         i = seed % k;  // pick one\r
4326         nrOfShuffles *= k;\r
4327         seed /= k;\r
4328         while(i >= j) i -= j--;\r
4329         j = n - 1 - j; i += j;\r
4330         put(board, pieceType, rank, j, ANY);\r
4331         put(board, pieceType, rank, i, ANY);\r
4332 }\r
4333 \r
4334 void SetUpShuffle(Board board, int number)\r
4335 {\r
4336         int i, p, first=1;\r
4337 \r
4338         GetPositionNumber(); nrOfShuffles = 1;\r
4339 \r
4340         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;\r
4341         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;\r
4342         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];\r
4343 \r
4344         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;\r
4345 \r
4346         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board\r
4347             p = (int) board[0][i];\r
4348             if(p < (int) BlackPawn) piecesLeft[p] ++;\r
4349             board[0][i] = EmptySquare;\r
4350         }\r
4351 \r
4352         if(PosFlags(0) & F_ALL_CASTLE_OK) {\r
4353             // shuffles restricted to allow normal castling put KRR first\r
4354             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle\r
4355                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);\r
4356             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles\r
4357                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);\r
4358             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling\r
4359                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);\r
4360             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling\r
4361                 put(board, WhiteRook, 0, 0, ANY);\r
4362             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle\r
4363         }\r
4364 \r
4365         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)\r
4366             // only for even boards make effort to put pairs of colorbound pieces on opposite colors\r
4367             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {\r
4368                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;\r
4369                 while(piecesLeft[p] >= 2) {\r
4370                     AddOnePiece(board, p, 0, LITE);\r
4371                     AddOnePiece(board, p, 0, DARK);\r
4372                 }\r
4373                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)\r
4374             }\r
4375 \r
4376         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {\r
4377             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere\r
4378             // but we leave King and Rooks for last, to possibly obey FRC restriction\r
4379             if(p == (int)WhiteRook) continue;\r
4380             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations\r
4381             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece\r
4382         }\r
4383 \r
4384         // now everything is placed, except perhaps King (Unicorn) and Rooks\r
4385 \r
4386         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {\r
4387             // Last King gets castling rights\r
4388             while(piecesLeft[(int)WhiteUnicorn]) {\r
4389                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);\r
4390                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;\r
4391             }\r
4392 \r
4393             while(piecesLeft[(int)WhiteKing]) {\r
4394                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);\r
4395                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;\r
4396             }\r
4397 \r
4398 \r
4399         } else {\r
4400             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);\r
4401             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);\r
4402         }\r
4403 \r
4404         // Only Rooks can be left; simply place them all\r
4405         while(piecesLeft[(int)WhiteRook]) {\r
4406                 i = put(board, WhiteRook, 0, 0, ANY);\r
4407                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights\r
4408                         if(first) {\r
4409                                 first=0;\r
4410                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;\r
4411                         }\r
4412                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;\r
4413                 }\r
4414         }\r
4415         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white\r
4416             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;\r
4417         }\r
4418 \r
4419         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize\r
4420 }\r
4421 \r
4422 int SetCharTable( char *table, const char * map )\r
4423 /* [HGM] moved here from winboard.c because of its general usefulness */\r
4424 /*       Basically a safe strcpy that uses the last character as King */\r
4425 {\r
4426     int result = FALSE; int NrPieces;\r
4427 \r
4428     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare \r
4429                     && NrPieces >= 12 && !(NrPieces&1)) {\r
4430         int i; /* [HGM] Accept even length from 12 to 34 */\r
4431 \r
4432         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';\r
4433         for( i=0; i<NrPieces/2-1; i++ ) {\r
4434             table[i] = map[i];\r
4435             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];\r
4436         }\r
4437         table[(int) WhiteKing]  = map[NrPieces/2-1];\r
4438         table[(int) BlackKing]  = map[NrPieces-1];\r
4439 \r
4440         result = TRUE;\r
4441     }\r
4442 \r
4443     return result;\r
4444 }\r
4445 \r
4446 void Prelude(Board board)\r
4447 {       // [HGM] superchess: random selection of exo-pieces\r
4448         int i, j, k; ChessSquare p; \r
4449         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };\r
4450 \r
4451         GetPositionNumber(); // use FRC position number\r
4452 \r
4453         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table\r
4454             SetCharTable(pieceToChar, appData.pieceToCharTable);\r
4455             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) \r
4456                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;\r
4457         }\r
4458 \r
4459         j = seed%4;                 seed /= 4; \r
4460         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);\r
4461         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4462         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4463         j = seed%3 + (seed%3 >= j); seed /= 3; \r
4464         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);\r
4465         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4466         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4467         j = seed%3;                 seed /= 3; \r
4468         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);\r
4469         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4470         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4471         j = seed%2 + (seed%2 >= j); seed /= 2; \r
4472         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);\r
4473         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4474         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4475         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);\r
4476         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);\r
4477         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);\r
4478         put(board, exoPieces[0],    0, 0, ANY);\r
4479         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];\r
4480 }\r
4481 \r
4482 void\r
4483 InitPosition(redraw)\r
4484      int redraw;\r
4485 {\r
4486     ChessSquare (* pieces)[BOARD_SIZE];\r
4487     int i, j, pawnRow, overrule,\r
4488     oldx = gameInfo.boardWidth,\r
4489     oldy = gameInfo.boardHeight,\r
4490     oldh = gameInfo.holdingsWidth,\r
4491     oldv = gameInfo.variant;\r
4492 \r
4493     currentMove = forwardMostMove = backwardMostMove = 0;\r
4494     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request\r
4495 \r
4496     /* [AS] Initialize pv info list [HGM] and game status */\r
4497     {\r
4498         for( i=0; i<MAX_MOVES; i++ ) {\r
4499             pvInfoList[i].depth = 0;\r
4500             epStatus[i]=EP_NONE;\r
4501             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
4502         }\r
4503 \r
4504         initialRulePlies = 0; /* 50-move counter start */\r
4505 \r
4506         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;\r
4507         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;\r
4508     }\r
4509 \r
4510     \r
4511     /* [HGM] logic here is completely changed. In stead of full positions */\r
4512     /* the initialized data only consist of the two backranks. The switch */\r
4513     /* selects which one we will use, which is than copied to the Board   */\r
4514     /* initialPosition, which for the rest is initialized by Pawns and    */\r
4515     /* empty squares. This initial position is then copied to boards[0],  */\r
4516     /* possibly after shuffling, so that it remains available.            */\r
4517 \r
4518     gameInfo.holdingsWidth = 0; /* default board sizes */\r
4519     gameInfo.boardWidth    = 8;\r
4520     gameInfo.boardHeight   = 8;\r
4521     gameInfo.holdingsSize  = 0;\r
4522     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */\r
4523     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */\r
4524     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); \r
4525 \r
4526     switch (gameInfo.variant) {\r
4527     case VariantFischeRandom:\r
4528       shuffleOpenings = TRUE;\r
4529     default:\r
4530       pieces = FIDEArray;\r
4531       break;\r
4532     case VariantShatranj:\r
4533       pieces = ShatranjArray;\r
4534       nrCastlingRights = 0;\r
4535       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); \r
4536       break;\r
4537     case VariantTwoKings:\r
4538       pieces = twoKingsArray;\r
4539       break;\r
4540     case VariantCapaRandom:\r
4541       shuffleOpenings = TRUE;\r
4542     case VariantCapablanca:\r
4543       pieces = CapablancaArray;\r
4544       gameInfo.boardWidth = 10;\r
4545       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
4546       break;\r
4547     case VariantGothic:\r
4548       pieces = GothicArray;\r
4549       gameInfo.boardWidth = 10;\r
4550       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
4551       break;\r
4552     case VariantJanus:\r
4553       pieces = JanusArray;\r
4554       gameInfo.boardWidth = 10;\r
4555       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); \r
4556       nrCastlingRights = 6;\r
4557         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
4558         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
4559         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;\r
4560         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
4561         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4562         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;\r
4563       break;\r
4564     case VariantFalcon:\r
4565       pieces = FalconArray;\r
4566       gameInfo.boardWidth = 10;\r
4567       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); \r
4568       break;\r
4569     case VariantXiangqi:\r
4570       pieces = XiangqiArray;\r
4571       gameInfo.boardWidth  = 9;\r
4572       gameInfo.boardHeight = 10;\r
4573       nrCastlingRights = 0;\r
4574       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); \r
4575       break;\r
4576     case VariantShogi:\r
4577       pieces = ShogiArray;\r
4578       gameInfo.boardWidth  = 9;\r
4579       gameInfo.boardHeight = 9;\r
4580       gameInfo.holdingsSize = 7;\r
4581       nrCastlingRights = 0;\r
4582       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); \r
4583       break;\r
4584     case VariantCourier:\r
4585       pieces = CourierArray;\r
4586       gameInfo.boardWidth  = 12;\r
4587       nrCastlingRights = 0;\r
4588       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); \r
4589       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
4590       break;\r
4591     case VariantKnightmate:\r
4592       pieces = KnightmateArray;\r
4593       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); \r
4594       break;\r
4595     case VariantFairy:\r
4596       pieces = fairyArray;\r
4597       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); \r
4598       break;\r
4599     case VariantGreat:\r
4600       pieces = GreatArray;\r
4601       gameInfo.boardWidth = 10;\r
4602       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");\r
4603       gameInfo.holdingsSize = 8;\r
4604       break;\r
4605     case VariantSuper:\r
4606       pieces = FIDEArray;\r
4607       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");\r
4608       gameInfo.holdingsSize = 8;\r
4609       startedFromSetupPosition = TRUE;\r
4610       break;\r
4611     case VariantCrazyhouse:\r
4612     case VariantBughouse:\r
4613       pieces = FIDEArray;\r
4614       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); \r
4615       gameInfo.holdingsSize = 5;\r
4616       break;\r
4617     case VariantWildCastle:\r
4618       pieces = FIDEArray;\r
4619       /* !!?shuffle with kings guaranteed to be on d or e file */\r
4620       shuffleOpenings = 1;\r
4621       break;\r
4622     case VariantNoCastle:\r
4623       pieces = FIDEArray;\r
4624       nrCastlingRights = 0;\r
4625       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
4626       /* !!?unconstrained back-rank shuffle */\r
4627       shuffleOpenings = 1;\r
4628       break;\r
4629     }\r
4630 \r
4631     overrule = 0;\r
4632     if(appData.NrFiles >= 0) {\r
4633         if(gameInfo.boardWidth != appData.NrFiles) overrule++;\r
4634         gameInfo.boardWidth = appData.NrFiles;\r
4635     }\r
4636     if(appData.NrRanks >= 0) {\r
4637         gameInfo.boardHeight = appData.NrRanks;\r
4638     }\r
4639     if(appData.holdingsSize >= 0) {\r
4640         i = appData.holdingsSize;\r
4641         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;\r
4642         gameInfo.holdingsSize = i;\r
4643     }\r
4644     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;\r
4645     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)\r
4646         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);\r
4647 \r
4648     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */\r
4649     if(pawnRow < 1) pawnRow = 1;\r
4650 \r
4651     /* User pieceToChar list overrules defaults */\r
4652     if(appData.pieceToCharTable != NULL)\r
4653         SetCharTable(pieceToChar, appData.pieceToCharTable);\r
4654 \r
4655     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;\r
4656 \r
4657         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)\r
4658             s = (ChessSquare) 0; /* account holding counts in guard band */\r
4659         for( i=0; i<BOARD_HEIGHT; i++ )\r
4660             initialPosition[i][j] = s;\r
4661 \r
4662         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;\r
4663         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];\r
4664         initialPosition[pawnRow][j] = WhitePawn;\r
4665         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;\r
4666         if(gameInfo.variant == VariantXiangqi) {\r
4667             if(j&1) {\r
4668                 initialPosition[pawnRow][j] = \r
4669                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;\r
4670                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {\r
4671                    initialPosition[2][j] = WhiteCannon;\r
4672                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;\r
4673                 }\r
4674             }\r
4675         }\r
4676         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];\r
4677     }\r
4678     if( (gameInfo.variant == VariantShogi) && !overrule ) {\r
4679 \r
4680             j=BOARD_LEFT+1;\r
4681             initialPosition[1][j] = WhiteBishop;\r
4682             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;\r
4683             j=BOARD_RGHT-2;\r
4684             initialPosition[1][j] = WhiteRook;\r
4685             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;\r
4686     }\r
4687 \r
4688     if( nrCastlingRights == -1) {\r
4689         /* [HGM] Build normal castling rights (must be done after board sizing!) */\r
4690         /*       This sets default castling rights from none to normal corners   */\r
4691         /* Variants with other castling rights must set them themselves above    */\r
4692         nrCastlingRights = 6;\r
4693        \r
4694         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
4695         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
4696         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;\r
4697         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
4698         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4699         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;\r
4700      }\r
4701 \r
4702      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);\r
4703      if(gameInfo.variant == VariantGreat) { // promotion commoners\r
4704         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;\r
4705         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;\r
4706         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;\r
4707         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;\r
4708      }\r
4709 #if 0\r
4710     if(gameInfo.variant == VariantFischeRandom) {\r
4711       if( appData.defaultFrcPosition < 0 ) {\r
4712         ShuffleFRC( initialPosition );\r
4713       }\r
4714       else {\r
4715         SetupFRC( initialPosition, appData.defaultFrcPosition );\r
4716       }\r
4717       startedFromSetupPosition = TRUE;\r
4718     } else \r
4719 #else\r
4720   if (appData.debugMode) {\r
4721     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);\r
4722   }\r
4723     if(shuffleOpenings) {\r
4724         SetUpShuffle(initialPosition, appData.defaultFrcPosition);\r
4725         startedFromSetupPosition = TRUE;\r
4726     }\r
4727 #endif\r
4728     if(startedFromPositionFile) {\r
4729       /* [HGM] loadPos: use PositionFile for every new game */\r
4730       CopyBoard(initialPosition, filePosition);\r
4731       for(i=0; i<nrCastlingRights; i++)\r
4732           castlingRights[0][i] = initialRights[i] = fileRights[i];\r
4733       startedFromSetupPosition = TRUE;\r
4734     }\r
4735 \r
4736     CopyBoard(boards[0], initialPosition);\r
4737 \r
4738     if(oldx != gameInfo.boardWidth ||\r
4739        oldy != gameInfo.boardHeight ||\r
4740        oldh != gameInfo.holdingsWidth\r
4741 #ifdef GOTHIC\r
4742        || oldv == VariantGothic ||        // For licensing popups\r
4743        gameInfo.variant == VariantGothic\r
4744 #endif\r
4745 #ifdef FALCON\r
4746        || oldv == VariantFalcon ||\r
4747        gameInfo.variant == VariantFalcon\r
4748 #endif\r
4749                                          )\r
4750             InitDrawingSizes(-2 ,0);\r
4751 \r
4752     if (redraw)\r
4753       DrawPosition(TRUE, boards[currentMove]);\r
4754 }\r
4755 \r
4756 void\r
4757 SendBoard(cps, moveNum)\r
4758      ChessProgramState *cps;\r
4759      int moveNum;\r
4760 {\r
4761     char message[MSG_SIZ];\r
4762     \r
4763     if (cps->useSetboard) {\r
4764       char* fen = PositionToFEN(moveNum, cps->fenOverride);\r
4765       sprintf(message, "setboard %s\n", fen);\r
4766       SendToProgram(message, cps);\r
4767       free(fen);\r
4768 \r
4769     } else {\r
4770       ChessSquare *bp;\r
4771       int i, j;\r
4772       /* Kludge to set black to move, avoiding the troublesome and now\r
4773        * deprecated "black" command.\r
4774        */\r
4775       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);\r
4776 \r
4777       SendToProgram("edit\n", cps);\r
4778       SendToProgram("#\n", cps);\r
4779       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4780         bp = &boards[moveNum][i][BOARD_LEFT];\r
4781         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4782           if ((int) *bp < (int) BlackPawn) {\r
4783             sprintf(message, "%c%c%c\n", PieceToChar(*bp), \r
4784                     AAA + j, ONE + i);\r
4785             if(message[0] == '+' || message[0] == '~') {\r
4786                 sprintf(message, "%c%c%c+\n",\r
4787                         PieceToChar((ChessSquare)(DEMOTED *bp)),\r
4788                         AAA + j, ONE + i);\r
4789             }\r
4790             if(cps->alphaRank) { /* [HGM] shogi: translate coords */\r
4791                 message[1] = BOARD_RGHT   - 1 - j + '1';\r
4792                 message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
4793             }\r
4794             SendToProgram(message, cps);\r
4795           }\r
4796         }\r
4797       }\r
4798     \r
4799       SendToProgram("c\n", cps);\r
4800       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4801         bp = &boards[moveNum][i][BOARD_LEFT];\r
4802         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4803           if (((int) *bp != (int) EmptySquare)\r
4804               && ((int) *bp >= (int) BlackPawn)) {\r
4805             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),\r
4806                     AAA + j, ONE + i);\r
4807             if(message[0] == '+' || message[0] == '~') {\r
4808                 sprintf(message, "%c%c%c+\n",\r
4809                         PieceToChar((ChessSquare)(DEMOTED *bp)),\r
4810                         AAA + j, ONE + i);\r
4811             }\r
4812             if(cps->alphaRank) { /* [HGM] shogi: translate coords */\r
4813                 message[1] = BOARD_RGHT   - 1 - j + '1';\r
4814                 message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
4815             }\r
4816             SendToProgram(message, cps);\r
4817           }\r
4818         }\r
4819       }\r
4820     \r
4821       SendToProgram(".\n", cps);\r
4822     }\r
4823     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */\r
4824 }\r
4825 \r
4826 int\r
4827 IsPromotion(fromX, fromY, toX, toY)\r
4828      int fromX, fromY, toX, toY;\r
4829 {\r
4830     /* [HGM] add Shogi promotions */\r
4831     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;\r
4832     ChessSquare piece;\r
4833 \r
4834     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||\r
4835       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;\r
4836    /* [HGM] Note to self: line above also weeds out drops */\r
4837     piece = boards[currentMove][fromY][fromX];\r
4838     if(gameInfo.variant == VariantShogi) {\r
4839         promotionZoneSize = 3;\r
4840         highestPromotingPiece = (int)WhiteKing;\r
4841         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,\r
4842            and if in normal chess we then allow promotion to King, why not\r
4843            allow promotion of other piece in Shogi?                         */\r
4844     }\r
4845     if((int)piece >= BlackPawn) {\r
4846         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)\r
4847              return FALSE;\r
4848         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;\r
4849     } else {\r
4850         if(  toY < BOARD_HEIGHT - promotionZoneSize &&\r
4851            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;\r
4852     }\r
4853     return ( (int)piece <= highestPromotingPiece );\r
4854 }\r
4855 \r
4856 int\r
4857 InPalace(row, column)\r
4858      int row, column;\r
4859 {   /* [HGM] for Xiangqi */\r
4860     if( (row < 3 || row > BOARD_HEIGHT-4) &&\r
4861          column < (BOARD_WIDTH + 4)/2 &&\r
4862          column > (BOARD_WIDTH - 5)/2 ) return TRUE;\r
4863     return FALSE;\r
4864 }\r
4865 \r
4866 int\r
4867 PieceForSquare (x, y)\r
4868      int x;\r
4869      int y;\r
4870 {\r
4871   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)\r
4872      return -1;\r
4873   else\r
4874      return boards[currentMove][y][x];\r
4875 }\r
4876 \r
4877 int\r
4878 OKToStartUserMove(x, y)\r
4879      int x, y;\r
4880 {\r
4881     ChessSquare from_piece;\r
4882     int white_piece;\r
4883 \r
4884     if (matchMode) return FALSE;\r
4885     if (gameMode == EditPosition) return TRUE;\r
4886 \r
4887     if (x >= 0 && y >= 0)\r
4888       from_piece = boards[currentMove][y][x];\r
4889     else\r
4890       from_piece = EmptySquare;\r
4891 \r
4892     if (from_piece == EmptySquare) return FALSE;\r
4893 \r
4894     white_piece = (int)from_piece >= (int)WhitePawn &&\r
4895       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */\r
4896 \r
4897     switch (gameMode) {\r
4898       case PlayFromGameFile:\r
4899       case AnalyzeFile:\r
4900       case TwoMachinesPlay:\r
4901       case EndOfGame:\r
4902         return FALSE;\r
4903 \r
4904       case IcsObserving:\r
4905       case IcsIdle:\r
4906         return FALSE;\r
4907 \r
4908       case MachinePlaysWhite:\r
4909       case IcsPlayingBlack:\r
4910         if (appData.zippyPlay) return FALSE;\r
4911         if (white_piece) {\r
4912             DisplayMoveError(_("You are playing Black"));\r
4913             return FALSE;\r
4914         }\r
4915         break;\r
4916 \r
4917       case MachinePlaysBlack:\r
4918       case IcsPlayingWhite:\r
4919         if (appData.zippyPlay) return FALSE;\r
4920         if (!white_piece) {\r
4921             DisplayMoveError(_("You are playing White"));\r
4922             return FALSE;\r
4923         }\r
4924         break;\r
4925 \r
4926       case EditGame:\r
4927         if (!white_piece && WhiteOnMove(currentMove)) {\r
4928             DisplayMoveError(_("It is White's turn"));\r
4929             return FALSE;\r
4930         }           \r
4931         if (white_piece && !WhiteOnMove(currentMove)) {\r
4932             DisplayMoveError(_("It is Black's turn"));\r
4933             return FALSE;\r
4934         }           \r
4935         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {\r
4936             /* Editing correspondence game history */\r
4937             /* Could disallow this or prompt for confirmation */\r
4938             cmailOldMove = -1;\r
4939         }\r
4940         if (currentMove < forwardMostMove) {\r
4941             /* Discarding moves */\r
4942             /* Could prompt for confirmation here,\r
4943                but I don't think that's such a good idea */\r
4944             forwardMostMove = currentMove;\r
4945         }\r
4946         break;\r
4947 \r
4948       case BeginningOfGame:\r
4949         if (appData.icsActive) return FALSE;\r
4950         if (!appData.noChessProgram) {\r
4951             if (!white_piece) {\r
4952                 DisplayMoveError(_("You are playing White"));\r
4953                 return FALSE;\r
4954             }\r
4955         }\r
4956         break;\r
4957         \r
4958       case Training:\r
4959         if (!white_piece && WhiteOnMove(currentMove)) {\r
4960             DisplayMoveError(_("It is White's turn"));\r
4961             return FALSE;\r
4962         }           \r
4963         if (white_piece && !WhiteOnMove(currentMove)) {\r
4964             DisplayMoveError(_("It is Black's turn"));\r
4965             return FALSE;\r
4966         }           \r
4967         break;\r
4968 \r
4969       default:\r
4970       case IcsExamining:\r
4971         break;\r
4972     }\r
4973     if (currentMove != forwardMostMove && gameMode != AnalyzeMode\r
4974         && gameMode != AnalyzeFile && gameMode != Training) {\r
4975         DisplayMoveError(_("Displayed position is not current"));\r
4976         return FALSE;\r
4977     }\r
4978     return TRUE;\r
4979 }\r
4980 \r
4981 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;\r
4982 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;\r
4983 int lastLoadGameUseList = FALSE;\r
4984 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];\r
4985 ChessMove lastLoadGameStart = (ChessMove) 0;\r
4986 \r
4987 \r
4988 ChessMove\r
4989 UserMoveTest(fromX, fromY, toX, toY, promoChar)\r
4990      int fromX, fromY, toX, toY;\r
4991      int promoChar;\r
4992 {\r
4993     ChessMove moveType;\r
4994     ChessSquare pdown, pup;\r
4995 \r
4996     if (fromX < 0 || fromY < 0) return ImpossibleMove;\r
4997     if ((fromX == toX) && (fromY == toY)) {\r
4998         return ImpossibleMove;\r
4999     }\r
5000 \r
5001     /* [HGM] suppress all moves into holdings area and guard band */\r
5002     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )\r
5003             return ImpossibleMove;\r
5004 \r
5005     /* [HGM] <sameColor> moved to here from winboard.c */\r
5006     /* note: this code seems to exist for filtering out some obviously illegal premoves */\r
5007     pdown = boards[currentMove][fromY][fromX];\r
5008     pup = boards[currentMove][toY][toX];\r
5009     if (    gameMode != EditPosition &&\r
5010             (WhitePawn <= pdown && pdown < BlackPawn &&\r
5011              WhitePawn <= pup && pup < BlackPawn  ||\r
5012              BlackPawn <= pdown && pdown < EmptySquare &&\r
5013              BlackPawn <= pup && pup < EmptySquare \r
5014             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&\r
5015                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||\r
5016                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) \r
5017         )           )\r
5018          return ImpossibleMove;\r
5019 \r
5020     /* Check if the user is playing in turn.  This is complicated because we\r
5021        let the user "pick up" a piece before it is his turn.  So the piece he\r
5022        tried to pick up may have been captured by the time he puts it down!\r
5023        Therefore we use the color the user is supposed to be playing in this\r
5024        test, not the color of the piece that is currently on the starting\r
5025        square---except in EditGame mode, where the user is playing both\r
5026        sides; fortunately there the capture race can't happen.  (It can\r
5027        now happen in IcsExamining mode, but that's just too bad.  The user\r
5028        will get a somewhat confusing message in that case.)\r
5029        */\r
5030 \r
5031     switch (gameMode) {\r
5032       case PlayFromGameFile:\r
5033       case AnalyzeFile:\r
5034       case TwoMachinesPlay:\r
5035       case EndOfGame:\r
5036       case IcsObserving:\r
5037       case IcsIdle:\r
5038         /* We switched into a game mode where moves are not accepted,\r
5039            perhaps while the mouse button was down. */\r
5040         return ImpossibleMove;\r
5041 \r
5042       case MachinePlaysWhite:\r
5043         /* User is moving for Black */\r
5044         if (WhiteOnMove(currentMove)) {\r
5045             DisplayMoveError(_("It is White's turn"));\r
5046             return ImpossibleMove;\r
5047         }\r
5048         break;\r
5049 \r
5050       case MachinePlaysBlack:\r
5051         /* User is moving for White */\r
5052         if (!WhiteOnMove(currentMove)) {\r
5053             DisplayMoveError(_("It is Black's turn"));\r
5054             return ImpossibleMove;\r
5055         }\r
5056         break;\r
5057 \r
5058       case EditGame:\r
5059       case IcsExamining:\r
5060       case BeginningOfGame:\r
5061       case AnalyzeMode:\r
5062       case Training:\r
5063         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&\r
5064             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {\r
5065             /* User is moving for Black */\r
5066             if (WhiteOnMove(currentMove)) {\r
5067                 DisplayMoveError(_("It is White's turn"));\r
5068                 return ImpossibleMove;\r
5069             }\r
5070         } else {\r
5071             /* User is moving for White */\r
5072             if (!WhiteOnMove(currentMove)) {\r
5073                 DisplayMoveError(_("It is Black's turn"));\r
5074                 return ImpossibleMove;\r
5075             }\r
5076         }\r
5077         break;\r
5078 \r
5079       case IcsPlayingBlack:\r
5080         /* User is moving for Black */\r
5081         if (WhiteOnMove(currentMove)) {\r
5082             if (!appData.premove) {\r
5083                 DisplayMoveError(_("It is White's turn"));\r
5084             } else if (toX >= 0 && toY >= 0) {\r
5085                 premoveToX = toX;\r
5086                 premoveToY = toY;\r
5087                 premoveFromX = fromX;\r
5088                 premoveFromY = fromY;\r
5089                 premovePromoChar = promoChar;\r
5090                 gotPremove = 1;\r
5091                 if (appData.debugMode) \r
5092                     fprintf(debugFP, "Got premove: fromX %d,"\r
5093                             "fromY %d, toX %d, toY %d\n",\r
5094                             fromX, fromY, toX, toY);\r
5095             }\r
5096             return ImpossibleMove;\r
5097         }\r
5098         break;\r
5099 \r
5100       case IcsPlayingWhite:\r
5101         /* User is moving for White */\r
5102         if (!WhiteOnMove(currentMove)) {\r
5103             if (!appData.premove) {\r
5104                 DisplayMoveError(_("It is Black's turn"));\r
5105             } else if (toX >= 0 && toY >= 0) {\r
5106                 premoveToX = toX;\r
5107                 premoveToY = toY;\r
5108                 premoveFromX = fromX;\r
5109                 premoveFromY = fromY;\r
5110                 premovePromoChar = promoChar;\r
5111                 gotPremove = 1;\r
5112                 if (appData.debugMode) \r
5113                     fprintf(debugFP, "Got premove: fromX %d,"\r
5114                             "fromY %d, toX %d, toY %d\n",\r
5115                             fromX, fromY, toX, toY);\r
5116             }\r
5117             return ImpossibleMove;\r
5118         }\r
5119         break;\r
5120 \r
5121       default:\r
5122         break;\r
5123 \r
5124       case EditPosition:\r
5125         /* EditPosition, empty square, or different color piece;\r
5126            click-click move is possible */\r
5127         if (toX == -2 || toY == -2) {\r
5128             boards[0][fromY][fromX] = EmptySquare;\r
5129             return AmbiguousMove;\r
5130         } else if (toX >= 0 && toY >= 0) {\r
5131             boards[0][toY][toX] = boards[0][fromY][fromX];\r
5132             boards[0][fromY][fromX] = EmptySquare;\r
5133             return AmbiguousMove;\r
5134         }\r
5135         return ImpossibleMove;\r
5136     }\r
5137 \r
5138     /* [HGM] If move started in holdings, it means a drop */\r
5139     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { \r
5140          if( pup != EmptySquare ) return ImpossibleMove;\r
5141          if(appData.testLegality) {\r
5142              /* it would be more logical if LegalityTest() also figured out\r
5143               * which drops are legal. For now we forbid pawns on back rank.\r
5144               * Shogi is on its own here...\r
5145               */\r
5146              if( (pdown == WhitePawn || pdown == BlackPawn) &&\r
5147                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )\r
5148                  return(ImpossibleMove); /* no pawn drops on 1st/8th */\r
5149          }\r
5150          return WhiteDrop; /* Not needed to specify white or black yet */\r
5151     }\r
5152 \r
5153     userOfferedDraw = FALSE;\r
5154         \r
5155     /* [HGM] always test for legality, to get promotion info */\r
5156     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),\r
5157                           epStatus[currentMove], castlingRights[currentMove],\r
5158                                          fromY, fromX, toY, toX, promoChar);\r
5159 \r
5160     /* [HGM] but possibly ignore an IllegalMove result */\r
5161     if (appData.testLegality) {\r
5162         if (moveType == IllegalMove || moveType == ImpossibleMove) {\r
5163             DisplayMoveError(_("Illegal move"));\r
5164             return ImpossibleMove;\r
5165         }\r
5166     }\r
5167 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);\r
5168     return moveType;\r
5169     /* [HGM] <popupFix> in stead of calling FinishMove directly, this\r
5170        function is made into one that returns an OK move type if FinishMove\r
5171        should be called. This to give the calling driver routine the\r
5172        opportunity to finish the userMove input with a promotion popup,\r
5173        without bothering the user with this for invalid or illegal moves */\r
5174 \r
5175 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */\r
5176 }\r
5177 \r
5178 /* Common tail of UserMoveEvent and DropMenuEvent */\r
5179 int\r
5180 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)\r
5181      ChessMove moveType;\r
5182      int fromX, fromY, toX, toY;\r
5183      /*char*/int promoChar;\r
5184 {\r
5185     char *bookHit = 0;\r
5186 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);\r
5187     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { \r
5188         // [HGM] superchess: suppress promotions to non-available piece\r
5189         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));\r
5190         if(WhiteOnMove(currentMove)) {\r
5191             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;\r
5192         } else {\r
5193             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;\r
5194         }\r
5195     }\r
5196 \r
5197     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion\r
5198        move type in caller when we know the move is a legal promotion */\r
5199     if(moveType == NormalMove && promoChar)\r
5200         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);\r
5201 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);\r
5202     /* [HGM] convert drag-and-drop piece drops to standard form */\r
5203     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {\r
5204          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
5205          fromX = boards[currentMove][fromY][fromX];\r
5206          fromY = DROP_RANK;\r
5207     }\r
5208 \r
5209     /* [HGM] <popupFix> The following if has been moved here from\r
5210        UserMoveEvent(). Because it seemed to belon here (why not allow\r
5211        piece drops in training games?), and because it can only be\r
5212        performed after it is known to what we promote. */\r
5213     if (gameMode == Training) {\r
5214       /* compare the move played on the board to the next move in the\r
5215        * game. If they match, display the move and the opponent's response. \r
5216        * If they don't match, display an error message.\r
5217        */\r
5218       int saveAnimate;\r
5219       Board testBoard;\r
5220       CopyBoard(testBoard, boards[currentMove]);\r
5221       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);\r
5222 \r
5223       if (CompareBoards(testBoard, boards[currentMove+1])) {\r
5224         ForwardInner(currentMove+1);\r
5225 \r
5226         /* Autoplay the opponent's response.\r
5227          * if appData.animate was TRUE when Training mode was entered,\r
5228          * the response will be animated.\r
5229          */\r
5230         saveAnimate = appData.animate;\r
5231         appData.animate = animateTraining;\r
5232         ForwardInner(currentMove+1);\r
5233         appData.animate = saveAnimate;\r
5234 \r
5235         /* check for the end of the game */\r
5236         if (currentMove >= forwardMostMove) {\r
5237           gameMode = PlayFromGameFile;\r
5238           ModeHighlight();\r
5239           SetTrainingModeOff();\r
5240           DisplayInformation(_("End of game"));\r
5241         }\r
5242       } else {\r
5243         DisplayError(_("Incorrect move"), 0);\r
5244       }\r
5245       return 1;\r
5246     }\r
5247 \r
5248   /* Ok, now we know that the move is good, so we can kill\r
5249      the previous line in Analysis Mode */\r
5250   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {\r
5251     forwardMostMove = currentMove;\r
5252   }\r
5253 \r
5254   /* If we need the chess program but it's dead, restart it */\r
5255   ResurrectChessProgram();\r
5256 \r
5257   /* A user move restarts a paused game*/\r
5258   if (pausing)\r
5259     PauseEvent();\r
5260 \r
5261   thinkOutput[0] = NULLCHAR;\r
5262 \r
5263   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/\r
5264 \r
5265     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) \r
5266                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { \r
5267         // [HGM] superchess: take promotion piece out of holdings\r
5268         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));\r
5269         if(WhiteOnMove(forwardMostMove-1)) {\r
5270             if(!--boards[forwardMostMove][k][BOARD_WIDTH-2])\r
5271                 boards[forwardMostMove][k][BOARD_WIDTH-1] = EmptySquare;\r
5272         } else {\r
5273             if(!--boards[forwardMostMove][BOARD_HEIGHT-1-k][1])\r
5274                 boards[forwardMostMove][BOARD_HEIGHT-1-k][0] = EmptySquare;\r
5275         }\r
5276     }\r
5277 \r
5278   if (gameMode == BeginningOfGame) {\r
5279     if (appData.noChessProgram) {\r
5280       gameMode = EditGame;\r
5281       SetGameInfo();\r
5282     } else {\r
5283       char buf[MSG_SIZ];\r
5284       gameMode = MachinePlaysBlack;\r
5285       StartClocks();\r
5286       SetGameInfo();\r
5287       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
5288       DisplayTitle(buf);\r
5289       if (first.sendName) {\r
5290         sprintf(buf, "name %s\n", gameInfo.white);\r
5291         SendToProgram(buf, &first);\r
5292       }\r
5293       StartClocks();\r
5294     }\r
5295     ModeHighlight();\r
5296   }\r
5297 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);\r
5298   /* Relay move to ICS or chess engine */\r
5299   if (appData.icsActive) {\r
5300     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
5301         gameMode == IcsExamining) {\r
5302       SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
5303       ics_user_moved = 1;\r
5304     }\r
5305   } else {\r
5306     if (first.sendTime && (gameMode == BeginningOfGame ||\r
5307                            gameMode == MachinePlaysWhite ||\r
5308                            gameMode == MachinePlaysBlack)) {\r
5309       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);\r
5310     }\r
5311     if (gameMode != EditGame && gameMode != PlayFromGameFile) {\r
5312          // [HGM] book: if program might be playing, let it use book\r
5313         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);\r
5314         first.maybeThinking = TRUE;\r
5315     } else SendMoveToProgram(forwardMostMove-1, &first);\r
5316     if (currentMove == cmailOldMove + 1) {\r
5317       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
5318     }\r
5319   }\r
5320 \r
5321   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5322 \r
5323   switch (gameMode) {\r
5324   case EditGame:\r
5325     switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
5326                      EP_UNKNOWN, castlingRights[currentMove]) ) {\r
5327     case MT_NONE:\r
5328     case MT_CHECK:\r
5329       break;\r
5330     case MT_CHECKMATE:\r
5331       if (WhiteOnMove(currentMove)) {\r
5332         GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
5333       } else {\r
5334         GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
5335       }\r
5336       break;\r
5337     case MT_STALEMATE:\r
5338       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
5339       break;\r
5340     }\r
5341     break;\r
5342     \r
5343   case MachinePlaysBlack:\r
5344   case MachinePlaysWhite:\r
5345     /* disable certain menu options while machine is thinking */\r
5346     SetMachineThinkingEnables();\r
5347     break;\r
5348 \r
5349   default:\r
5350     break;\r
5351   }\r
5352 \r
5353   if(bookHit) { // [HGM] book: simulate book reply\r
5354         static char bookMove[MSG_SIZ]; // a bit generous?\r
5355 \r
5356         programStats.nodes = programStats.depth = programStats.time = \r
5357         programStats.score = programStats.got_only_move = 0;\r
5358         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
5359 \r
5360         strcpy(bookMove, "move ");\r
5361         strcat(bookMove, bookHit);\r
5362         HandleMachineMove(bookMove, &first);\r
5363   }\r
5364   return 1;\r
5365 }\r
5366 \r
5367 void\r
5368 UserMoveEvent(fromX, fromY, toX, toY, promoChar)\r
5369      int fromX, fromY, toX, toY;\r
5370      int promoChar;\r
5371 {\r
5372     /* [HGM] This routine was added to allow calling of its two logical\r
5373        parts from other modules in the old way. Before, UserMoveEvent()\r
5374        automatically called FinishMove() if the move was OK, and returned\r
5375        otherwise. I separated the two, in order to make it possible to\r
5376        slip a promotion popup in between. But that it always needs two\r
5377        calls, to the first part, (now called UserMoveTest() ), and to\r
5378        FinishMove if the first part succeeded. Calls that do not need\r
5379        to do anything in between, can call this routine the old way. \r
5380     */\r
5381     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);\r
5382 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);\r
5383     if(moveType != ImpossibleMove)\r
5384         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);\r
5385 }\r
5386 \r
5387 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )\r
5388 {\r
5389 //    char * hint = lastHint;\r
5390     FrontEndProgramStats stats;\r
5391 \r
5392     stats.which = cps == &first ? 0 : 1;\r
5393     stats.depth = cpstats->depth;\r
5394     stats.nodes = cpstats->nodes;\r
5395     stats.score = cpstats->score;\r
5396     stats.time = cpstats->time;\r
5397     stats.pv = cpstats->movelist;\r
5398     stats.hint = lastHint;\r
5399     stats.an_move_index = 0;\r
5400     stats.an_move_count = 0;\r
5401 \r
5402     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {\r
5403         stats.hint = cpstats->move_name;\r
5404         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;\r
5405         stats.an_move_count = cpstats->nr_moves;\r
5406     }\r
5407 \r
5408     SetProgramStats( &stats );\r
5409 }\r
5410 \r
5411 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)\r
5412 {   // [HGM] book: this routine intercepts moves to simulate book replies\r
5413     char *bookHit = NULL;\r
5414 \r
5415     //first determine if the incoming move brings opponent into his book\r
5416     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))\r
5417         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move\r
5418     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");\r
5419     if(bookHit != NULL && !cps->bookSuspend) {\r
5420         // make sure opponent is not going to reply after receiving move to book position\r
5421         SendToProgram("force\n", cps);\r
5422         cps->bookSuspend = TRUE; // flag indicating it has to be restarted\r
5423     }\r
5424     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move\r
5425     // now arrange restart after book miss\r
5426     if(bookHit) {\r
5427         // after a book hit we never send 'go', and the code after the call to this routine\r
5428         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').\r
5429         char buf[MSG_SIZ];\r
5430         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(\r
5431         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it\r
5432         SendToProgram(buf, cps);\r
5433         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'\r
5434     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine\r
5435         SendToProgram("go\n", cps);\r
5436         cps->bookSuspend = FALSE; // after a 'go' we are never suspended\r
5437     } else { // 'go' might be sent based on 'firstMove' after this routine returns\r
5438         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return\r
5439             SendToProgram("go\n", cps); \r
5440         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss\r
5441     }\r
5442     return bookHit; // notify caller of hit, so it can take action to send move to opponent\r
5443 }\r
5444 \r
5445 char *savedMessage;\r
5446 ChessProgramState *savedState;\r
5447 void DeferredBookMove(void)\r
5448 {\r
5449         if(savedState->lastPing != savedState->lastPong)\r
5450                     ScheduleDelayedEvent(DeferredBookMove, 10);\r
5451         else\r
5452         HandleMachineMove(savedMessage, savedState);\r
5453 }\r
5454 \r
5455 void\r
5456 HandleMachineMove(message, cps)\r
5457      char *message;\r
5458      ChessProgramState *cps;\r
5459 {\r
5460     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];\r
5461     char realname[MSG_SIZ];\r
5462     int fromX, fromY, toX, toY;\r
5463     ChessMove moveType;\r
5464     char promoChar;\r
5465     char *p;\r
5466     int machineWhite;\r
5467     char *bookHit;\r
5468 \r
5469 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit\r
5470     /*\r
5471      * Kludge to ignore BEL characters\r
5472      */\r
5473     while (*message == '\007') message++;\r
5474 \r
5475     /*\r
5476      * [HGM] engine debug message: ignore lines starting with '#' character\r
5477      */\r
5478     if(cps->debug && *message == '#') return;\r
5479 \r
5480     /*\r
5481      * Look for book output\r
5482      */\r
5483     if (cps == &first && bookRequested) {\r
5484         if (message[0] == '\t' || message[0] == ' ') {\r
5485             /* Part of the book output is here; append it */\r
5486             strcat(bookOutput, message);\r
5487             strcat(bookOutput, "  \n");\r
5488             return;\r
5489         } else if (bookOutput[0] != NULLCHAR) {\r
5490             /* All of book output has arrived; display it */\r
5491             char *p = bookOutput;\r
5492             while (*p != NULLCHAR) {\r
5493                 if (*p == '\t') *p = ' ';\r
5494                 p++;\r
5495             }\r
5496             DisplayInformation(bookOutput);\r
5497             bookRequested = FALSE;\r
5498             /* Fall through to parse the current output */\r
5499         }\r
5500     }\r
5501 \r
5502     /*\r
5503      * Look for machine move.\r
5504      */\r
5505     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||\r
5506         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) \r
5507     {\r
5508         /* This method is only useful on engines that support ping */\r
5509         if (cps->lastPing != cps->lastPong) {\r
5510           if (gameMode == BeginningOfGame) {\r
5511             /* Extra move from before last new; ignore */\r
5512             if (appData.debugMode) {\r
5513                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
5514             }\r
5515           } else {\r
5516             if (appData.debugMode) {\r
5517                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
5518                         cps->which, gameMode);\r
5519             }\r
5520 \r
5521             SendToProgram("undo\n", cps);\r
5522           }\r
5523           return;\r
5524         }\r
5525 \r
5526         switch (gameMode) {\r
5527           case BeginningOfGame:\r
5528             /* Extra move from before last reset; ignore */\r
5529             if (appData.debugMode) {\r
5530                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
5531             }\r
5532             return;\r
5533 \r
5534           case EndOfGame:\r
5535           case IcsIdle:\r
5536           default:\r
5537             /* Extra move after we tried to stop.  The mode test is\r
5538                not a reliable way of detecting this problem, but it's\r
5539                the best we can do on engines that don't support ping.\r
5540             */\r
5541             if (appData.debugMode) {\r
5542                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
5543                         cps->which, gameMode);\r
5544             }\r
5545             SendToProgram("undo\n", cps);\r
5546             return;\r
5547 \r
5548           case MachinePlaysWhite:\r
5549           case IcsPlayingWhite:\r
5550             machineWhite = TRUE;\r
5551             break;\r
5552 \r
5553           case MachinePlaysBlack:\r
5554           case IcsPlayingBlack:\r
5555             machineWhite = FALSE;\r
5556             break;\r
5557 \r
5558           case TwoMachinesPlay:\r
5559             machineWhite = (cps->twoMachinesColor[0] == 'w');\r
5560             break;\r
5561         }\r
5562         if (WhiteOnMove(forwardMostMove) != machineWhite) {\r
5563             if (appData.debugMode) {\r
5564                 fprintf(debugFP,\r
5565                         "Ignoring move out of turn by %s, gameMode %d"\r
5566                         ", forwardMost %d\n",\r
5567                         cps->which, gameMode, forwardMostMove);\r
5568             }\r
5569             return;\r
5570         }\r
5571 \r
5572     if (appData.debugMode) { int f = forwardMostMove;\r
5573         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,\r
5574                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
5575     }\r
5576         if(cps->alphaRank) AlphaRank(machineMove, 4);\r
5577         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,\r
5578                               &fromX, &fromY, &toX, &toY, &promoChar)) {\r
5579             /* Machine move could not be parsed; ignore it. */\r
5580             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),\r
5581                     machineMove, cps->which);\r
5582             DisplayError(buf1, 0);\r
5583             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",\r
5584                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);\r
5585             if (gameMode == TwoMachinesPlay) {\r
5586               GameEnds(machineWhite ? BlackWins : WhiteWins,\r
5587                        buf1, GE_XBOARD);\r
5588             }\r
5589             return;\r
5590         }\r
5591 \r
5592         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */\r
5593         /* So we have to redo legality test with true e.p. status here,  */\r
5594         /* to make sure an illegal e.p. capture does not slip through,   */\r
5595         /* to cause a forfeit on a justified illegal-move complaint      */\r
5596         /* of the opponent.                                              */\r
5597         if( gameMode==TwoMachinesPlay && appData.testLegality\r
5598             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */\r
5599                                                               ) {\r
5600            ChessMove moveType;\r
5601            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
5602                         epStatus[forwardMostMove], castlingRights[forwardMostMove],\r
5603                              fromY, fromX, toY, toX, promoChar);\r
5604             if (appData.debugMode) {\r
5605                 int i;\r
5606                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",\r
5607                     castlingRights[forwardMostMove][i], castlingRank[i]);\r
5608                 fprintf(debugFP, "castling rights\n");\r
5609             }\r
5610             if(moveType == IllegalMove) {\r
5611                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",\r
5612                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);\r
5613                 GameEnds(machineWhite ? BlackWins : WhiteWins,\r
5614                            buf1, GE_XBOARD);\r
5615                 return;\r
5616            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)\r
5617            /* [HGM] Kludge to handle engines that send FRC-style castling\r
5618               when they shouldn't (like TSCP-Gothic) */\r
5619            switch(moveType) {\r
5620              case WhiteASideCastleFR:\r
5621              case BlackASideCastleFR:\r
5622                toX+=2;\r
5623                currentMoveString[2]++;\r
5624                break;\r
5625              case WhiteHSideCastleFR:\r
5626              case BlackHSideCastleFR:\r
5627                toX--;\r
5628                currentMoveString[2]--;\r
5629                break;\r
5630              default: ; // nothing to do, but suppresses warning of pedantic compilers\r
5631            }\r
5632         }\r
5633         hintRequested = FALSE;\r
5634         lastHint[0] = NULLCHAR;\r
5635         bookRequested = FALSE;\r
5636         /* Program may be pondering now */\r
5637         cps->maybeThinking = TRUE;\r
5638         if (cps->sendTime == 2) cps->sendTime = 1;\r
5639         if (cps->offeredDraw) cps->offeredDraw--;\r
5640 \r
5641 #if ZIPPY\r
5642         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&\r
5643             first.initDone) {\r
5644           SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
5645           ics_user_moved = 1;\r
5646           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */\r
5647                 char buf[3*MSG_SIZ];\r
5648 \r
5649                 sprintf(buf, "kibitz %+.2f/%d (%.2f sec, %.0f nodes, %1.0f knps) PV=%s\n",\r
5650                         programStats.score / 100.,\r
5651                         programStats.depth,\r
5652                         programStats.time / 100.,\r
5653                         u64ToDouble(programStats.nodes),\r
5654                         u64ToDouble(programStats.nodes) / (10*abs(programStats.time) + 1.),\r
5655                         programStats.movelist);\r
5656                 SendToICS(buf);\r
5657           }\r
5658         }\r
5659 #endif\r
5660         /* currentMoveString is set as a side-effect of ParseOneMove */\r
5661         strcpy(machineMove, currentMoveString);\r
5662         strcat(machineMove, "\n");\r
5663         strcpy(moveList[forwardMostMove], machineMove);\r
5664 \r
5665         /* [AS] Save move info and clear stats for next move */\r
5666         pvInfoList[ forwardMostMove ].score = programStats.score;\r
5667         pvInfoList[ forwardMostMove ].depth = programStats.depth;\r
5668         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats\r
5669         ClearProgramStats();\r
5670         thinkOutput[0] = NULLCHAR;\r
5671         hiddenThinkOutputState = 0;\r
5672 \r
5673         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/\r
5674 \r
5675         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */\r
5676         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {\r
5677             int count = 0;\r
5678 \r
5679             while( count < adjudicateLossPlies ) {\r
5680                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;\r
5681 \r
5682                 if( count & 1 ) {\r
5683                     score = -score; /* Flip score for winning side */\r
5684                 }\r
5685 \r
5686                 if( score > adjudicateLossThreshold ) {\r
5687                     break;\r
5688                 }\r
5689 \r
5690                 count++;\r
5691             }\r
5692 \r
5693             if( count >= adjudicateLossPlies ) {\r
5694                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5695 \r
5696                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5697                     "Xboard adjudication", \r
5698                     GE_XBOARD );\r
5699 \r
5700                 return;\r
5701             }\r
5702         }\r
5703 \r
5704         if( gameMode == TwoMachinesPlay ) {\r
5705           // [HGM] some adjudications useful with buggy engines\r
5706             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;\r
5707           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {\r
5708 \r
5709 \r
5710             if( appData.testLegality )\r
5711             {   /* [HGM] Some more adjudications for obstinate engines */\r
5712                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,\r
5713                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,\r
5714                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;\r
5715                 static int moveCount = 6;\r
5716 \r
5717                 /* Count what is on board. */\r
5718                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
5719                 {   ChessSquare p = boards[forwardMostMove][i][j];\r
5720                     int m=i;\r
5721 \r
5722                     switch((int) p)\r
5723                     {   /* count B,N,R and other of each side */\r
5724                         case WhiteKing:\r
5725                         case BlackKing:\r
5726                              NrK++; break; // [HGM] atomic: count Kings\r
5727                         case WhiteKnight:\r
5728                              NrWN++; break;\r
5729                         case WhiteBishop:\r
5730                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj\r
5731                              bishopsColor |= 1 << ((i^j)&1);\r
5732                              NrWB++; break;\r
5733                         case BlackKnight:\r
5734                              NrBN++; break;\r
5735                         case BlackBishop:\r
5736                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj\r
5737                              bishopsColor |= 1 << ((i^j)&1);\r
5738                              NrBB++; break;\r
5739                         case WhiteRook:\r
5740                              NrWR++; break;\r
5741                         case BlackRook:\r
5742                              NrBR++; break;\r
5743                         case WhiteQueen:\r
5744                              NrWQ++; break;\r
5745                         case BlackQueen:\r
5746                              NrBQ++; break;\r
5747                         case EmptySquare: \r
5748                              break;\r
5749                         case BlackPawn:\r
5750                              m = 7-i;\r
5751                         case WhitePawn:\r
5752                              PawnAdvance += m; NrPawns++;\r
5753                     }\r
5754                     NrPieces += (p != EmptySquare);\r
5755                     NrW += ((int)p < (int)BlackPawn);\r
5756                     if(gameInfo.variant == VariantXiangqi && \r
5757                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {\r
5758                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces\r
5759                         NrW -= ((int)p < (int)BlackPawn);\r
5760                     }\r
5761                 }\r
5762 \r
5763                 /* Some material-based adjudications that have to be made before stalemate test */\r
5764                 if(gameInfo.variant == VariantAtomic && NrK < 2) {\r
5765                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal\r
5766                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated\r
5767                      if(appData.checkMates) {\r
5768                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move\r
5769                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5770                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, \r
5771                                                         "Xboard adjudication: King destroyed", GE_XBOARD );\r
5772                          return;\r
5773                      }\r
5774                 }\r
5775 \r
5776                 /* Bare King in Shatranj (loses) or Losers (wins) */\r
5777                 if( NrW == 1 || NrPieces - NrW == 1) {\r
5778                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)\r
5779                      epStatus[forwardMostMove] = EP_STALEMATE; // kludge to make position claimable as win\r
5780                      if(appData.checkMates) {\r
5781                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5782                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5783                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5784                                                         "Xboard adjudication: Bare king", GE_XBOARD );\r
5785                          return;\r
5786                      }\r
5787                   } else\r
5788                   if( gameInfo.variant == VariantShatranj && --bare < 0)\r
5789                   {    /* bare King */\r
5790                         epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as win for stm\r
5791                         if(appData.checkMates) {\r
5792                             /* but only adjudicate if adjudication enabled */\r
5793                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move\r
5794                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5795                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, \r
5796                                                         "Xboard adjudication: Bare king", GE_XBOARD );\r
5797                             return;\r
5798                         }\r
5799                   }\r
5800                 } else bare = 1;\r
5801 \r
5802 \r
5803             // don't wait for engine to announce game end if we can judge ourselves\r
5804             switch (MateTest(boards[forwardMostMove],\r
5805                                  PosFlags(forwardMostMove), epFile,\r
5806                                        castlingRights[forwardMostMove]) ) {\r
5807               case MT_NONE:\r
5808               case MT_CHECK:\r
5809               default:\r
5810                 break;\r
5811               case MT_STALEMATE:\r
5812                 if(epStatus[forwardMostMove] != EP_CHECKMATE) // [HGM] spare win through baring or K-capt\r
5813                     epStatus[forwardMostMove] = EP_STALEMATE;\r
5814                 if(appData.checkMates) {\r
5815                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5816                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5817                     if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantSuicide\r
5818                                                          || gameInfo.variant == VariantGiveaway) // [HGM] losers:\r
5819                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, // stalemated side wins!\r
5820                                 "Xboard adjudication: Stalemate", GE_XBOARD );\r
5821                     else\r
5822                         GameEnds( GameIsDrawn, "Xboard adjudication: Stalemate", GE_XBOARD );\r
5823                     return;\r
5824                 }\r
5825                 break;\r
5826               case MT_CHECKMATE:\r
5827                 epStatus[forwardMostMove] = EP_CHECKMATE;\r
5828                 if(appData.checkMates) {\r
5829                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5830                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5831                     GameEnds( WhiteOnMove(forwardMostMove) != (gameInfo.variant == VariantLosers) // [HGM] losers:\r
5832                              ? BlackWins : WhiteWins,            // reverse the result ( A!=1 is !A for a boolean)\r
5833                              "Xboard adjudication: Checkmate", GE_XBOARD );\r
5834                     return;\r
5835                 }\r
5836                 break;\r
5837             }\r
5838 \r
5839                 /* Next absolutely insufficient mating material. */\r
5840                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && \r
5841                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible\r
5842                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||\r
5843                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color\r
5844                 {    /* KBK, KNK, KK of KBKB with like Bishops */\r
5845 \r
5846                      /* always flag draws, for judging claims */\r
5847                      epStatus[forwardMostMove] = EP_INSUF_DRAW;\r
5848 \r
5849                      if(appData.materialDraws) {\r
5850                          /* but only adjudicate them if adjudication enabled */\r
5851                          SendToProgram("force\n", cps->other); // suppress reply\r
5852                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */\r
5853                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5854                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );\r
5855                          return;\r
5856                      }\r
5857                 }\r
5858 \r
5859                 /* Then some trivial draws (only adjudicate, cannot be claimed) */\r
5860                 if(NrPieces == 4 && \r
5861                    (   NrWR == 1 && NrBR == 1 /* KRKR */\r
5862                    || NrWQ==1 && NrBQ==1     /* KQKQ */\r
5863                    || NrWN==2 || NrBN==2     /* KNNK */\r
5864                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */\r
5865                   ) ) {\r
5866                      if(--moveCount < 0 && appData.trivialDraws)\r
5867                      {    /* if the first 3 moves do not show a tactical win, declare draw */\r
5868                           SendToProgram("force\n", cps->other); // suppress reply\r
5869                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5870                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5871                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );\r
5872                           return;\r
5873                      }\r
5874                 } else moveCount = 6;\r
5875             }\r
5876           }\r
5877 #if 1\r
5878     if (appData.debugMode) { int i;\r
5879       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",\r
5880               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],\r
5881               appData.drawRepeats);\r
5882       for( i=forwardMostMove; i>=backwardMostMove; i-- )\r
5883            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);\r
5884 \r
5885     }\r
5886 #endif\r
5887                 /* Check for rep-draws */\r
5888                 count = 0;\r
5889                 for(k = forwardMostMove-2;\r
5890                     k>=backwardMostMove && k>=forwardMostMove-100 &&\r
5891                         epStatus[k] < EP_UNKNOWN &&\r
5892                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;\r
5893                     k-=2)\r
5894                 {   int rights=0;\r
5895 #if 0\r
5896     if (appData.debugMode) {\r
5897       fprintf(debugFP, " loop\n");\r
5898     }\r
5899 #endif\r
5900                     if(CompareBoards(boards[k], boards[forwardMostMove])) {\r
5901 #if 0\r
5902     if (appData.debugMode) {\r
5903       fprintf(debugFP, "match\n");\r
5904     }\r
5905 #endif\r
5906                         /* compare castling rights */\r
5907                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&\r
5908                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )\r
5909                                 rights++; /* King lost rights, while rook still had them */\r
5910                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */\r
5911                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||\r
5912                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )\r
5913                                    rights++; /* but at least one rook lost them */\r
5914                         }\r
5915                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&\r
5916                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )\r
5917                                 rights++; \r
5918                         if( castlingRights[forwardMostMove][5] >= 0 ) {\r
5919                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||\r
5920                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )\r
5921                                    rights++;\r
5922                         }\r
5923 #if 0\r
5924     if (appData.debugMode) {\r
5925       for(i=0; i<nrCastlingRights; i++)\r
5926       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);\r
5927     }\r
5928 \r
5929     if (appData.debugMode) {\r
5930       fprintf(debugFP, " %d %d\n", rights, k);\r
5931     }\r
5932 #endif\r
5933                         if( rights == 0 && ++count > appData.drawRepeats-2\r
5934                             && appData.drawRepeats > 1) {\r
5935                              /* adjudicate after user-specified nr of repeats */\r
5936                              SendToProgram("force\n", cps->other); // suppress reply\r
5937                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5938                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5939                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { \r
5940                                 // [HGM] xiangqi: check for forbidden perpetuals\r
5941                                 int m, ourPerpetual = 1, hisPerpetual = 1;\r
5942                                 for(m=forwardMostMove; m>k; m-=2) {\r
5943                                     if(MateTest(boards[m], PosFlags(m), \r
5944                                                         EP_NONE, castlingRights[m]) != MT_CHECK)\r
5945                                         ourPerpetual = 0; // the current mover did not always check\r
5946                                     if(MateTest(boards[m-1], PosFlags(m-1), \r
5947                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)\r
5948                                         hisPerpetual = 0; // the opponent did not always check\r
5949                                 }\r
5950                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",\r
5951                                                                         ourPerpetual, hisPerpetual);\r
5952                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit\r
5953                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5954                                            "Xboard adjudication: perpetual checking", GE_XBOARD );\r
5955                                     return;\r
5956                                 }\r
5957                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet\r
5958                                     break; // (or we would have caught him before). Abort repetition-checking loop.\r
5959                                 // Now check for perpetual chases\r
5960                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase\r
5961                                     hisPerpetual = PerpetualChase(k, forwardMostMove);\r
5962                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);\r
5963                                     if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit\r
5964                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5965                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );\r
5966                                         return;\r
5967                                     }\r
5968                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet\r
5969                                         break; // Abort repetition-checking loop.\r
5970                                 }\r
5971                                 // if neither of us is checking or chasing all the time, or both are, it is draw\r
5972                              }\r
5973                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );\r
5974                              return;\r
5975                         }\r
5976                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */\r
5977                              epStatus[forwardMostMove] = EP_REP_DRAW;\r
5978                     }\r
5979                 }\r
5980 \r
5981                 /* Now we test for 50-move draws. Determine ply count */\r
5982                 count = forwardMostMove;\r
5983                 /* look for last irreversble move */\r
5984                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )\r
5985                     count--;\r
5986                 /* if we hit starting position, add initial plies */\r
5987                 if( count == backwardMostMove )\r
5988                     count -= initialRulePlies;\r
5989                 count = forwardMostMove - count; \r
5990                 if( count >= 100)\r
5991                          epStatus[forwardMostMove] = EP_RULE_DRAW;\r
5992                          /* this is used to judge if draw claims are legal */\r
5993                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {\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                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );\r
5998                          return;\r
5999                 }\r
6000 \r
6001                 /* if draw offer is pending, treat it as a draw claim\r
6002                  * when draw condition present, to allow engines a way to\r
6003                  * claim draws before making their move to avoid a race\r
6004                  * condition occurring after their move\r
6005                  */\r
6006                 if( cps->other->offeredDraw || cps->offeredDraw ) {\r
6007                          char *p = NULL;\r
6008                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)\r
6009                              p = "Draw claim: 50-move rule";\r
6010                          if(epStatus[forwardMostMove] == EP_REP_DRAW)\r
6011                              p = "Draw claim: 3-fold repetition";\r
6012                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)\r
6013                              p = "Draw claim: insufficient mating material";\r
6014                          if( p != NULL ) {\r
6015                              SendToProgram("force\n", cps->other); // suppress reply\r
6016                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6017                              GameEnds( GameIsDrawn, p, GE_XBOARD );\r
6018                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6019                              return;\r
6020                          }\r
6021                 }\r
6022 \r
6023 \r
6024                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {\r
6025                     SendToProgram("force\n", cps->other); // suppress reply\r
6026                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6027                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6028 \r
6029                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );\r
6030 \r
6031                     return;\r
6032                 }\r
6033         }\r
6034 \r
6035         bookHit = NULL;\r
6036         if (gameMode == TwoMachinesPlay) {\r
6037             /* [HGM] relaying draw offers moved to after reception of move */\r
6038             /* and interpreting offer as claim if it brings draw condition */\r
6039             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {\r
6040                 SendToProgram("draw\n", cps->other);\r
6041             }\r
6042             if (cps->other->sendTime) {\r
6043                 SendTimeRemaining(cps->other,\r
6044                                   cps->other->twoMachinesColor[0] == 'w');\r
6045             }\r
6046             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);\r
6047             if (firstMove && !bookHit) {\r
6048                 firstMove = FALSE;\r
6049                 if (cps->other->useColors) {\r
6050                   SendToProgram(cps->other->twoMachinesColor, cps->other);\r
6051                 }\r
6052                 SendToProgram("go\n", cps->other);\r
6053             }\r
6054             cps->other->maybeThinking = TRUE;\r
6055         }\r
6056 \r
6057         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6058         \r
6059         if (!pausing && appData.ringBellAfterMoves) {\r
6060             RingBell();\r
6061         }\r
6062 \r
6063         /* \r
6064          * Reenable menu items that were disabled while\r
6065          * machine was thinking\r
6066          */\r
6067         if (gameMode != TwoMachinesPlay)\r
6068             SetUserThinkingEnables();\r
6069 \r
6070         // [HGM] book: after book hit opponent has received move and is now in force mode\r
6071         // force the book reply into it, and then fake that it outputted this move by jumping\r
6072         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move\r
6073         if(bookHit) {\r
6074                 static char bookMove[MSG_SIZ]; // a bit generous?\r
6075 \r
6076                 strcpy(bookMove, "move ");\r
6077                 strcat(bookMove, bookHit);\r
6078                 message = bookMove;\r
6079                 cps = cps->other;\r
6080                 programStats.nodes = programStats.depth = programStats.time = \r
6081                 programStats.score = programStats.got_only_move = 0;\r
6082                 sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
6083 \r
6084                 if(cps->lastPing != cps->lastPong) {\r
6085                     savedMessage = message; // args for deferred call\r
6086                     savedState = cps;\r
6087                     ScheduleDelayedEvent(DeferredBookMove, 10);\r
6088                     return;\r
6089                 }\r
6090                 goto FakeBookMove;\r
6091         }\r
6092 \r
6093         return;\r
6094     }\r
6095 \r
6096     /* Set special modes for chess engines.  Later something general\r
6097      *  could be added here; for now there is just one kludge feature,\r
6098      *  needed because Crafty 15.10 and earlier don't ignore SIGINT\r
6099      *  when "xboard" is given as an interactive command.\r
6100      */\r
6101     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {\r
6102         cps->useSigint = FALSE;\r
6103         cps->useSigterm = FALSE;\r
6104     }\r
6105 \r
6106     /* [HGM] Allow engine to set up a position. Don't ask me why one would\r
6107      * want this, I was asked to put it in, and obliged.\r
6108      */\r
6109     if (!strncmp(message, "setboard ", 9)) {\r
6110         Board initial_position; int i;\r
6111 \r
6112         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);\r
6113 \r
6114         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {\r
6115             DisplayError(_("Bad FEN received from engine"), 0);\r
6116             return ;\r
6117         } else {\r
6118            Reset(FALSE, FALSE);\r
6119            CopyBoard(boards[0], initial_position);\r
6120            initialRulePlies = FENrulePlies;\r
6121            epStatus[0] = FENepStatus;\r
6122            for( i=0; i<nrCastlingRights; i++ )\r
6123                 castlingRights[0][i] = FENcastlingRights[i];\r
6124            if(blackPlaysFirst) gameMode = MachinePlaysWhite;\r
6125            else gameMode = MachinePlaysBlack;                 \r
6126            DrawPosition(FALSE, boards[currentMove]);\r
6127         }\r
6128         return;\r
6129     }\r
6130 \r
6131     /*\r
6132      * Look for communication commands\r
6133      */\r
6134     if (!strncmp(message, "telluser ", 9)) {\r
6135         DisplayNote(message + 9);\r
6136         return;\r
6137     }\r
6138     if (!strncmp(message, "tellusererror ", 14)) {\r
6139         DisplayError(message + 14, 0);\r
6140         return;\r
6141     }\r
6142     if (!strncmp(message, "tellopponent ", 13)) {\r
6143       if (appData.icsActive) {\r
6144         if (loggedOn) {\r
6145           sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);\r
6146           SendToICS(buf1);\r
6147         }\r
6148       } else {\r
6149         DisplayNote(message + 13);\r
6150       }\r
6151       return;\r
6152     }\r
6153     if (!strncmp(message, "tellothers ", 11)) {\r
6154       if (appData.icsActive) {\r
6155         if (loggedOn) {\r
6156           sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);\r
6157           SendToICS(buf1);\r
6158         }\r
6159       }\r
6160       return;\r
6161     }\r
6162     if (!strncmp(message, "tellall ", 8)) {\r
6163       if (appData.icsActive) {\r
6164         if (loggedOn) {\r
6165           sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);\r
6166           SendToICS(buf1);\r
6167         }\r
6168       } else {\r
6169         DisplayNote(message + 8);\r
6170       }\r
6171       return;\r
6172     }\r
6173     if (strncmp(message, "warning", 7) == 0) {\r
6174         /* Undocumented feature, use tellusererror in new code */\r
6175         DisplayError(message, 0);\r
6176         return;\r
6177     }\r
6178     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {\r
6179         strcpy(realname, cps->tidy);\r
6180         strcat(realname, " query");\r
6181         AskQuestion(realname, buf2, buf1, cps->pr);\r
6182         return;\r
6183     }\r
6184     /* Commands from the engine directly to ICS.  We don't allow these to be \r
6185      *  sent until we are logged on. Crafty kibitzes have been known to \r
6186      *  interfere with the login process.\r
6187      */\r
6188     if (loggedOn) {\r
6189         if (!strncmp(message, "tellics ", 8)) {\r
6190             SendToICS(message + 8);\r
6191             SendToICS("\n");\r
6192             return;\r
6193         }\r
6194         if (!strncmp(message, "tellicsnoalias ", 15)) {\r
6195             SendToICS(ics_prefix);\r
6196             SendToICS(message + 15);\r
6197             SendToICS("\n");\r
6198             return;\r
6199         }\r
6200         /* The following are for backward compatibility only */\r
6201         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||\r
6202             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {\r
6203             SendToICS(ics_prefix);\r
6204             SendToICS(message);\r
6205             SendToICS("\n");\r
6206             return;\r
6207         }\r
6208     }\r
6209     if (strncmp(message, "feature ", 8) == 0) {\r
6210       ParseFeatures(message+8, cps);\r
6211     }\r
6212     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {\r
6213         return;\r
6214     }\r
6215     /*\r
6216      * If the move is illegal, cancel it and redraw the board.\r
6217      * Also deal with other error cases.  Matching is rather loose\r
6218      * here to accommodate engines written before the spec.\r
6219      */\r
6220     if (strncmp(message + 1, "llegal move", 11) == 0 ||\r
6221         strncmp(message, "Error", 5) == 0) {\r
6222         if (StrStr(message, "name") || \r
6223             StrStr(message, "rating") || StrStr(message, "?") ||\r
6224             StrStr(message, "result") || StrStr(message, "board") ||\r
6225             StrStr(message, "bk") || StrStr(message, "computer") ||\r
6226             StrStr(message, "variant") || StrStr(message, "hint") ||\r
6227             StrStr(message, "random") || StrStr(message, "depth") ||\r
6228             StrStr(message, "accepted")) {\r
6229             return;\r
6230         }\r
6231         if (StrStr(message, "protover")) {\r
6232           /* Program is responding to input, so it's apparently done\r
6233              initializing, and this error message indicates it is\r
6234              protocol version 1.  So we don't need to wait any longer\r
6235              for it to initialize and send feature commands. */\r
6236           FeatureDone(cps, 1);\r
6237           cps->protocolVersion = 1;\r
6238           return;\r
6239         }\r
6240         cps->maybeThinking = FALSE;\r
6241 \r
6242         if (StrStr(message, "draw")) {\r
6243             /* Program doesn't have "draw" command */\r
6244             cps->sendDrawOffers = 0;\r
6245             return;\r
6246         }\r
6247         if (cps->sendTime != 1 &&\r
6248             (StrStr(message, "time") || StrStr(message, "otim"))) {\r
6249           /* Program apparently doesn't have "time" or "otim" command */\r
6250           cps->sendTime = 0;\r
6251           return;\r
6252         }\r
6253         if (StrStr(message, "analyze")) {\r
6254             cps->analysisSupport = FALSE;\r
6255             cps->analyzing = FALSE;\r
6256             Reset(FALSE, TRUE);\r
6257             sprintf(buf2, _("%s does not support analysis"), cps->tidy);\r
6258             DisplayError(buf2, 0);\r
6259             return;\r
6260         }\r
6261         if (StrStr(message, "(no matching move)st")) {\r
6262           /* Special kludge for GNU Chess 4 only */\r
6263           cps->stKludge = TRUE;\r
6264           SendTimeControl(cps, movesPerSession, timeControl,\r
6265                           timeIncrement, appData.searchDepth,\r
6266                           searchTime);\r
6267           return;\r
6268         }\r
6269         if (StrStr(message, "(no matching move)sd")) {\r
6270           /* Special kludge for GNU Chess 4 only */\r
6271           cps->sdKludge = TRUE;\r
6272           SendTimeControl(cps, movesPerSession, timeControl,\r
6273                           timeIncrement, appData.searchDepth,\r
6274                           searchTime);\r
6275           return;\r
6276         }\r
6277         if (!StrStr(message, "llegal")) {\r
6278             return;\r
6279         }\r
6280         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
6281             gameMode == IcsIdle) return;\r
6282         if (forwardMostMove <= backwardMostMove) return;\r
6283 #if 0\r
6284         /* Following removed: it caused a bug where a real illegal move\r
6285            message in analyze mored would be ignored. */\r
6286         if (cps == &first && programStats.ok_to_send == 0) {\r
6287             /* Bogus message from Crafty responding to "."  This filtering\r
6288                can miss some of the bad messages, but fortunately the bug \r
6289                is fixed in current Crafty versions, so it doesn't matter. */\r
6290             return;\r
6291         }\r
6292 #endif\r
6293         if (pausing) PauseEvent();\r
6294         if (gameMode == PlayFromGameFile) {\r
6295             /* Stop reading this game file */\r
6296             gameMode = EditGame;\r
6297             ModeHighlight();\r
6298         }\r
6299         currentMove = --forwardMostMove;\r
6300         DisplayMove(currentMove-1); /* before DisplayMoveError */\r
6301         SwitchClocks();\r
6302         DisplayBothClocks();\r
6303         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),\r
6304                 parseList[currentMove], cps->which);\r
6305         DisplayMoveError(buf1);\r
6306         DrawPosition(FALSE, boards[currentMove]);\r
6307 \r
6308         /* [HGM] illegal-move claim should forfeit game when Xboard */\r
6309         /* only passes fully legal moves                            */\r
6310         if( appData.testLegality && gameMode == TwoMachinesPlay ) {\r
6311             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,\r
6312                                 "False illegal-move claim", GE_XBOARD );\r
6313         }\r
6314         return;\r
6315     }\r
6316     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {\r
6317         /* Program has a broken "time" command that\r
6318            outputs a string not ending in newline.\r
6319            Don't use it. */\r
6320         cps->sendTime = 0;\r
6321     }\r
6322     \r
6323     /*\r
6324      * If chess program startup fails, exit with an error message.\r
6325      * Attempts to recover here are futile.\r
6326      */\r
6327     if ((StrStr(message, "unknown host") != NULL)\r
6328         || (StrStr(message, "No remote directory") != NULL)\r
6329         || (StrStr(message, "not found") != NULL)\r
6330         || (StrStr(message, "No such file") != NULL)\r
6331         || (StrStr(message, "can't alloc") != NULL)\r
6332         || (StrStr(message, "Permission denied") != NULL)) {\r
6333 \r
6334         cps->maybeThinking = FALSE;\r
6335         sprintf(buf1, _("Failed to start %s chess program %s on %s: %s\n"),\r
6336                 cps->which, cps->program, cps->host, message);\r
6337         RemoveInputSource(cps->isr);\r
6338         DisplayFatalError(buf1, 0, 1);\r
6339         return;\r
6340     }\r
6341     \r
6342     /* \r
6343      * Look for hint output\r
6344      */\r
6345     if (sscanf(message, "Hint: %s", buf1) == 1) {\r
6346         if (cps == &first && hintRequested) {\r
6347             hintRequested = FALSE;\r
6348             if (ParseOneMove(buf1, forwardMostMove, &moveType,\r
6349                                  &fromX, &fromY, &toX, &toY, &promoChar)) {\r
6350                 (void) CoordsToAlgebraic(boards[forwardMostMove],\r
6351                                     PosFlags(forwardMostMove), EP_UNKNOWN,\r
6352                                     fromY, fromX, toY, toX, promoChar, buf1);\r
6353                 sprintf(buf2, _("Hint: %s"), buf1);\r
6354                 DisplayInformation(buf2);\r
6355             } else {\r
6356                 /* Hint move could not be parsed!? */\r
6357                 sprintf(buf2,\r
6358                         _("Illegal hint move \"%s\"\nfrom %s chess program"),\r
6359                         buf1, cps->which);\r
6360                 DisplayError(buf2, 0);\r
6361             }\r
6362         } else {\r
6363             strcpy(lastHint, buf1);\r
6364         }\r
6365         return;\r
6366     }\r
6367 \r
6368     /*\r
6369      * Ignore other messages if game is not in progress\r
6370      */\r
6371     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
6372         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;\r
6373 \r
6374     /*\r
6375      * look for win, lose, draw, or draw offer\r
6376      */\r
6377     if (strncmp(message, "1-0", 3) == 0) {\r
6378         char *p, *q, *r = "";\r
6379         p = strchr(message, '{');\r
6380         if (p) {\r
6381             q = strchr(p, '}');\r
6382             if (q) {\r
6383                 *q = NULLCHAR;\r
6384                 r = p + 1;\r
6385             }\r
6386         }\r
6387         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */\r
6388         return;\r
6389     } else if (strncmp(message, "0-1", 3) == 0) {\r
6390         char *p, *q, *r = "";\r
6391         p = strchr(message, '{');\r
6392         if (p) {\r
6393             q = strchr(p, '}');\r
6394             if (q) {\r
6395                 *q = NULLCHAR;\r
6396                 r = p + 1;\r
6397             }\r
6398         }\r
6399         /* Kludge for Arasan 4.1 bug */\r
6400         if (strcmp(r, "Black resigns") == 0) {\r
6401             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));\r
6402             return;\r
6403         }\r
6404         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));\r
6405         return;\r
6406     } else if (strncmp(message, "1/2", 3) == 0) {\r
6407         char *p, *q, *r = "";\r
6408         p = strchr(message, '{');\r
6409         if (p) {\r
6410             q = strchr(p, '}');\r
6411             if (q) {\r
6412                 *q = NULLCHAR;\r
6413                 r = p + 1;\r
6414             }\r
6415         }\r
6416             \r
6417         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));\r
6418         return;\r
6419 \r
6420     } else if (strncmp(message, "White resign", 12) == 0) {\r
6421         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
6422         return;\r
6423     } else if (strncmp(message, "Black resign", 12) == 0) {\r
6424         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
6425         return;\r
6426     } else if (strncmp(message, "White matches", 13) == 0 ||\r
6427                strncmp(message, "Black matches", 13) == 0   ) {\r
6428         /* [HGM] ignore GNUShogi noises */\r
6429         return;\r
6430     } else if (strncmp(message, "White", 5) == 0 &&\r
6431                message[5] != '(' &&\r
6432                StrStr(message, "Black") == NULL) {\r
6433         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6434         return;\r
6435     } else if (strncmp(message, "Black", 5) == 0 &&\r
6436                message[5] != '(') {\r
6437         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6438         return;\r
6439     } else if (strcmp(message, "resign") == 0 ||\r
6440                strcmp(message, "computer resigns") == 0) {\r
6441         switch (gameMode) {\r
6442           case MachinePlaysBlack:\r
6443           case IcsPlayingBlack:\r
6444             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);\r
6445             break;\r
6446           case MachinePlaysWhite:\r
6447           case IcsPlayingWhite:\r
6448             GameEnds(BlackWins, "White resigns", GE_ENGINE);\r
6449             break;\r
6450           case TwoMachinesPlay:\r
6451             if (cps->twoMachinesColor[0] == 'w')\r
6452               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
6453             else\r
6454               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
6455             break;\r
6456           default:\r
6457             /* can't happen */\r
6458             break;\r
6459         }\r
6460         return;\r
6461     } else if (strncmp(message, "opponent mates", 14) == 0) {\r
6462         switch (gameMode) {\r
6463           case MachinePlaysBlack:\r
6464           case IcsPlayingBlack:\r
6465             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
6466             break;\r
6467           case MachinePlaysWhite:\r
6468           case IcsPlayingWhite:\r
6469             GameEnds(BlackWins, "Black mates", GE_ENGINE);\r
6470             break;\r
6471           case TwoMachinesPlay:\r
6472             if (cps->twoMachinesColor[0] == 'w')\r
6473               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6474             else\r
6475               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6476             break;\r
6477           default:\r
6478             /* can't happen */\r
6479             break;\r
6480         }\r
6481         return;\r
6482     } else if (strncmp(message, "computer mates", 14) == 0) {\r
6483         switch (gameMode) {\r
6484           case MachinePlaysBlack:\r
6485           case IcsPlayingBlack:\r
6486             GameEnds(BlackWins, "Black mates", GE_ENGINE1);\r
6487             break;\r
6488           case MachinePlaysWhite:\r
6489           case IcsPlayingWhite:\r
6490             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
6491             break;\r
6492           case TwoMachinesPlay:\r
6493             if (cps->twoMachinesColor[0] == 'w')\r
6494               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6495             else\r
6496               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6497             break;\r
6498           default:\r
6499             /* can't happen */\r
6500             break;\r
6501         }\r
6502         return;\r
6503     } else if (strncmp(message, "checkmate", 9) == 0) {\r
6504         if (WhiteOnMove(forwardMostMove)) {\r
6505             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6506         } else {\r
6507             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6508         }\r
6509         return;\r
6510     } else if (strstr(message, "Draw") != NULL ||\r
6511                strstr(message, "game is a draw") != NULL) {\r
6512         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));\r
6513         return;\r
6514     } else if (strstr(message, "offer") != NULL &&\r
6515                strstr(message, "draw") != NULL) {\r
6516 #if ZIPPY\r
6517         if (appData.zippyPlay && first.initDone) {\r
6518             /* Relay offer to ICS */\r
6519             SendToICS(ics_prefix);\r
6520             SendToICS("draw\n");\r
6521         }\r
6522 #endif\r
6523         cps->offeredDraw = 2; /* valid until this engine moves twice */\r
6524         if (gameMode == TwoMachinesPlay) {\r
6525             if (cps->other->offeredDraw) {\r
6526                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
6527             /* [HGM] in two-machine mode we delay relaying draw offer      */\r
6528             /* until after we also have move, to see if it is really claim */\r
6529             }\r
6530 #if 0\r
6531               else {\r
6532                 if (cps->other->sendDrawOffers) {\r
6533                     SendToProgram("draw\n", cps->other);\r
6534                 }\r
6535             }\r
6536 #endif\r
6537         } else if (gameMode == MachinePlaysWhite ||\r
6538                    gameMode == MachinePlaysBlack) {\r
6539           if (userOfferedDraw) {\r
6540             DisplayInformation(_("Machine accepts your draw offer"));\r
6541             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
6542           } else {\r
6543             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));\r
6544           }\r
6545         }\r
6546     }\r
6547 \r
6548     \r
6549     /*\r
6550      * Look for thinking output\r
6551      */\r
6552     if ( appData.showThinking // [HGM] thinking: test all options that cause this output\r
6553           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()\r
6554                                 ) {\r
6555         int plylev, mvleft, mvtot, curscore, time;\r
6556         char mvname[MOVE_LEN];\r
6557         u64 nodes; // [DM]\r
6558         char plyext;\r
6559         int ignore = FALSE;\r
6560         int prefixHint = FALSE;\r
6561         mvname[0] = NULLCHAR;\r
6562 \r
6563         switch (gameMode) {\r
6564           case MachinePlaysBlack:\r
6565           case IcsPlayingBlack:\r
6566             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
6567             break;\r
6568           case MachinePlaysWhite:\r
6569           case IcsPlayingWhite:\r
6570             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
6571             break;\r
6572           case AnalyzeMode:\r
6573           case AnalyzeFile:\r
6574             break;\r
6575           case IcsObserving: /* [DM] icsEngineAnalyze */\r
6576             if (!appData.icsEngineAnalyze) ignore = TRUE;\r
6577             break;\r
6578           case TwoMachinesPlay:\r
6579             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {\r
6580                 ignore = TRUE;\r
6581             }\r
6582             break;\r
6583           default:\r
6584             ignore = TRUE;\r
6585             break;\r
6586         }\r
6587 \r
6588         if (!ignore) {\r
6589             buf1[0] = NULLCHAR;\r
6590             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",\r
6591                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {\r
6592 \r
6593                 if (plyext != ' ' && plyext != '\t') {\r
6594                     time *= 100;\r
6595                 }\r
6596 \r
6597                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
6598                 if( cps->scoreIsAbsolute && \r
6599                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )\r
6600                 {\r
6601                     curscore = -curscore;\r
6602                 }\r
6603 \r
6604 \r
6605                 programStats.depth = plylev;\r
6606                 programStats.nodes = nodes;\r
6607                 programStats.time = time;\r
6608                 programStats.score = curscore;\r
6609                 programStats.got_only_move = 0;\r
6610 \r
6611                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */\r
6612                         int ticklen;\r
6613 \r
6614                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time\r
6615                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time\r
6616                         if(WhiteOnMove(forwardMostMove)) \r
6617                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;\r
6618                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;\r
6619                 }\r
6620 \r
6621                 /* Buffer overflow protection */\r
6622                 if (buf1[0] != NULLCHAR) {\r
6623                     if (strlen(buf1) >= sizeof(programStats.movelist)\r
6624                         && appData.debugMode) {\r
6625                         fprintf(debugFP,\r
6626                                 "PV is too long; using the first %d bytes.\n",\r
6627                                 sizeof(programStats.movelist) - 1);\r
6628                     }\r
6629 \r
6630                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );\r
6631                 } else {\r
6632                     sprintf(programStats.movelist, " no PV\n");\r
6633                 }\r
6634 \r
6635                 if (programStats.seen_stat) {\r
6636                     programStats.ok_to_send = 1;\r
6637                 }\r
6638 \r
6639                 if (strchr(programStats.movelist, '(') != NULL) {\r
6640                     programStats.line_is_book = 1;\r
6641                     programStats.nr_moves = 0;\r
6642                     programStats.moves_left = 0;\r
6643                 } else {\r
6644                     programStats.line_is_book = 0;\r
6645                 }\r
6646 \r
6647                 SendProgramStatsToFrontend( cps, &programStats );\r
6648 \r
6649                 /* \r
6650                     [AS] Protect the thinkOutput buffer from overflow... this\r
6651                     is only useful if buf1 hasn't overflowed first!\r
6652                 */\r
6653                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",\r
6654                         plylev, \r
6655                         (gameMode == TwoMachinesPlay ?\r
6656                          ToUpper(cps->twoMachinesColor[0]) : ' '),\r
6657                         ((double) curscore) / 100.0,\r
6658                         prefixHint ? lastHint : "",\r
6659                         prefixHint ? " " : "" );\r
6660 \r
6661                 if( buf1[0] != NULLCHAR ) {\r
6662                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;\r
6663 \r
6664                     if( strlen(buf1) > max_len ) {\r
6665                         if( appData.debugMode) {\r
6666                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");\r
6667                         }\r
6668                         buf1[max_len+1] = '\0';\r
6669                     }\r
6670 \r
6671                     strcat( thinkOutput, buf1 );\r
6672                 }\r
6673 \r
6674                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode\r
6675                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6676                     DisplayMove(currentMove - 1);\r
6677                     DisplayAnalysis();\r
6678                 }\r
6679                 return;\r
6680 \r
6681             } else if ((p=StrStr(message, "(only move)")) != NULL) {\r
6682                 /* crafty (9.25+) says "(only move) <move>"\r
6683                  * if there is only 1 legal move\r
6684                  */\r
6685                 sscanf(p, "(only move) %s", buf1);\r
6686                 sprintf(thinkOutput, "%s (only move)", buf1);\r
6687                 sprintf(programStats.movelist, "%s (only move)", buf1);\r
6688                 programStats.depth = 1;\r
6689                 programStats.nr_moves = 1;\r
6690                 programStats.moves_left = 1;\r
6691                 programStats.nodes = 1;\r
6692                 programStats.time = 1;\r
6693                 programStats.got_only_move = 1;\r
6694 \r
6695                 /* Not really, but we also use this member to\r
6696                    mean "line isn't going to change" (Crafty\r
6697                    isn't searching, so stats won't change) */\r
6698                 programStats.line_is_book = 1;\r
6699 \r
6700                 SendProgramStatsToFrontend( cps, &programStats );\r
6701                 \r
6702                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || \r
6703                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6704                     DisplayMove(currentMove - 1);\r
6705                     DisplayAnalysis();\r
6706                 }\r
6707                 return;\r
6708             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",\r
6709                               &time, &nodes, &plylev, &mvleft,\r
6710                               &mvtot, mvname) >= 5) {\r
6711                 /* The stat01: line is from Crafty (9.29+) in response\r
6712                    to the "." command */\r
6713                 programStats.seen_stat = 1;\r
6714                 cps->maybeThinking = TRUE;\r
6715 \r
6716                 if (programStats.got_only_move || !appData.periodicUpdates)\r
6717                   return;\r
6718 \r
6719                 programStats.depth = plylev;\r
6720                 programStats.time = time;\r
6721                 programStats.nodes = nodes;\r
6722                 programStats.moves_left = mvleft;\r
6723                 programStats.nr_moves = mvtot;\r
6724                 strcpy(programStats.move_name, mvname);\r
6725                 programStats.ok_to_send = 1;\r
6726                 programStats.movelist[0] = '\0';\r
6727 \r
6728                 SendProgramStatsToFrontend( cps, &programStats );\r
6729 \r
6730                 DisplayAnalysis();\r
6731                 return;\r
6732 \r
6733             } else if (strncmp(message,"++",2) == 0) {\r
6734                 /* Crafty 9.29+ outputs this */\r
6735                 programStats.got_fail = 2;\r
6736                 return;\r
6737 \r
6738             } else if (strncmp(message,"--",2) == 0) {\r
6739                 /* Crafty 9.29+ outputs this */\r
6740                 programStats.got_fail = 1;\r
6741                 return;\r
6742 \r
6743             } else if (thinkOutput[0] != NULLCHAR &&\r
6744                        strncmp(message, "    ", 4) == 0) {\r
6745                 unsigned message_len;\r
6746 \r
6747                 p = message;\r
6748                 while (*p && *p == ' ') p++;\r
6749 \r
6750                 message_len = strlen( p );\r
6751 \r
6752                 /* [AS] Avoid buffer overflow */\r
6753                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {\r
6754                     strcat(thinkOutput, " ");\r
6755                     strcat(thinkOutput, p);\r
6756                 }\r
6757 \r
6758                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {\r
6759                     strcat(programStats.movelist, " ");\r
6760                     strcat(programStats.movelist, p);\r
6761                 }\r
6762 \r
6763                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||\r
6764                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6765                     DisplayMove(currentMove - 1);\r
6766                     DisplayAnalysis();\r
6767                 }\r
6768                 return;\r
6769             }\r
6770         }\r
6771         else {\r
6772             buf1[0] = NULLCHAR;\r
6773 \r
6774             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",\r
6775                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) \r
6776             {\r
6777                 ChessProgramStats cpstats;\r
6778 \r
6779                 if (plyext != ' ' && plyext != '\t') {\r
6780                     time *= 100;\r
6781                 }\r
6782 \r
6783                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
6784                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {\r
6785                     curscore = -curscore;\r
6786                 }\r
6787 \r
6788                 cpstats.depth = plylev;\r
6789                 cpstats.nodes = nodes;\r
6790                 cpstats.time = time;\r
6791                 cpstats.score = curscore;\r
6792                 cpstats.got_only_move = 0;\r
6793                 cpstats.movelist[0] = '\0';\r
6794 \r
6795                 if (buf1[0] != NULLCHAR) {\r
6796                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );\r
6797                 }\r
6798 \r
6799                 cpstats.ok_to_send = 0;\r
6800                 cpstats.line_is_book = 0;\r
6801                 cpstats.nr_moves = 0;\r
6802                 cpstats.moves_left = 0;\r
6803 \r
6804                 SendProgramStatsToFrontend( cps, &cpstats );\r
6805             }\r
6806         }\r
6807     }\r
6808 }\r
6809 \r
6810 \r
6811 /* Parse a game score from the character string "game", and\r
6812    record it as the history of the current game.  The game\r
6813    score is NOT assumed to start from the standard position. \r
6814    The display is not updated in any way.\r
6815    */\r
6816 void\r
6817 ParseGameHistory(game)\r
6818      char *game;\r
6819 {\r
6820     ChessMove moveType;\r
6821     int fromX, fromY, toX, toY, boardIndex;\r
6822     char promoChar;\r
6823     char *p, *q;\r
6824     char buf[MSG_SIZ];\r
6825 \r
6826     if (appData.debugMode)\r
6827       fprintf(debugFP, "Parsing game history: %s\n", game);\r
6828 \r
6829     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");\r
6830     gameInfo.site = StrSave(appData.icsHost);\r
6831     gameInfo.date = PGNDate();\r
6832     gameInfo.round = StrSave("-");\r
6833 \r
6834     /* Parse out names of players */\r
6835     while (*game == ' ') game++;\r
6836     p = buf;\r
6837     while (*game != ' ') *p++ = *game++;\r
6838     *p = NULLCHAR;\r
6839     gameInfo.white = StrSave(buf);\r
6840     while (*game == ' ') game++;\r
6841     p = buf;\r
6842     while (*game != ' ' && *game != '\n') *p++ = *game++;\r
6843     *p = NULLCHAR;\r
6844     gameInfo.black = StrSave(buf);\r
6845 \r
6846     /* Parse moves */\r
6847     boardIndex = blackPlaysFirst ? 1 : 0;\r
6848     yynewstr(game);\r
6849     for (;;) {\r
6850         yyboardindex = boardIndex;\r
6851         moveType = (ChessMove) yylex();\r
6852         switch (moveType) {\r
6853           case IllegalMove:             /* maybe suicide chess, etc. */\r
6854   if (appData.debugMode) {\r
6855     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);\r
6856     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6857     setbuf(debugFP, NULL);\r
6858   }\r
6859           case WhitePromotionChancellor:\r
6860           case BlackPromotionChancellor:\r
6861           case WhitePromotionArchbishop:\r
6862           case BlackPromotionArchbishop:\r
6863           case WhitePromotionQueen:\r
6864           case BlackPromotionQueen:\r
6865           case WhitePromotionRook:\r
6866           case BlackPromotionRook:\r
6867           case WhitePromotionBishop:\r
6868           case BlackPromotionBishop:\r
6869           case WhitePromotionKnight:\r
6870           case BlackPromotionKnight:\r
6871           case WhitePromotionKing:\r
6872           case BlackPromotionKing:\r
6873           case NormalMove:\r
6874           case WhiteCapturesEnPassant:\r
6875           case BlackCapturesEnPassant:\r
6876           case WhiteKingSideCastle:\r
6877           case WhiteQueenSideCastle:\r
6878           case BlackKingSideCastle:\r
6879           case BlackQueenSideCastle:\r
6880           case WhiteKingSideCastleWild:\r
6881           case WhiteQueenSideCastleWild:\r
6882           case BlackKingSideCastleWild:\r
6883           case BlackQueenSideCastleWild:\r
6884           /* PUSH Fabien */\r
6885           case WhiteHSideCastleFR:\r
6886           case WhiteASideCastleFR:\r
6887           case BlackHSideCastleFR:\r
6888           case BlackASideCastleFR:\r
6889           /* POP Fabien */\r
6890             fromX = currentMoveString[0] - AAA;\r
6891             fromY = currentMoveString[1] - ONE;\r
6892             toX = currentMoveString[2] - AAA;\r
6893             toY = currentMoveString[3] - ONE;\r
6894             promoChar = currentMoveString[4];\r
6895             break;\r
6896           case WhiteDrop:\r
6897           case BlackDrop:\r
6898             fromX = moveType == WhiteDrop ?\r
6899               (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
6900             (int) CharToPiece(ToLower(currentMoveString[0]));\r
6901             fromY = DROP_RANK;\r
6902             toX = currentMoveString[2] - AAA;\r
6903             toY = currentMoveString[3] - ONE;\r
6904             promoChar = NULLCHAR;\r
6905             break;\r
6906           case AmbiguousMove:\r
6907             /* bug? */\r
6908             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);\r
6909   if (appData.debugMode) {\r
6910     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);\r
6911     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6912     setbuf(debugFP, NULL);\r
6913   }\r
6914             DisplayError(buf, 0);\r
6915             return;\r
6916           case ImpossibleMove:\r
6917             /* bug? */\r
6918             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);\r
6919   if (appData.debugMode) {\r
6920     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);\r
6921     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6922     setbuf(debugFP, NULL);\r
6923   }\r
6924             DisplayError(buf, 0);\r
6925             return;\r
6926           case (ChessMove) 0:   /* end of file */\r
6927             if (boardIndex < backwardMostMove) {\r
6928                 /* Oops, gap.  How did that happen? */\r
6929                 DisplayError(_("Gap in move list"), 0);\r
6930                 return;\r
6931             }\r
6932             backwardMostMove =  blackPlaysFirst ? 1 : 0;\r
6933             if (boardIndex > forwardMostMove) {\r
6934                 forwardMostMove = boardIndex;\r
6935             }\r
6936             return;\r
6937           case ElapsedTime:\r
6938             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {\r
6939                 strcat(parseList[boardIndex-1], " ");\r
6940                 strcat(parseList[boardIndex-1], yy_text);\r
6941             }\r
6942             continue;\r
6943           case Comment:\r
6944           case PGNTag:\r
6945           case NAG:\r
6946           default:\r
6947             /* ignore */\r
6948             continue;\r
6949           case WhiteWins:\r
6950           case BlackWins:\r
6951           case GameIsDrawn:\r
6952           case GameUnfinished:\r
6953             if (gameMode == IcsExamining) {\r
6954                 if (boardIndex < backwardMostMove) {\r
6955                     /* Oops, gap.  How did that happen? */\r
6956                     return;\r
6957                 }\r
6958                 backwardMostMove = blackPlaysFirst ? 1 : 0;\r
6959                 return;\r
6960             }\r
6961             gameInfo.result = moveType;\r
6962             p = strchr(yy_text, '{');\r
6963             if (p == NULL) p = strchr(yy_text, '(');\r
6964             if (p == NULL) {\r
6965                 p = yy_text;\r
6966                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
6967             } else {\r
6968                 q = strchr(p, *p == '{' ? '}' : ')');\r
6969                 if (q != NULL) *q = NULLCHAR;\r
6970                 p++;\r
6971             }\r
6972             gameInfo.resultDetails = StrSave(p);\r
6973             continue;\r
6974         }\r
6975         if (boardIndex >= forwardMostMove &&\r
6976             !(gameMode == IcsObserving && ics_gamenum == -1)) {\r
6977             backwardMostMove = blackPlaysFirst ? 1 : 0;\r
6978             return;\r
6979         }\r
6980         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),\r
6981                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,\r
6982                                  parseList[boardIndex]);\r
6983         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);\r
6984         /* currentMoveString is set as a side-effect of yylex */\r
6985         strcpy(moveList[boardIndex], currentMoveString);\r
6986         strcat(moveList[boardIndex], "\n");\r
6987         boardIndex++;\r
6988         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);\r
6989         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),\r
6990                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {\r
6991           case MT_NONE:\r
6992           case MT_STALEMATE:\r
6993           default:\r
6994             break;\r
6995           case MT_CHECK:\r
6996             if(gameInfo.variant != VariantShogi)\r
6997                 strcat(parseList[boardIndex - 1], "+");\r
6998             break;\r
6999           case MT_CHECKMATE:\r
7000             strcat(parseList[boardIndex - 1], "#");\r
7001             break;\r
7002         }\r
7003     }\r
7004 }\r
7005 \r
7006 \r
7007 /* Apply a move to the given board  */\r
7008 void\r
7009 ApplyMove(fromX, fromY, toX, toY, promoChar, board)\r
7010      int fromX, fromY, toX, toY;\r
7011      int promoChar;\r
7012      Board board;\r
7013 {\r
7014   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;\r
7015 \r
7016     /* [HGM] compute & store e.p. status and castling rights for new position */\r
7017     /* if we are updating a board for which those exist (i.e. in boards[])    */\r
7018     if((p = ((int)board - (int)boards[0])/((int)boards[1]-(int)boards[0])) < MAX_MOVES && p > 0)\r
7019     { int i;\r
7020 \r
7021       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;\r
7022       oldEP = epStatus[p-1];\r
7023       epStatus[p] = EP_NONE;\r
7024 \r
7025       if( board[toY][toX] != EmptySquare ) \r
7026            epStatus[p] = EP_CAPTURE;  \r
7027 \r
7028       if( board[fromY][fromX] == WhitePawn ) {\r
7029            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers\r
7030                epStatus[p] = EP_PAWN_MOVE;\r
7031            if( toY-fromY==2) {\r
7032                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&\r
7033                         gameInfo.variant != VariantBerolina || toX < fromX)\r
7034                       epStatus[p] = toX | berolina;\r
7035                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&\r
7036                         gameInfo.variant != VariantBerolina || toX > fromX) \r
7037                       epStatus[p] = toX;\r
7038            }\r
7039       } else \r
7040       if( board[fromY][fromX] == BlackPawn ) {\r
7041            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers\r
7042                epStatus[p] = EP_PAWN_MOVE; \r
7043            if( toY-fromY== -2) {\r
7044                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&\r
7045                         gameInfo.variant != VariantBerolina || toX < fromX)\r
7046                       epStatus[p] = toX | berolina;\r
7047                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&\r
7048                         gameInfo.variant != VariantBerolina || toX > fromX) \r
7049                       epStatus[p] = toX;\r
7050            }\r
7051        }\r
7052 \r
7053        for(i=0; i<nrCastlingRights; i++) {\r
7054            castlingRights[p][i] = castlingRights[p-1][i];\r
7055            if(castlingRights[p][i] == fromX && castlingRank[i] == fromY ||\r
7056               castlingRights[p][i] == toX   && castlingRank[i] == toY   \r
7057              ) castlingRights[p][i] = -1; // revoke for moved or captured piece\r
7058        }\r
7059 \r
7060     }\r
7061 \r
7062   /* [HGM] In Shatranj and Courier all promotions are to Ferz */\r
7063   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)\r
7064        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);\r
7065          \r
7066   if (fromX == toX && fromY == toY) return;\r
7067 \r
7068   if (fromY == DROP_RANK) {\r
7069         /* must be first */\r
7070         piece = board[toY][toX] = (ChessSquare) fromX;\r
7071   } else {\r
7072      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */\r
7073      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */\r
7074      if(gameInfo.variant == VariantKnightmate)\r
7075          king += (int) WhiteUnicorn - (int) WhiteKing;\r
7076 \r
7077     /* Code added by Tord: */\r
7078     /* FRC castling assumed when king captures friendly rook. */\r
7079     if (board[fromY][fromX] == WhiteKing &&\r
7080              board[toY][toX] == WhiteRook) {\r
7081       board[fromY][fromX] = EmptySquare;\r
7082       board[toY][toX] = EmptySquare;\r
7083       if(toX > fromX) {\r
7084         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;\r
7085       } else {\r
7086         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;\r
7087       }\r
7088     } else if (board[fromY][fromX] == BlackKing &&\r
7089                board[toY][toX] == BlackRook) {\r
7090       board[fromY][fromX] = EmptySquare;\r
7091       board[toY][toX] = EmptySquare;\r
7092       if(toX > fromX) {\r
7093         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;\r
7094       } else {\r
7095         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;\r
7096       }\r
7097     /* End of code added by Tord */\r
7098 \r
7099     } else if (board[fromY][fromX] == king\r
7100         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7101         && toY == fromY && toX > fromX+1) {\r
7102         board[fromY][fromX] = EmptySquare;\r
7103         board[toY][toX] = king;\r
7104         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
7105         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
7106     } else if (board[fromY][fromX] == king\r
7107         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7108                && toY == fromY && toX < fromX-1) {\r
7109         board[fromY][fromX] = EmptySquare;\r
7110         board[toY][toX] = king;\r
7111         board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
7112         board[fromY][BOARD_LEFT] = EmptySquare;\r
7113     } else if (board[fromY][fromX] == WhitePawn\r
7114                && toY == BOARD_HEIGHT-1\r
7115                && gameInfo.variant != VariantXiangqi\r
7116                ) {\r
7117         /* white pawn promotion */\r
7118         board[toY][toX] = CharToPiece(ToUpper(promoChar));\r
7119         if (board[toY][toX] == EmptySquare) {\r
7120             board[toY][toX] = WhiteQueen;\r
7121         }\r
7122         if(gameInfo.variant==VariantBughouse ||\r
7123            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
7124             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
7125         board[fromY][fromX] = EmptySquare;\r
7126     } else if ((fromY == BOARD_HEIGHT-4)\r
7127                && (toX != fromX)\r
7128                && gameInfo.variant != VariantXiangqi\r
7129                && gameInfo.variant != VariantBerolina\r
7130                && (board[fromY][fromX] == WhitePawn)\r
7131                && (board[toY][toX] == EmptySquare)) {\r
7132         board[fromY][fromX] = EmptySquare;\r
7133         board[toY][toX] = WhitePawn;\r
7134         captured = board[toY - 1][toX];\r
7135         board[toY - 1][toX] = EmptySquare;\r
7136     } else if ((fromY == BOARD_HEIGHT-4)\r
7137                && (toX == fromX)\r
7138                && gameInfo.variant == VariantBerolina\r
7139                && (board[fromY][fromX] == WhitePawn)\r
7140                && (board[toY][toX] == EmptySquare)) {\r
7141         board[fromY][fromX] = EmptySquare;\r
7142         board[toY][toX] = WhitePawn;\r
7143         if(oldEP & EP_BEROLIN_A) {\r
7144                 captured = board[fromY][fromX-1];\r
7145                 board[fromY][fromX-1] = EmptySquare;\r
7146         }else{  captured = board[fromY][fromX+1];\r
7147                 board[fromY][fromX+1] = EmptySquare;\r
7148         }\r
7149     } else if (board[fromY][fromX] == king\r
7150         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7151                && toY == fromY && toX > fromX+1) {\r
7152         board[fromY][fromX] = EmptySquare;\r
7153         board[toY][toX] = king;\r
7154         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
7155         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
7156     } else if (board[fromY][fromX] == king\r
7157         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7158                && toY == fromY && toX < fromX-1) {\r
7159         board[fromY][fromX] = EmptySquare;\r
7160         board[toY][toX] = king;\r
7161         board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
7162         board[fromY][BOARD_LEFT] = EmptySquare;\r
7163     } else if (fromY == 7 && fromX == 3\r
7164                && board[fromY][fromX] == BlackKing\r
7165                && toY == 7 && toX == 5) {\r
7166         board[fromY][fromX] = EmptySquare;\r
7167         board[toY][toX] = BlackKing;\r
7168         board[fromY][7] = EmptySquare;\r
7169         board[toY][4] = BlackRook;\r
7170     } else if (fromY == 7 && fromX == 3\r
7171                && board[fromY][fromX] == BlackKing\r
7172                && toY == 7 && toX == 1) {\r
7173         board[fromY][fromX] = EmptySquare;\r
7174         board[toY][toX] = BlackKing;\r
7175         board[fromY][0] = EmptySquare;\r
7176         board[toY][2] = BlackRook;\r
7177     } else if (board[fromY][fromX] == BlackPawn\r
7178                && toY == 0\r
7179                && gameInfo.variant != VariantXiangqi\r
7180                ) {\r
7181         /* black pawn promotion */\r
7182         board[0][toX] = CharToPiece(ToLower(promoChar));\r
7183         if (board[0][toX] == EmptySquare) {\r
7184             board[0][toX] = BlackQueen;\r
7185         }\r
7186         if(gameInfo.variant==VariantBughouse ||\r
7187            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
7188             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
7189         board[fromY][fromX] = EmptySquare;\r
7190     } else if ((fromY == 3)\r
7191                && (toX != fromX)\r
7192                && gameInfo.variant != VariantXiangqi\r
7193                && gameInfo.variant != VariantBerolina\r
7194                && (board[fromY][fromX] == BlackPawn)\r
7195                && (board[toY][toX] == EmptySquare)) {\r
7196         board[fromY][fromX] = EmptySquare;\r
7197         board[toY][toX] = BlackPawn;\r
7198         captured = board[toY + 1][toX];\r
7199         board[toY + 1][toX] = EmptySquare;\r
7200     } else if ((fromY == 3)\r
7201                && (toX == fromX)\r
7202                && gameInfo.variant == VariantBerolina\r
7203                && (board[fromY][fromX] == BlackPawn)\r
7204                && (board[toY][toX] == EmptySquare)) {\r
7205         board[fromY][fromX] = EmptySquare;\r
7206         board[toY][toX] = BlackPawn;\r
7207         if(oldEP & EP_BEROLIN_A) {\r
7208                 captured = board[fromY][fromX-1];\r
7209                 board[fromY][fromX-1] = EmptySquare;\r
7210         }else{  captured = board[fromY][fromX+1];\r
7211                 board[fromY][fromX+1] = EmptySquare;\r
7212         }\r
7213     } else {\r
7214         board[toY][toX] = board[fromY][fromX];\r
7215         board[fromY][fromX] = EmptySquare;\r
7216     }\r
7217 \r
7218     /* [HGM] now we promote for Shogi, if needed */\r
7219     if(gameInfo.variant == VariantShogi && promoChar == 'q')\r
7220         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
7221   }\r
7222 \r
7223     if (gameInfo.holdingsWidth != 0) {\r
7224 \r
7225       /* !!A lot more code needs to be written to support holdings  */\r
7226       /* [HGM] OK, so I have written it. Holdings are stored in the */\r
7227       /* penultimate board files, so they are automaticlly stored   */\r
7228       /* in the game history.                                       */\r
7229       if (fromY == DROP_RANK) {\r
7230         /* Delete from holdings, by decreasing count */\r
7231         /* and erasing image if necessary            */\r
7232         p = (int) fromX;\r
7233         if(p < (int) BlackPawn) { /* white drop */\r
7234              p -= (int)WhitePawn;\r
7235              if(p >= gameInfo.holdingsSize) p = 0;\r
7236              if(--board[p][BOARD_WIDTH-2] == 0)\r
7237                   board[p][BOARD_WIDTH-1] = EmptySquare;\r
7238         } else {                  /* black drop */\r
7239              p -= (int)BlackPawn;\r
7240              if(p >= gameInfo.holdingsSize) p = 0;\r
7241              if(--board[BOARD_HEIGHT-1-p][1] == 0)\r
7242                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;\r
7243         }\r
7244       }\r
7245       if (captured != EmptySquare && gameInfo.holdingsSize > 0\r
7246           && gameInfo.variant != VariantBughouse        ) {\r
7247         /* [HGM] holdings: Add to holdings, if holdings exist */\r
7248         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { \r
7249                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip\r
7250                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;\r
7251         }\r
7252         p = (int) captured;\r
7253         if (p >= (int) BlackPawn) {\r
7254           p -= (int)BlackPawn;\r
7255           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
7256                   /* in Shogi restore piece to its original  first */\r
7257                   captured = (ChessSquare) (DEMOTED captured);\r
7258                   p = DEMOTED p;\r
7259           }\r
7260           p = PieceToNumber((ChessSquare)p);\r
7261           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }\r
7262           board[p][BOARD_WIDTH-2]++;\r
7263           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;\r
7264         } else {\r
7265           p -= (int)WhitePawn;\r
7266           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
7267                   captured = (ChessSquare) (DEMOTED captured);\r
7268                   p = DEMOTED p;\r
7269           }\r
7270           p = PieceToNumber((ChessSquare)p);\r
7271           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }\r
7272           board[BOARD_HEIGHT-1-p][1]++;\r
7273           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;\r
7274         }\r
7275       }\r
7276 \r
7277     } else if (gameInfo.variant == VariantAtomic) {\r
7278       if (captured != EmptySquare) {\r
7279         int y, x;\r
7280         for (y = toY-1; y <= toY+1; y++) {\r
7281           for (x = toX-1; x <= toX+1; x++) {\r
7282             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&\r
7283                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {\r
7284               board[y][x] = EmptySquare;\r
7285             }\r
7286           }\r
7287         }\r
7288         board[toY][toX] = EmptySquare;\r
7289       }\r
7290     }\r
7291     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {\r
7292         /* [HGM] Shogi promotions */\r
7293         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
7294     }\r
7295 \r
7296 }\r
7297 \r
7298 /* Updates forwardMostMove */\r
7299 void\r
7300 MakeMove(fromX, fromY, toX, toY, promoChar)\r
7301      int fromX, fromY, toX, toY;\r
7302      int promoChar;\r
7303 {\r
7304 //    forwardMostMove++; // [HGM] bare: moved downstream\r
7305 \r
7306     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */\r
7307         int timeLeft; static int lastLoadFlag=0; int king, piece;\r
7308         piece = boards[forwardMostMove][fromY][fromX];\r
7309         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;\r
7310         if(gameInfo.variant == VariantKnightmate)\r
7311             king += (int) WhiteUnicorn - (int) WhiteKing;\r
7312         if(forwardMostMove == 0) {\r
7313             if(blackPlaysFirst) \r
7314                 fprintf(serverMoves, "%s;", second.tidy);\r
7315             fprintf(serverMoves, "%s;", first.tidy);\r
7316             if(!blackPlaysFirst) \r
7317                 fprintf(serverMoves, "%s;", second.tidy);\r
7318         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");\r
7319         lastLoadFlag = loadFlag;\r
7320         // print base move\r
7321         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);\r
7322         // print castling suffix\r
7323         if( toY == fromY && piece == king ) {\r
7324             if(toX-fromX > 1)\r
7325                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);\r
7326             if(fromX-toX >1)\r
7327                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);\r
7328         }\r
7329         // e.p. suffix\r
7330         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||\r
7331              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&\r
7332              boards[forwardMostMove][toY][toX] == EmptySquare\r
7333              && fromX != toX )\r
7334                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);\r
7335         // promotion suffix\r
7336         if(promoChar != NULLCHAR)\r
7337                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);\r
7338         if(!loadFlag) {\r
7339             fprintf(serverMoves, "/%d/%d",\r
7340                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);\r
7341             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;\r
7342             else                      timeLeft = blackTimeRemaining/1000;\r
7343             fprintf(serverMoves, "/%d", timeLeft);\r
7344         }\r
7345         fflush(serverMoves);\r
7346     }\r
7347 \r
7348     if (forwardMostMove+1 >= MAX_MOVES) {\r
7349       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),\r
7350                         0, 1);\r
7351       return;\r
7352     }\r
7353     SwitchClocks();\r
7354     timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;\r
7355     timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;\r
7356     if (commentList[forwardMostMove+1] != NULL) {\r
7357         free(commentList[forwardMostMove+1]);\r
7358         commentList[forwardMostMove+1] = NULL;\r
7359     }\r
7360     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);\r
7361     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);\r
7362     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board\r
7363     gameInfo.result = GameUnfinished;\r
7364     if (gameInfo.resultDetails != NULL) {\r
7365         free(gameInfo.resultDetails);\r
7366         gameInfo.resultDetails = NULL;\r
7367     }\r
7368     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,\r
7369                               moveList[forwardMostMove - 1]);\r
7370     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],\r
7371                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,\r
7372                              fromY, fromX, toY, toX, promoChar,\r
7373                              parseList[forwardMostMove - 1]);\r
7374     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
7375                        epStatus[forwardMostMove], /* [HGM] use true e.p. */\r
7376                             castlingRights[forwardMostMove]) ) {\r
7377       case MT_NONE:\r
7378       case MT_STALEMATE:\r
7379       default:\r
7380         break;\r
7381       case MT_CHECK:\r
7382         if(gameInfo.variant != VariantShogi)\r
7383             strcat(parseList[forwardMostMove - 1], "+");\r
7384         break;\r
7385       case MT_CHECKMATE:\r
7386         strcat(parseList[forwardMostMove - 1], "#");\r
7387         break;\r
7388     }\r
7389     if (appData.debugMode) {\r
7390         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);\r
7391     }\r
7392 \r
7393 }\r
7394 \r
7395 /* Updates currentMove if not pausing */\r
7396 void\r
7397 ShowMove(fromX, fromY, toX, toY)\r
7398 {\r
7399     int instant = (gameMode == PlayFromGameFile) ?\r
7400         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;\r
7401     if(appData.noGUI) return;\r
7402     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
7403         if (!instant) {\r
7404             if (forwardMostMove == currentMove + 1) {\r
7405                 AnimateMove(boards[forwardMostMove - 1],\r
7406                             fromX, fromY, toX, toY);\r
7407             }\r
7408             if (appData.highlightLastMove) {\r
7409                 SetHighlights(fromX, fromY, toX, toY);\r
7410             }\r
7411         }\r
7412         currentMove = forwardMostMove;\r
7413     }\r
7414 \r
7415     if (instant) return;\r
7416 \r
7417     DisplayMove(currentMove - 1);\r
7418     DrawPosition(FALSE, boards[currentMove]);\r
7419     DisplayBothClocks();\r
7420     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
7421 }\r
7422 \r
7423 void SendEgtPath(ChessProgramState *cps)\r
7424 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */\r
7425         char buf[MSG_SIZ], name[MSG_SIZ], *p;\r
7426 \r
7427         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;\r
7428 \r
7429         while(*p) {\r
7430             char c, *q = name+1, *r, *s;\r
7431 \r
7432             name[0] = ','; // extract next format name from feature and copy with prefixed ','\r
7433             while(*p && *p != ',') *q++ = *p++;\r
7434             *q++ = ':'; *q = 0;\r
7435             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && \r
7436                 strcmp(name, ",nalimov:") == 0 ) {\r
7437                 // take nalimov path from the menu-changeable option first, if it is defined\r
7438                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);\r
7439                 SendToProgram(buf,cps);     // send egtbpath command for nalimov\r
7440             } else\r
7441             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||\r
7442                 (s = StrStr(appData.egtFormats, name)) != NULL) {\r
7443                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma\r
7444                 s = r = StrStr(s, ":") + 1; // beginning of path info\r
7445                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string\r
7446                 c = *r; *r = 0;             // temporarily null-terminate path info\r
7447                     *--q = 0;               // strip of trailig ':' from name\r
7448                     sprintf(buf, "egtbpath %s %s\n", name+1, s);\r
7449                 *r = c;\r
7450                 SendToProgram(buf,cps);     // send egtbpath command for this format\r
7451             }\r
7452             if(*p == ',') p++; // read away comma to position for next format name\r
7453         }\r
7454 }\r
7455 \r
7456 void\r
7457 InitChessProgram(cps, setup)\r
7458      ChessProgramState *cps;\r
7459      int setup; /* [HGM] needed to setup FRC opening position */\r
7460 {\r
7461     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;\r
7462     if (appData.noChessProgram) return;\r
7463     hintRequested = FALSE;\r
7464     bookRequested = FALSE;\r
7465 \r
7466     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */\r
7467     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */\r
7468     if(cps->memSize) { /* [HGM] memory */\r
7469         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);\r
7470         SendToProgram(buf, cps);\r
7471     }\r
7472     SendEgtPath(cps); /* [HGM] EGT */\r
7473     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */\r
7474         sprintf(buf, "cores %d\n", appData.smpCores);\r
7475         SendToProgram(buf, cps);\r
7476     }\r
7477 \r
7478     SendToProgram(cps->initString, cps);\r
7479     if (gameInfo.variant != VariantNormal &&\r
7480         gameInfo.variant != VariantLoadable\r
7481         /* [HGM] also send variant if board size non-standard */\r
7482         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0\r
7483                                             ) {\r
7484       char *v = VariantName(gameInfo.variant);\r
7485       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {\r
7486         /* [HGM] in protocol 1 we have to assume all variants valid */\r
7487         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);\r
7488         DisplayFatalError(buf, 0, 1);\r
7489         return;\r
7490       }\r
7491 \r
7492       /* [HGM] make prefix for non-standard board size. Awkward testing... */\r
7493       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7494       if( gameInfo.variant == VariantXiangqi )\r
7495            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;\r
7496       if( gameInfo.variant == VariantShogi )\r
7497            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;\r
7498       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )\r
7499            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;\r
7500       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || \r
7501                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )\r
7502            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7503       if( gameInfo.variant == VariantCourier )\r
7504            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7505       if( gameInfo.variant == VariantSuper )\r
7506            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;\r
7507       if( gameInfo.variant == VariantGreat )\r
7508            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;\r
7509 \r
7510       if(overruled) {\r
7511            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, \r
7512                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name\r
7513            /* [HGM] varsize: try first if this defiant size variant is specifically known */\r
7514            if(StrStr(cps->variants, b) == NULL) { \r
7515                // specific sized variant not known, check if general sizing allowed\r
7516                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best\r
7517                    if(StrStr(cps->variants, "boardsize") == NULL) {\r
7518                        sprintf(buf, "Board size %dx%d+%d not supported by %s",\r
7519                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);\r
7520                        DisplayFatalError(buf, 0, 1);\r
7521                        return;\r
7522                    }\r
7523                    /* [HGM] here we really should compare with the maximum supported board size */\r
7524                }\r
7525            }\r
7526       } else sprintf(b, "%s", VariantName(gameInfo.variant));\r
7527       sprintf(buf, "variant %s\n", b);\r
7528       SendToProgram(buf, cps);\r
7529     }\r
7530     currentlyInitializedVariant = gameInfo.variant;\r
7531 \r
7532     /* [HGM] send opening position in FRC to first engine */\r
7533     if(setup) {\r
7534           SendToProgram("force\n", cps);\r
7535           SendBoard(cps, 0);\r
7536           /* engine is now in force mode! Set flag to wake it up after first move. */\r
7537           setboardSpoiledMachineBlack = 1;\r
7538     }\r
7539 \r
7540     if (cps->sendICS) {\r
7541       sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");\r
7542       SendToProgram(buf, cps);\r
7543     }\r
7544     cps->maybeThinking = FALSE;\r
7545     cps->offeredDraw = 0;\r
7546     if (!appData.icsActive) {\r
7547         SendTimeControl(cps, movesPerSession, timeControl,\r
7548                         timeIncrement, appData.searchDepth,\r
7549                         searchTime);\r
7550     }\r
7551     if (appData.showThinking \r
7552         // [HGM] thinking: four options require thinking output to be sent\r
7553         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()\r
7554                                 ) {\r
7555         SendToProgram("post\n", cps);\r
7556     }\r
7557     SendToProgram("hard\n", cps);\r
7558     if (!appData.ponderNextMove) {\r
7559         /* Warning: "easy" is a toggle in GNU Chess, so don't send\r
7560            it without being sure what state we are in first.  "hard"\r
7561            is not a toggle, so that one is OK.\r
7562          */\r
7563         SendToProgram("easy\n", cps);\r
7564     }\r
7565     if (cps->usePing) {\r
7566       sprintf(buf, "ping %d\n", ++cps->lastPing);\r
7567       SendToProgram(buf, cps);\r
7568     }\r
7569     cps->initDone = TRUE;\r
7570 }   \r
7571 \r
7572 \r
7573 void\r
7574 StartChessProgram(cps)\r
7575      ChessProgramState *cps;\r
7576 {\r
7577     char buf[MSG_SIZ];\r
7578     int err;\r
7579 \r
7580     if (appData.noChessProgram) return;\r
7581     cps->initDone = FALSE;\r
7582 \r
7583     if (strcmp(cps->host, "localhost") == 0) {\r
7584         err = StartChildProcess(cps->program, cps->dir, &cps->pr);\r
7585     } else if (*appData.remoteShell == NULLCHAR) {\r
7586         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);\r
7587     } else {\r
7588         if (*appData.remoteUser == NULLCHAR) {\r
7589             sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,\r
7590                     cps->program);\r
7591         } else {\r
7592             sprintf(buf, "%s %s -l %s %s", appData.remoteShell,\r
7593                     cps->host, appData.remoteUser, cps->program);\r
7594         }\r
7595         err = StartChildProcess(buf, "", &cps->pr);\r
7596     }\r
7597     \r
7598     if (err != 0) {\r
7599         sprintf(buf, _("Startup failure on '%s'"), cps->program);\r
7600         DisplayFatalError(buf, err, 1);\r
7601         cps->pr = NoProc;\r
7602         cps->isr = NULL;\r
7603         return;\r
7604     }\r
7605     \r
7606     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);\r
7607     if (cps->protocolVersion > 1) {\r
7608       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);\r
7609       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options\r
7610       cps->comboCnt = 0;  //                and values of combo boxes\r
7611       SendToProgram(buf, cps);\r
7612     } else {\r
7613       SendToProgram("xboard\n", cps);\r
7614     }\r
7615 }\r
7616 \r
7617 \r
7618 void\r
7619 TwoMachinesEventIfReady P((void))\r
7620 {\r
7621   if (first.lastPing != first.lastPong) {\r
7622     DisplayMessage("", _("Waiting for first chess program"));\r
7623     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000\r
7624     return;\r
7625   }\r
7626   if (second.lastPing != second.lastPong) {\r
7627     DisplayMessage("", _("Waiting for second chess program"));\r
7628     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000\r
7629     return;\r
7630   }\r
7631   ThawUI();\r
7632   TwoMachinesEvent();\r
7633 }\r
7634 \r
7635 void\r
7636 NextMatchGame P((void))\r
7637 {\r
7638     int index; /* [HGM] autoinc: step lod index during match */\r
7639     Reset(FALSE, TRUE);\r
7640     if (*appData.loadGameFile != NULLCHAR) {\r
7641         index = appData.loadGameIndex;\r
7642         if(index < 0) { // [HGM] autoinc\r
7643             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;\r
7644             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;\r
7645         } \r
7646         LoadGameFromFile(appData.loadGameFile,\r
7647                          index,\r
7648                          appData.loadGameFile, FALSE);\r
7649     } else if (*appData.loadPositionFile != NULLCHAR) {\r
7650         index = appData.loadPositionIndex;\r
7651         if(index < 0) { // [HGM] autoinc\r
7652             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;\r
7653             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;\r
7654         } \r
7655         LoadPositionFromFile(appData.loadPositionFile,\r
7656                              index,\r
7657                              appData.loadPositionFile);\r
7658     }\r
7659     TwoMachinesEventIfReady();\r
7660 }\r
7661 \r
7662 void UserAdjudicationEvent( int result )\r
7663 {\r
7664     ChessMove gameResult = GameIsDrawn;\r
7665 \r
7666     if( result > 0 ) {\r
7667         gameResult = WhiteWins;\r
7668     }\r
7669     else if( result < 0 ) {\r
7670         gameResult = BlackWins;\r
7671     }\r
7672 \r
7673     if( gameMode == TwoMachinesPlay ) {\r
7674         GameEnds( gameResult, "User adjudication", GE_XBOARD );\r
7675     }\r
7676 }\r
7677 \r
7678 \r
7679 void\r
7680 GameEnds(result, resultDetails, whosays)\r
7681      ChessMove result;\r
7682      char *resultDetails;\r
7683      int whosays;\r
7684 {\r
7685     GameMode nextGameMode;\r
7686     int isIcsGame;\r
7687     char buf[MSG_SIZ];\r
7688 \r
7689     if(endingGame) return; /* [HGM] crash: forbid recursion */\r
7690     endingGame = 1;\r
7691 \r
7692     if (appData.debugMode) {\r
7693       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",\r
7694               result, resultDetails ? resultDetails : "(null)", whosays);\r
7695     }\r
7696 \r
7697     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {\r
7698         /* If we are playing on ICS, the server decides when the\r
7699            game is over, but the engine can offer to draw, claim \r
7700            a draw, or resign. \r
7701          */\r
7702 #if ZIPPY\r
7703         if (appData.zippyPlay && first.initDone) {\r
7704             if (result == GameIsDrawn) {\r
7705                 /* In case draw still needs to be claimed */\r
7706                 SendToICS(ics_prefix);\r
7707                 SendToICS("draw\n");\r
7708             } else if (StrCaseStr(resultDetails, "resign")) {\r
7709                 SendToICS(ics_prefix);\r
7710                 SendToICS("resign\n");\r
7711             }\r
7712         }\r
7713 #endif\r
7714         endingGame = 0; /* [HGM] crash */\r
7715         return;\r
7716     }\r
7717 \r
7718     /* If we're loading the game from a file, stop */\r
7719     if (whosays == GE_FILE) {\r
7720       (void) StopLoadGameTimer();\r
7721       gameFileFP = NULL;\r
7722     }\r
7723 \r
7724     /* Cancel draw offers */\r
7725     first.offeredDraw = second.offeredDraw = 0;\r
7726 \r
7727     /* If this is an ICS game, only ICS can really say it's done;\r
7728        if not, anyone can. */\r
7729     isIcsGame = (gameMode == IcsPlayingWhite || \r
7730                  gameMode == IcsPlayingBlack || \r
7731                  gameMode == IcsObserving    || \r
7732                  gameMode == IcsExamining);\r
7733 \r
7734     if (!isIcsGame || whosays == GE_ICS) {\r
7735         /* OK -- not an ICS game, or ICS said it was done */\r
7736         StopClocks();\r
7737         if (!isIcsGame && !appData.noChessProgram) \r
7738           SetUserThinkingEnables();\r
7739     \r
7740         /* [HGM] if a machine claims the game end we verify this claim */\r
7741         if(gameMode == TwoMachinesPlay && appData.testClaims) {\r
7742             if(appData.testLegality && whosays >= GE_ENGINE1 ) {\r
7743                 char claimer;\r
7744                 ChessMove trueResult = (ChessMove) -1;\r
7745 \r
7746                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */\r
7747                                             first.twoMachinesColor[0] :\r
7748                                             second.twoMachinesColor[0] ;\r
7749 \r
7750                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first\r
7751                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {\r
7752                     /* [HGM] verify: engine mate claims accepted if they were flagged */\r
7753                     trueResult = WhiteOnMove(forwardMostMove) != (gameInfo.variant == VariantLosers)\r
7754                         ? BlackWins : WhiteWins; // [HGM] losers: reverse the result in VariantLosers!\r
7755                 } else\r
7756                 if(epStatus[forwardMostMove] == EP_STALEMATE) {\r
7757                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE\r
7758                     if(gameInfo.variant == VariantGiveaway || gameInfo.variant == VariantSuicide || \r
7759                        gameInfo.variant == VariantLosers)  // [HGM] losers: in giveaway variants stalemate wins\r
7760                         trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;\r
7761                 }\r
7762 \r
7763                 // now verify win claims, but not in drop games, as we don't understand those yet\r
7764                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper\r
7765                                                  || gameInfo.variant == VariantGreat) &&\r
7766                     (result == WhiteWins && claimer == 'w' ||\r
7767                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win\r
7768                       if (appData.debugMode) {\r
7769                         fprintf(debugFP, "result=%d sp=%d move=%d\n",\r
7770                                 result, epStatus[forwardMostMove], forwardMostMove);\r
7771                       }\r
7772                       if(result != trueResult) {\r
7773                               sprintf(buf, "False win claim: '%s'", resultDetails);\r
7774                               result = claimer == 'w' ? BlackWins : WhiteWins;\r
7775                               resultDetails = buf;\r
7776                       }\r
7777                 } else\r
7778                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS\r
7779                     && (forwardMostMove <= backwardMostMove ||\r
7780                         epStatus[forwardMostMove-1] > EP_DRAWS ||\r
7781                         (claimer=='b')==(forwardMostMove&1))\r
7782                                                                                   ) {\r
7783                       /* [HGM] verify: draws that were not flagged are false claims */\r
7784                       sprintf(buf, "False draw claim: '%s'", resultDetails);\r
7785                       result = claimer == 'w' ? BlackWins : WhiteWins;\r
7786                       resultDetails = buf;\r
7787                 }\r
7788                 /* (Claiming a loss is accepted no questions asked!) */\r
7789             }\r
7790             /* [HGM] bare: don't allow bare King to win */\r
7791             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)\r
7792                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway \r
7793                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...\r
7794                && result != GameIsDrawn)\r
7795             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);\r
7796                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {\r
7797                         int p = (int)boards[forwardMostMove][i][j] - color;\r
7798                         if(p >= 0 && p <= (int)WhiteKing) k++;\r
7799                 }\r
7800                 if (appData.debugMode) {\r
7801                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",\r
7802                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);\r
7803                 }\r
7804                 if(k <= 1) {\r
7805                         result = GameIsDrawn;\r
7806                         sprintf(buf, "%s but bare king", resultDetails);\r
7807                         resultDetails = buf;\r
7808                 }\r
7809             }\r
7810         }\r
7811 \r
7812 \r
7813         if(serverMoves != NULL && !loadFlag) { char c = '=';\r
7814             if(result==WhiteWins) c = '+';\r
7815             if(result==BlackWins) c = '-';\r
7816             if(resultDetails != NULL)\r
7817                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);\r
7818         }\r
7819         if (resultDetails != NULL) {\r
7820             gameInfo.result = result;\r
7821             gameInfo.resultDetails = StrSave(resultDetails);\r
7822 \r
7823             /* display last move only if game was not loaded from file */\r
7824             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))\r
7825                 DisplayMove(currentMove - 1);\r
7826     \r
7827             if (forwardMostMove != 0) {\r
7828                 if (gameMode != PlayFromGameFile && gameMode != EditGame) {\r
7829                     if (*appData.saveGameFile != NULLCHAR) {\r
7830                         SaveGameToFile(appData.saveGameFile, TRUE);\r
7831                     } else if (appData.autoSaveGames) {\r
7832                         AutoSaveGame();\r
7833                     }\r
7834                     if (*appData.savePositionFile != NULLCHAR) {\r
7835                         SavePositionToFile(appData.savePositionFile);\r
7836                     }\r
7837                 }\r
7838             }\r
7839 \r
7840             /* Tell program how game ended in case it is learning */\r
7841             /* [HGM] Moved this to after saving the PGN, just in case */\r
7842             /* engine died and we got here through time loss. In that */\r
7843             /* case we will get a fatal error writing the pipe, which */\r
7844             /* would otherwise lose us the PGN.                       */\r
7845             /* [HGM] crash: not needed anymore, but doesn't hurt;     */\r
7846             /* output during GameEnds should never be fatal anymore   */\r
7847             if (gameMode == MachinePlaysWhite ||\r
7848                 gameMode == MachinePlaysBlack ||\r
7849                 gameMode == TwoMachinesPlay ||\r
7850                 gameMode == IcsPlayingWhite ||\r
7851                 gameMode == IcsPlayingBlack ||\r
7852                 gameMode == BeginningOfGame) {\r
7853                 char buf[MSG_SIZ];\r
7854                 sprintf(buf, "result %s {%s}\n", PGNResult(result),\r
7855                         resultDetails);\r
7856                 if (first.pr != NoProc) {\r
7857                     SendToProgram(buf, &first);\r
7858                 }\r
7859                 if (second.pr != NoProc &&\r
7860                     gameMode == TwoMachinesPlay) {\r
7861                     SendToProgram(buf, &second);\r
7862                 }\r
7863             }\r
7864         }\r
7865 \r
7866         if (appData.icsActive) {\r
7867             if (appData.quietPlay &&\r
7868                 (gameMode == IcsPlayingWhite ||\r
7869                  gameMode == IcsPlayingBlack)) {\r
7870                 SendToICS(ics_prefix);\r
7871                 SendToICS("set shout 1\n");\r
7872             }\r
7873             nextGameMode = IcsIdle;\r
7874             ics_user_moved = FALSE;\r
7875             /* clean up premove.  It's ugly when the game has ended and the\r
7876              * premove highlights are still on the board.\r
7877              */\r
7878             if (gotPremove) {\r
7879               gotPremove = FALSE;\r
7880               ClearPremoveHighlights();\r
7881               DrawPosition(FALSE, boards[currentMove]);\r
7882             }\r
7883             if (whosays == GE_ICS) {\r
7884                 switch (result) {\r
7885                 case WhiteWins:\r
7886                     if (gameMode == IcsPlayingWhite)\r
7887                         PlayIcsWinSound();\r
7888                     else if(gameMode == IcsPlayingBlack)\r
7889                         PlayIcsLossSound();\r
7890                     break;\r
7891                 case BlackWins:\r
7892                     if (gameMode == IcsPlayingBlack)\r
7893                         PlayIcsWinSound();\r
7894                     else if(gameMode == IcsPlayingWhite)\r
7895                         PlayIcsLossSound();\r
7896                     break;\r
7897                 case GameIsDrawn:\r
7898                     PlayIcsDrawSound();\r
7899                     break;\r
7900                 default:\r
7901                     PlayIcsUnfinishedSound();\r
7902                 }\r
7903             }\r
7904         } else if (gameMode == EditGame ||\r
7905                    gameMode == PlayFromGameFile || \r
7906                    gameMode == AnalyzeMode || \r
7907                    gameMode == AnalyzeFile) {\r
7908             nextGameMode = gameMode;\r
7909         } else {\r
7910             nextGameMode = EndOfGame;\r
7911         }\r
7912         pausing = FALSE;\r
7913         ModeHighlight();\r
7914     } else {\r
7915         nextGameMode = gameMode;\r
7916     }\r
7917 \r
7918     if (appData.noChessProgram) {\r
7919         gameMode = nextGameMode;\r
7920         ModeHighlight();\r
7921         endingGame = 0; /* [HGM] crash */\r
7922         return;\r
7923     }\r
7924 \r
7925     if (first.reuse) {\r
7926         /* Put first chess program into idle state */\r
7927         if (first.pr != NoProc &&\r
7928             (gameMode == MachinePlaysWhite ||\r
7929              gameMode == MachinePlaysBlack ||\r
7930              gameMode == TwoMachinesPlay ||\r
7931              gameMode == IcsPlayingWhite ||\r
7932              gameMode == IcsPlayingBlack ||\r
7933              gameMode == BeginningOfGame)) {\r
7934             SendToProgram("force\n", &first);\r
7935             if (first.usePing) {\r
7936               char buf[MSG_SIZ];\r
7937               sprintf(buf, "ping %d\n", ++first.lastPing);\r
7938               SendToProgram(buf, &first);\r
7939             }\r
7940         }\r
7941     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
7942         /* Kill off first chess program */\r
7943         if (first.isr != NULL)\r
7944           RemoveInputSource(first.isr);\r
7945         first.isr = NULL;\r
7946     \r
7947         if (first.pr != NoProc) {\r
7948             ExitAnalyzeMode();\r
7949             DoSleep( appData.delayBeforeQuit );\r
7950             SendToProgram("quit\n", &first);\r
7951             DoSleep( appData.delayAfterQuit );\r
7952             DestroyChildProcess(first.pr, first.useSigterm);\r
7953         }\r
7954         first.pr = NoProc;\r
7955     }\r
7956     if (second.reuse) {\r
7957         /* Put second chess program into idle state */\r
7958         if (second.pr != NoProc &&\r
7959             gameMode == TwoMachinesPlay) {\r
7960             SendToProgram("force\n", &second);\r
7961             if (second.usePing) {\r
7962               char buf[MSG_SIZ];\r
7963               sprintf(buf, "ping %d\n", ++second.lastPing);\r
7964               SendToProgram(buf, &second);\r
7965             }\r
7966         }\r
7967     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
7968         /* Kill off second chess program */\r
7969         if (second.isr != NULL)\r
7970           RemoveInputSource(second.isr);\r
7971         second.isr = NULL;\r
7972     \r
7973         if (second.pr != NoProc) {\r
7974             DoSleep( appData.delayBeforeQuit );\r
7975             SendToProgram("quit\n", &second);\r
7976             DoSleep( appData.delayAfterQuit );\r
7977             DestroyChildProcess(second.pr, second.useSigterm);\r
7978         }\r
7979         second.pr = NoProc;\r
7980     }\r
7981 \r
7982     if (matchMode && gameMode == TwoMachinesPlay) {\r
7983         switch (result) {\r
7984         case WhiteWins:\r
7985           if (first.twoMachinesColor[0] == 'w') {\r
7986             first.matchWins++;\r
7987           } else {\r
7988             second.matchWins++;\r
7989           }\r
7990           break;\r
7991         case BlackWins:\r
7992           if (first.twoMachinesColor[0] == 'b') {\r
7993             first.matchWins++;\r
7994           } else {\r
7995             second.matchWins++;\r
7996           }\r
7997           break;\r
7998         default:\r
7999           break;\r
8000         }\r
8001         if (matchGame < appData.matchGames) {\r
8002             char *tmp;\r
8003             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */\r
8004                 tmp = first.twoMachinesColor;\r
8005                 first.twoMachinesColor = second.twoMachinesColor;\r
8006                 second.twoMachinesColor = tmp;\r
8007             }\r
8008             gameMode = nextGameMode;\r
8009             matchGame++;\r
8010             if(appData.matchPause>10000 || appData.matchPause<10)\r
8011                 appData.matchPause = 10000; /* [HGM] make pause adjustable */\r
8012             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);\r
8013             endingGame = 0; /* [HGM] crash */\r
8014             return;\r
8015         } else {\r
8016             char buf[MSG_SIZ];\r
8017             gameMode = nextGameMode;\r
8018             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),\r
8019                     first.tidy, second.tidy,\r
8020                     first.matchWins, second.matchWins,\r
8021                     appData.matchGames - (first.matchWins + second.matchWins));\r
8022             DisplayFatalError(buf, 0, 0);\r
8023         }\r
8024     }\r
8025     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&\r
8026         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))\r
8027       ExitAnalyzeMode();\r
8028     gameMode = nextGameMode;\r
8029     ModeHighlight();\r
8030     endingGame = 0;  /* [HGM] crash */\r
8031 }\r
8032 \r
8033 /* Assumes program was just initialized (initString sent).\r
8034    Leaves program in force mode. */\r
8035 void\r
8036 FeedMovesToProgram(cps, upto) \r
8037      ChessProgramState *cps;\r
8038      int upto;\r
8039 {\r
8040     int i;\r
8041     \r
8042     if (appData.debugMode)\r
8043       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",\r
8044               startedFromSetupPosition ? "position and " : "",\r
8045               backwardMostMove, upto, cps->which);\r
8046     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];\r
8047         // [HGM] variantswitch: make engine aware of new variant\r
8048         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)\r
8049                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!\r
8050         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));\r
8051         SendToProgram(buf, cps);\r
8052         currentlyInitializedVariant = gameInfo.variant;\r
8053     }\r
8054     SendToProgram("force\n", cps);\r
8055     if (startedFromSetupPosition) {\r
8056         SendBoard(cps, backwardMostMove);\r
8057     if (appData.debugMode) {\r
8058         fprintf(debugFP, "feedMoves\n");\r
8059     }\r
8060     }\r
8061     for (i = backwardMostMove; i < upto; i++) {\r
8062         SendMoveToProgram(i, cps);\r
8063     }\r
8064 }\r
8065 \r
8066 \r
8067 void\r
8068 ResurrectChessProgram()\r
8069 {\r
8070      /* The chess program may have exited.\r
8071         If so, restart it and feed it all the moves made so far. */\r
8072 \r
8073     if (appData.noChessProgram || first.pr != NoProc) return;\r
8074     \r
8075     StartChessProgram(&first);\r
8076     InitChessProgram(&first, FALSE);\r
8077     FeedMovesToProgram(&first, currentMove);\r
8078 \r
8079     if (!first.sendTime) {\r
8080         /* can't tell gnuchess what its clock should read,\r
8081            so we bow to its notion. */\r
8082         ResetClocks();\r
8083         timeRemaining[0][currentMove] = whiteTimeRemaining;\r
8084         timeRemaining[1][currentMove] = blackTimeRemaining;\r
8085     }\r
8086 \r
8087     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||\r
8088                 appData.icsEngineAnalyze) && first.analysisSupport) {\r
8089       SendToProgram("analyze\n", &first);\r
8090       first.analyzing = TRUE;\r
8091     }\r
8092 }\r
8093 \r
8094 /*\r
8095  * Button procedures\r
8096  */\r
8097 void\r
8098 Reset(redraw, init)\r
8099      int redraw, init;\r
8100 {\r
8101     int i;\r
8102 \r
8103     if (appData.debugMode) {\r
8104         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",\r
8105                 redraw, init, gameMode);\r
8106     }\r
8107     pausing = pauseExamInvalid = FALSE;\r
8108     startedFromSetupPosition = blackPlaysFirst = FALSE;\r
8109     firstMove = TRUE;\r
8110     whiteFlag = blackFlag = FALSE;\r
8111     userOfferedDraw = FALSE;\r
8112     hintRequested = bookRequested = FALSE;\r
8113     first.maybeThinking = FALSE;\r
8114     second.maybeThinking = FALSE;\r
8115     first.bookSuspend = FALSE; // [HGM] book\r
8116     second.bookSuspend = FALSE;\r
8117     thinkOutput[0] = NULLCHAR;\r
8118     lastHint[0] = NULLCHAR;\r
8119     ClearGameInfo(&gameInfo);\r
8120     gameInfo.variant = StringToVariant(appData.variant);\r
8121     ics_user_moved = ics_clock_paused = FALSE;\r
8122     ics_getting_history = H_FALSE;\r
8123     ics_gamenum = -1;\r
8124     white_holding[0] = black_holding[0] = NULLCHAR;\r
8125     ClearProgramStats();\r
8126     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode\r
8127     \r
8128     ResetFrontEnd();\r
8129     ClearHighlights();\r
8130     flipView = appData.flipView;\r
8131     ClearPremoveHighlights();\r
8132     gotPremove = FALSE;\r
8133     alarmSounded = FALSE;\r
8134 \r
8135     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
8136     if(appData.serverMovesName != NULL) {\r
8137         /* [HGM] prepare to make moves file for broadcasting */\r
8138         clock_t t = clock();\r
8139         if(serverMoves != NULL) fclose(serverMoves);\r
8140         serverMoves = fopen(appData.serverMovesName, "r");\r
8141         if(serverMoves != NULL) {\r
8142             fclose(serverMoves);\r
8143             /* delay 15 sec before overwriting, so all clients can see end */\r
8144             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);\r
8145         }\r
8146         serverMoves = fopen(appData.serverMovesName, "w");\r
8147     }\r
8148 \r
8149     ExitAnalyzeMode();\r
8150     gameMode = BeginningOfGame;\r
8151     ModeHighlight();\r
8152     if(appData.icsActive) gameInfo.variant = VariantNormal;\r
8153     InitPosition(redraw);\r
8154     for (i = 0; i < MAX_MOVES; i++) {\r
8155         if (commentList[i] != NULL) {\r
8156             free(commentList[i]);\r
8157             commentList[i] = NULL;\r
8158         }\r
8159     }\r
8160     ResetClocks();\r
8161     timeRemaining[0][0] = whiteTimeRemaining;\r
8162     timeRemaining[1][0] = blackTimeRemaining;\r
8163     if (first.pr == NULL) {\r
8164         StartChessProgram(&first);\r
8165     }\r
8166     if (init) {\r
8167             InitChessProgram(&first, startedFromSetupPosition);\r
8168     }\r
8169     DisplayTitle("");\r
8170     DisplayMessage("", "");\r
8171     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
8172 }\r
8173 \r
8174 void\r
8175 AutoPlayGameLoop()\r
8176 {\r
8177     for (;;) {\r
8178         if (!AutoPlayOneMove())\r
8179           return;\r
8180         if (matchMode || appData.timeDelay == 0)\r
8181           continue;\r
8182         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)\r
8183           return;\r
8184         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));\r
8185         break;\r
8186     }\r
8187 }\r
8188 \r
8189 \r
8190 int\r
8191 AutoPlayOneMove()\r
8192 {\r
8193     int fromX, fromY, toX, toY;\r
8194 \r
8195     if (appData.debugMode) {\r
8196       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);\r
8197     }\r
8198 \r
8199     if (gameMode != PlayFromGameFile)\r
8200       return FALSE;\r
8201 \r
8202     if (currentMove >= forwardMostMove) {\r
8203       gameMode = EditGame;\r
8204       ModeHighlight();\r
8205 \r
8206       /* [AS] Clear current move marker at the end of a game */\r
8207       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */\r
8208 \r
8209       return FALSE;\r
8210     }\r
8211     \r
8212     toX = moveList[currentMove][2] - AAA;\r
8213     toY = moveList[currentMove][3] - ONE;\r
8214 \r
8215     if (moveList[currentMove][1] == '@') {\r
8216         if (appData.highlightLastMove) {\r
8217             SetHighlights(-1, -1, toX, toY);\r
8218         }\r
8219     } else {\r
8220         fromX = moveList[currentMove][0] - AAA;\r
8221         fromY = moveList[currentMove][1] - ONE;\r
8222 \r
8223         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */\r
8224 \r
8225         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
8226 \r
8227         if (appData.highlightLastMove) {\r
8228             SetHighlights(fromX, fromY, toX, toY);\r
8229         }\r
8230     }\r
8231     DisplayMove(currentMove);\r
8232     SendMoveToProgram(currentMove++, &first);\r
8233     DisplayBothClocks();\r
8234     DrawPosition(FALSE, boards[currentMove]);\r
8235     // [HGM] PV info: always display, routine tests if empty\r
8236     DisplayComment(currentMove - 1, commentList[currentMove]);\r
8237     return TRUE;\r
8238 }\r
8239 \r
8240 \r
8241 int\r
8242 LoadGameOneMove(readAhead)\r
8243      ChessMove readAhead;\r
8244 {\r
8245     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;\r
8246     char promoChar = NULLCHAR;\r
8247     ChessMove moveType;\r
8248     char move[MSG_SIZ];\r
8249     char *p, *q;\r
8250     \r
8251     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && \r
8252         gameMode != AnalyzeMode && gameMode != Training) {\r
8253         gameFileFP = NULL;\r
8254         return FALSE;\r
8255     }\r
8256     \r
8257     yyboardindex = forwardMostMove;\r
8258     if (readAhead != (ChessMove)0) {\r
8259       moveType = readAhead;\r
8260     } else {\r
8261       if (gameFileFP == NULL)\r
8262           return FALSE;\r
8263       moveType = (ChessMove) yylex();\r
8264     }\r
8265     \r
8266     done = FALSE;\r
8267     switch (moveType) {\r
8268       case Comment:\r
8269         if (appData.debugMode) \r
8270           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
8271         p = yy_text;\r
8272         if (*p == '{' || *p == '[' || *p == '(') {\r
8273             p[strlen(p) - 1] = NULLCHAR;\r
8274             p++;\r
8275         }\r
8276 \r
8277         /* append the comment but don't display it */\r
8278         while (*p == '\n') p++;\r
8279         AppendComment(currentMove, p);\r
8280         return TRUE;\r
8281 \r
8282       case WhiteCapturesEnPassant:\r
8283       case BlackCapturesEnPassant:\r
8284       case WhitePromotionChancellor:\r
8285       case BlackPromotionChancellor:\r
8286       case WhitePromotionArchbishop:\r
8287       case BlackPromotionArchbishop:\r
8288       case WhitePromotionCentaur:\r
8289       case BlackPromotionCentaur:\r
8290       case WhitePromotionQueen:\r
8291       case BlackPromotionQueen:\r
8292       case WhitePromotionRook:\r
8293       case BlackPromotionRook:\r
8294       case WhitePromotionBishop:\r
8295       case BlackPromotionBishop:\r
8296       case WhitePromotionKnight:\r
8297       case BlackPromotionKnight:\r
8298       case WhitePromotionKing:\r
8299       case BlackPromotionKing:\r
8300       case NormalMove:\r
8301       case WhiteKingSideCastle:\r
8302       case WhiteQueenSideCastle:\r
8303       case BlackKingSideCastle:\r
8304       case BlackQueenSideCastle:\r
8305       case WhiteKingSideCastleWild:\r
8306       case WhiteQueenSideCastleWild:\r
8307       case BlackKingSideCastleWild:\r
8308       case BlackQueenSideCastleWild:\r
8309       /* PUSH Fabien */\r
8310       case WhiteHSideCastleFR:\r
8311       case WhiteASideCastleFR:\r
8312       case BlackHSideCastleFR:\r
8313       case BlackASideCastleFR:\r
8314       /* POP Fabien */\r
8315         if (appData.debugMode)\r
8316           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
8317         fromX = currentMoveString[0] - AAA;\r
8318         fromY = currentMoveString[1] - ONE;\r
8319         toX = currentMoveString[2] - AAA;\r
8320         toY = currentMoveString[3] - ONE;\r
8321         promoChar = currentMoveString[4];\r
8322         break;\r
8323 \r
8324       case WhiteDrop:\r
8325       case BlackDrop:\r
8326         if (appData.debugMode)\r
8327           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
8328         fromX = moveType == WhiteDrop ?\r
8329           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
8330         (int) CharToPiece(ToLower(currentMoveString[0]));\r
8331         fromY = DROP_RANK;\r
8332         toX = currentMoveString[2] - AAA;\r
8333         toY = currentMoveString[3] - ONE;\r
8334         break;\r
8335 \r
8336       case WhiteWins:\r
8337       case BlackWins:\r
8338       case GameIsDrawn:\r
8339       case GameUnfinished:\r
8340         if (appData.debugMode)\r
8341           fprintf(debugFP, "Parsed game end: %s\n", yy_text);\r
8342         p = strchr(yy_text, '{');\r
8343         if (p == NULL) p = strchr(yy_text, '(');\r
8344         if (p == NULL) {\r
8345             p = yy_text;\r
8346             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
8347         } else {\r
8348             q = strchr(p, *p == '{' ? '}' : ')');\r
8349             if (q != NULL) *q = NULLCHAR;\r
8350             p++;\r
8351         }\r
8352         GameEnds(moveType, p, GE_FILE);\r
8353         done = TRUE;\r
8354         if (cmailMsgLoaded) {\r
8355             ClearHighlights();\r
8356             flipView = WhiteOnMove(currentMove);\r
8357             if (moveType == GameUnfinished) flipView = !flipView;\r
8358             if (appData.debugMode)\r
8359               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;\r
8360         }\r
8361         break;\r
8362 \r
8363       case (ChessMove) 0:       /* end of file */\r
8364         if (appData.debugMode)\r
8365           fprintf(debugFP, "Parser hit end of file\n");\r
8366         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8367                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8368           case MT_NONE:\r
8369           case MT_CHECK:\r
8370             break;\r
8371           case MT_CHECKMATE:\r
8372             if (WhiteOnMove(currentMove)) {\r
8373                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
8374             } else {\r
8375                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
8376             }\r
8377             break;\r
8378           case MT_STALEMATE:\r
8379             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
8380             break;\r
8381         }\r
8382         done = TRUE;\r
8383         break;\r
8384 \r
8385       case MoveNumberOne:\r
8386         if (lastLoadGameStart == GNUChessGame) {\r
8387             /* GNUChessGames have numbers, but they aren't move numbers */\r
8388             if (appData.debugMode)\r
8389               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
8390                       yy_text, (int) moveType);\r
8391             return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
8392         }\r
8393         /* else fall thru */\r
8394 \r
8395       case XBoardGame:\r
8396       case GNUChessGame:\r
8397       case PGNTag:\r
8398         /* Reached start of next game in file */\r
8399         if (appData.debugMode)\r
8400           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);\r
8401         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8402                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8403           case MT_NONE:\r
8404           case MT_CHECK:\r
8405             break;\r
8406           case MT_CHECKMATE:\r
8407             if (WhiteOnMove(currentMove)) {\r
8408                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
8409             } else {\r
8410                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
8411             }\r
8412             break;\r
8413           case MT_STALEMATE:\r
8414             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
8415             break;\r
8416         }\r
8417         done = TRUE;\r
8418         break;\r
8419 \r
8420       case PositionDiagram:     /* should not happen; ignore */\r
8421       case ElapsedTime:         /* ignore */\r
8422       case NAG:                 /* ignore */\r
8423         if (appData.debugMode)\r
8424           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
8425                   yy_text, (int) moveType);\r
8426         return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
8427 \r
8428       case IllegalMove:\r
8429         if (appData.testLegality) {\r
8430             if (appData.debugMode)\r
8431               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);\r
8432             sprintf(move, _("Illegal move: %d.%s%s"),\r
8433                     (forwardMostMove / 2) + 1,\r
8434                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8435             DisplayError(move, 0);\r
8436             done = TRUE;\r
8437         } else {\r
8438             if (appData.debugMode)\r
8439               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",\r
8440                       yy_text, currentMoveString);\r
8441             fromX = currentMoveString[0] - AAA;\r
8442             fromY = currentMoveString[1] - ONE;\r
8443             toX = currentMoveString[2] - AAA;\r
8444             toY = currentMoveString[3] - ONE;\r
8445             promoChar = currentMoveString[4];\r
8446         }\r
8447         break;\r
8448 \r
8449       case AmbiguousMove:\r
8450         if (appData.debugMode)\r
8451           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);\r
8452         sprintf(move, _("Ambiguous move: %d.%s%s"),\r
8453                 (forwardMostMove / 2) + 1,\r
8454                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8455         DisplayError(move, 0);\r
8456         done = TRUE;\r
8457         break;\r
8458 \r
8459       default:\r
8460       case ImpossibleMove:\r
8461         if (appData.debugMode)\r
8462           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);\r
8463         sprintf(move, _("Illegal move: %d.%s%s"),\r
8464                 (forwardMostMove / 2) + 1,\r
8465                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8466         DisplayError(move, 0);\r
8467         done = TRUE;\r
8468         break;\r
8469     }\r
8470 \r
8471     if (done) {\r
8472         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {\r
8473             DrawPosition(FALSE, boards[currentMove]);\r
8474             DisplayBothClocks();\r
8475             if (!appData.matchMode) // [HGM] PV info: routine tests if empty\r
8476               DisplayComment(currentMove - 1, commentList[currentMove]);\r
8477         }\r
8478         (void) StopLoadGameTimer();\r
8479         gameFileFP = NULL;\r
8480         cmailOldMove = forwardMostMove;\r
8481         return FALSE;\r
8482     } else {\r
8483         /* currentMoveString is set as a side-effect of yylex */\r
8484         strcat(currentMoveString, "\n");\r
8485         strcpy(moveList[forwardMostMove], currentMoveString);\r
8486         \r
8487         thinkOutput[0] = NULLCHAR;\r
8488         MakeMove(fromX, fromY, toX, toY, promoChar);\r
8489         currentMove = forwardMostMove;\r
8490         return TRUE;\r
8491     }\r
8492 }\r
8493 \r
8494 /* Load the nth game from the given file */\r
8495 int\r
8496 LoadGameFromFile(filename, n, title, useList)\r
8497      char *filename;\r
8498      int n;\r
8499      char *title;\r
8500      /*Boolean*/ int useList;\r
8501 {\r
8502     FILE *f;\r
8503     char buf[MSG_SIZ];\r
8504 \r
8505     if (strcmp(filename, "-") == 0) {\r
8506         f = stdin;\r
8507         title = "stdin";\r
8508     } else {\r
8509         f = fopen(filename, "rb");\r
8510         if (f == NULL) {\r
8511             sprintf(buf, _("Can't open \"%s\""), filename);\r
8512             DisplayError(buf, errno);\r
8513             return FALSE;\r
8514         }\r
8515     }\r
8516     if (fseek(f, 0, 0) == -1) {\r
8517         /* f is not seekable; probably a pipe */\r
8518         useList = FALSE;\r
8519     }\r
8520     if (useList && n == 0) {\r
8521         int error = GameListBuild(f);\r
8522         if (error) {\r
8523             DisplayError(_("Cannot build game list"), error);\r
8524         } else if (!ListEmpty(&gameList) &&\r
8525                    ((ListGame *) gameList.tailPred)->number > 1) {\r
8526             GameListPopUp(f, title);\r
8527             return TRUE;\r
8528         }\r
8529         GameListDestroy();\r
8530         n = 1;\r
8531     }\r
8532     if (n == 0) n = 1;\r
8533     return LoadGame(f, n, title, FALSE);\r
8534 }\r
8535 \r
8536 \r
8537 void\r
8538 MakeRegisteredMove()\r
8539 {\r
8540     int fromX, fromY, toX, toY;\r
8541     char promoChar;\r
8542     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8543         switch (cmailMoveType[lastLoadGameNumber - 1]) {\r
8544           case CMAIL_MOVE:\r
8545           case CMAIL_DRAW:\r
8546             if (appData.debugMode)\r
8547               fprintf(debugFP, "Restoring %s for game %d\n",\r
8548                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
8549     \r
8550             thinkOutput[0] = NULLCHAR;\r
8551             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);\r
8552             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;\r
8553             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;\r
8554             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;\r
8555             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;\r
8556             promoChar = cmailMove[lastLoadGameNumber - 1][4];\r
8557             MakeMove(fromX, fromY, toX, toY, promoChar);\r
8558             ShowMove(fromX, fromY, toX, toY);\r
8559               \r
8560             switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8561                              EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8562               case MT_NONE:\r
8563               case MT_CHECK:\r
8564                 break;\r
8565                 \r
8566               case MT_CHECKMATE:\r
8567                 if (WhiteOnMove(currentMove)) {\r
8568                     GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
8569                 } else {\r
8570                     GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
8571                 }\r
8572                 break;\r
8573                 \r
8574               case MT_STALEMATE:\r
8575                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
8576                 break;\r
8577             }\r
8578 \r
8579             break;\r
8580             \r
8581           case CMAIL_RESIGN:\r
8582             if (WhiteOnMove(currentMove)) {\r
8583                 GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
8584             } else {\r
8585                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
8586             }\r
8587             break;\r
8588             \r
8589           case CMAIL_ACCEPT:\r
8590             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
8591             break;\r
8592               \r
8593           default:\r
8594             break;\r
8595         }\r
8596     }\r
8597 \r
8598     return;\r
8599 }\r
8600 \r
8601 /* Wrapper around LoadGame for use when a Cmail message is loaded */\r
8602 int\r
8603 CmailLoadGame(f, gameNumber, title, useList)\r
8604      FILE *f;\r
8605      int gameNumber;\r
8606      char *title;\r
8607      int useList;\r
8608 {\r
8609     int retVal;\r
8610 \r
8611     if (gameNumber > nCmailGames) {\r
8612         DisplayError(_("No more games in this message"), 0);\r
8613         return FALSE;\r
8614     }\r
8615     if (f == lastLoadGameFP) {\r
8616         int offset = gameNumber - lastLoadGameNumber;\r
8617         if (offset == 0) {\r
8618             cmailMsg[0] = NULLCHAR;\r
8619             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8620                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
8621                 nCmailMovesRegistered--;\r
8622             }\r
8623             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
8624             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {\r
8625                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;\r
8626             }\r
8627         } else {\r
8628             if (! RegisterMove()) return FALSE;\r
8629         }\r
8630     }\r
8631 \r
8632     retVal = LoadGame(f, gameNumber, title, useList);\r
8633 \r
8634     /* Make move registered during previous look at this game, if any */\r
8635     MakeRegisteredMove();\r
8636 \r
8637     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {\r
8638         commentList[currentMove]\r
8639           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);\r
8640         DisplayComment(currentMove - 1, commentList[currentMove]);\r
8641     }\r
8642 \r
8643     return retVal;\r
8644 }\r
8645 \r
8646 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */\r
8647 int\r
8648 ReloadGame(offset)\r
8649      int offset;\r
8650 {\r
8651     int gameNumber = lastLoadGameNumber + offset;\r
8652     if (lastLoadGameFP == NULL) {\r
8653         DisplayError(_("No game has been loaded yet"), 0);\r
8654         return FALSE;\r
8655     }\r
8656     if (gameNumber <= 0) {\r
8657         DisplayError(_("Can't back up any further"), 0);\r
8658         return FALSE;\r
8659     }\r
8660     if (cmailMsgLoaded) {\r
8661         return CmailLoadGame(lastLoadGameFP, gameNumber,\r
8662                              lastLoadGameTitle, lastLoadGameUseList);\r
8663     } else {\r
8664         return LoadGame(lastLoadGameFP, gameNumber,\r
8665                         lastLoadGameTitle, lastLoadGameUseList);\r
8666     }\r
8667 }\r
8668 \r
8669 \r
8670 \r
8671 /* Load the nth game from open file f */\r
8672 int\r
8673 LoadGame(f, gameNumber, title, useList)\r
8674      FILE *f;\r
8675      int gameNumber;\r
8676      char *title;\r
8677      int useList;\r
8678 {\r
8679     ChessMove cm;\r
8680     char buf[MSG_SIZ];\r
8681     int gn = gameNumber;\r
8682     ListGame *lg = NULL;\r
8683     int numPGNTags = 0;\r
8684     int err;\r
8685     GameMode oldGameMode;\r
8686     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */\r
8687 \r
8688     if (appData.debugMode) \r
8689         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);\r
8690 \r
8691     if (gameMode == Training )\r
8692         SetTrainingModeOff();\r
8693 \r
8694     oldGameMode = gameMode;\r
8695     if (gameMode != BeginningOfGame) {\r
8696       Reset(FALSE, TRUE);\r
8697     }\r
8698 \r
8699     gameFileFP = f;\r
8700     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {\r
8701         fclose(lastLoadGameFP);\r
8702     }\r
8703 \r
8704     if (useList) {\r
8705         lg = (ListGame *) ListElem(&gameList, gameNumber-1);\r
8706         \r
8707         if (lg) {\r
8708             fseek(f, lg->offset, 0);\r
8709             GameListHighlight(gameNumber);\r
8710             gn = 1;\r
8711         }\r
8712         else {\r
8713             DisplayError(_("Game number out of range"), 0);\r
8714             return FALSE;\r
8715         }\r
8716     } else {\r
8717         GameListDestroy();\r
8718         if (fseek(f, 0, 0) == -1) {\r
8719             if (f == lastLoadGameFP ?\r
8720                 gameNumber == lastLoadGameNumber + 1 :\r
8721                 gameNumber == 1) {\r
8722                 gn = 1;\r
8723             } else {\r
8724                 DisplayError(_("Can't seek on game file"), 0);\r
8725                 return FALSE;\r
8726             }\r
8727         }\r
8728     }\r
8729     lastLoadGameFP = f;\r
8730     lastLoadGameNumber = gameNumber;\r
8731     strcpy(lastLoadGameTitle, title);\r
8732     lastLoadGameUseList = useList;\r
8733 \r
8734     yynewfile(f);\r
8735 \r
8736     if (lg && lg->gameInfo.white && lg->gameInfo.black) {\r
8737         sprintf(buf, "%s vs. %s", lg->gameInfo.white,\r
8738                 lg->gameInfo.black);\r
8739             DisplayTitle(buf);\r
8740     } else if (*title != NULLCHAR) {\r
8741         if (gameNumber > 1) {\r
8742             sprintf(buf, "%s %d", title, gameNumber);\r
8743             DisplayTitle(buf);\r
8744         } else {\r
8745             DisplayTitle(title);\r
8746         }\r
8747     }\r
8748 \r
8749     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {\r
8750         gameMode = PlayFromGameFile;\r
8751         ModeHighlight();\r
8752     }\r
8753 \r
8754     currentMove = forwardMostMove = backwardMostMove = 0;\r
8755     CopyBoard(boards[0], initialPosition);\r
8756     StopClocks();\r
8757 \r
8758     /*\r
8759      * Skip the first gn-1 games in the file.\r
8760      * Also skip over anything that precedes an identifiable \r
8761      * start of game marker, to avoid being confused by \r
8762      * garbage at the start of the file.  Currently \r
8763      * recognized start of game markers are the move number "1",\r
8764      * the pattern "gnuchess .* game", the pattern\r
8765      * "^[#;%] [^ ]* game file", and a PGN tag block.  \r
8766      * A game that starts with one of the latter two patterns\r
8767      * will also have a move number 1, possibly\r
8768      * following a position diagram.\r
8769      * 5-4-02: Let's try being more lenient and allowing a game to\r
8770      * start with an unnumbered move.  Does that break anything?\r
8771      */\r
8772     cm = lastLoadGameStart = (ChessMove) 0;\r
8773     while (gn > 0) {\r
8774         yyboardindex = forwardMostMove;\r
8775         cm = (ChessMove) yylex();\r
8776         switch (cm) {\r
8777           case (ChessMove) 0:\r
8778             if (cmailMsgLoaded) {\r
8779                 nCmailGames = CMAIL_MAX_GAMES - gn;\r
8780             } else {\r
8781                 Reset(TRUE, TRUE);\r
8782                 DisplayError(_("Game not found in file"), 0);\r
8783             }\r
8784             return FALSE;\r
8785 \r
8786           case GNUChessGame:\r
8787           case XBoardGame:\r
8788             gn--;\r
8789             lastLoadGameStart = cm;\r
8790             break;\r
8791             \r
8792           case MoveNumberOne:\r
8793             switch (lastLoadGameStart) {\r
8794               case GNUChessGame:\r
8795               case XBoardGame:\r
8796               case PGNTag:\r
8797                 break;\r
8798               case MoveNumberOne:\r
8799               case (ChessMove) 0:\r
8800                 gn--;           /* count this game */\r
8801                 lastLoadGameStart = cm;\r
8802                 break;\r
8803               default:\r
8804                 /* impossible */\r
8805                 break;\r
8806             }\r
8807             break;\r
8808 \r
8809           case PGNTag:\r
8810             switch (lastLoadGameStart) {\r
8811               case GNUChessGame:\r
8812               case PGNTag:\r
8813               case MoveNumberOne:\r
8814               case (ChessMove) 0:\r
8815                 gn--;           /* count this game */\r
8816                 lastLoadGameStart = cm;\r
8817                 break;\r
8818               case XBoardGame:\r
8819                 lastLoadGameStart = cm; /* game counted already */\r
8820                 break;\r
8821               default:\r
8822                 /* impossible */\r
8823                 break;\r
8824             }\r
8825             if (gn > 0) {\r
8826                 do {\r
8827                     yyboardindex = forwardMostMove;\r
8828                     cm = (ChessMove) yylex();\r
8829                 } while (cm == PGNTag || cm == Comment);\r
8830             }\r
8831             break;\r
8832 \r
8833           case WhiteWins:\r
8834           case BlackWins:\r
8835           case GameIsDrawn:\r
8836             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {\r
8837                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]\r
8838                     != CMAIL_OLD_RESULT) {\r
8839                     nCmailResults ++ ;\r
8840                     cmailResult[  CMAIL_MAX_GAMES\r
8841                                 - gn - 1] = CMAIL_OLD_RESULT;\r
8842                 }\r
8843             }\r
8844             break;\r
8845 \r
8846           case NormalMove:\r
8847             /* Only a NormalMove can be at the start of a game\r
8848              * without a position diagram. */\r
8849             if (lastLoadGameStart == (ChessMove) 0) {\r
8850               gn--;\r
8851               lastLoadGameStart = MoveNumberOne;\r
8852             }\r
8853             break;\r
8854 \r
8855           default:\r
8856             break;\r
8857         }\r
8858     }\r
8859     \r
8860     if (appData.debugMode)\r
8861       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);\r
8862 \r
8863     if (cm == XBoardGame) {\r
8864         /* Skip any header junk before position diagram and/or move 1 */\r
8865         for (;;) {\r
8866             yyboardindex = forwardMostMove;\r
8867             cm = (ChessMove) yylex();\r
8868 \r
8869             if (cm == (ChessMove) 0 ||\r
8870                 cm == GNUChessGame || cm == XBoardGame) {\r
8871                 /* Empty game; pretend end-of-file and handle later */\r
8872                 cm = (ChessMove) 0;\r
8873                 break;\r
8874             }\r
8875 \r
8876             if (cm == MoveNumberOne || cm == PositionDiagram ||\r
8877                 cm == PGNTag || cm == Comment)\r
8878               break;\r
8879         }\r
8880     } else if (cm == GNUChessGame) {\r
8881         if (gameInfo.event != NULL) {\r
8882             free(gameInfo.event);\r
8883         }\r
8884         gameInfo.event = StrSave(yy_text);\r
8885     }   \r
8886 \r
8887     startedFromSetupPosition = FALSE;\r
8888     while (cm == PGNTag) {\r
8889         if (appData.debugMode) \r
8890           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);\r
8891         err = ParsePGNTag(yy_text, &gameInfo);\r
8892         if (!err) numPGNTags++;\r
8893 \r
8894         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */\r
8895         if(gameInfo.variant != oldVariant) {\r
8896             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */\r
8897             InitPosition(TRUE);\r
8898             oldVariant = gameInfo.variant;\r
8899             if (appData.debugMode) \r
8900               fprintf(debugFP, "New variant %d\n", (int) oldVariant);\r
8901         }\r
8902 \r
8903 \r
8904         if (gameInfo.fen != NULL) {\r
8905           Board initial_position;\r
8906           startedFromSetupPosition = TRUE;\r
8907           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {\r
8908             Reset(TRUE, TRUE);\r
8909             DisplayError(_("Bad FEN position in file"), 0);\r
8910             return FALSE;\r
8911           }\r
8912           CopyBoard(boards[0], initial_position);\r
8913           if (blackPlaysFirst) {\r
8914             currentMove = forwardMostMove = backwardMostMove = 1;\r
8915             CopyBoard(boards[1], initial_position);\r
8916             strcpy(moveList[0], "");\r
8917             strcpy(parseList[0], "");\r
8918             timeRemaining[0][1] = whiteTimeRemaining;\r
8919             timeRemaining[1][1] = blackTimeRemaining;\r
8920             if (commentList[0] != NULL) {\r
8921               commentList[1] = commentList[0];\r
8922               commentList[0] = NULL;\r
8923             }\r
8924           } else {\r
8925             currentMove = forwardMostMove = backwardMostMove = 0;\r
8926           }\r
8927           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */\r
8928           {   int i;\r
8929               initialRulePlies = FENrulePlies;\r
8930               epStatus[forwardMostMove] = FENepStatus;\r
8931               for( i=0; i< nrCastlingRights; i++ )\r
8932                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];\r
8933           }\r
8934           yyboardindex = forwardMostMove;\r
8935           free(gameInfo.fen);\r
8936           gameInfo.fen = NULL;\r
8937         }\r
8938 \r
8939         yyboardindex = forwardMostMove;\r
8940         cm = (ChessMove) yylex();\r
8941 \r
8942         /* Handle comments interspersed among the tags */\r
8943         while (cm == Comment) {\r
8944             char *p;\r
8945             if (appData.debugMode) \r
8946               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
8947             p = yy_text;\r
8948             if (*p == '{' || *p == '[' || *p == '(') {\r
8949                 p[strlen(p) - 1] = NULLCHAR;\r
8950                 p++;\r
8951             }\r
8952             while (*p == '\n') p++;\r
8953             AppendComment(currentMove, p);\r
8954             yyboardindex = forwardMostMove;\r
8955             cm = (ChessMove) yylex();\r
8956         }\r
8957     }\r
8958 \r
8959     /* don't rely on existence of Event tag since if game was\r
8960      * pasted from clipboard the Event tag may not exist\r
8961      */\r
8962     if (numPGNTags > 0){\r
8963         char *tags;\r
8964         if (gameInfo.variant == VariantNormal) {\r
8965           gameInfo.variant = StringToVariant(gameInfo.event);\r
8966         }\r
8967         if (!matchMode) {\r
8968           if( appData.autoDisplayTags ) {\r
8969             tags = PGNTags(&gameInfo);\r
8970             TagsPopUp(tags, CmailMsg());\r
8971             free(tags);\r
8972           }\r
8973         }\r
8974     } else {\r
8975         /* Make something up, but don't display it now */\r
8976         SetGameInfo();\r
8977         TagsPopDown();\r
8978     }\r
8979 \r
8980     if (cm == PositionDiagram) {\r
8981         int i, j;\r
8982         char *p;\r
8983         Board initial_position;\r
8984 \r
8985         if (appData.debugMode)\r
8986           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);\r
8987 \r
8988         if (!startedFromSetupPosition) {\r
8989             p = yy_text;\r
8990             for (i = BOARD_HEIGHT - 1; i >= 0; i--)\r
8991               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)\r
8992                 switch (*p) {\r
8993                   case '[':\r
8994                   case '-':\r
8995                   case ' ':\r
8996                   case '\t':\r
8997                   case '\n':\r
8998                   case '\r':\r
8999                     break;\r
9000                   default:\r
9001                     initial_position[i][j++] = CharToPiece(*p);\r
9002                     break;\r
9003                 }\r
9004             while (*p == ' ' || *p == '\t' ||\r
9005                    *p == '\n' || *p == '\r') p++;\r
9006         \r
9007             if (strncmp(p, "black", strlen("black"))==0)\r
9008               blackPlaysFirst = TRUE;\r
9009             else\r
9010               blackPlaysFirst = FALSE;\r
9011             startedFromSetupPosition = TRUE;\r
9012         \r
9013             CopyBoard(boards[0], initial_position);\r
9014             if (blackPlaysFirst) {\r
9015                 currentMove = forwardMostMove = backwardMostMove = 1;\r
9016                 CopyBoard(boards[1], initial_position);\r
9017                 strcpy(moveList[0], "");\r
9018                 strcpy(parseList[0], "");\r
9019                 timeRemaining[0][1] = whiteTimeRemaining;\r
9020                 timeRemaining[1][1] = blackTimeRemaining;\r
9021                 if (commentList[0] != NULL) {\r
9022                     commentList[1] = commentList[0];\r
9023                     commentList[0] = NULL;\r
9024                 }\r
9025             } else {\r
9026                 currentMove = forwardMostMove = backwardMostMove = 0;\r
9027             }\r
9028         }\r
9029         yyboardindex = forwardMostMove;\r
9030         cm = (ChessMove) yylex();\r
9031     }\r
9032 \r
9033     if (first.pr == NoProc) {\r
9034         StartChessProgram(&first);\r
9035     }\r
9036     InitChessProgram(&first, FALSE);\r
9037     SendToProgram("force\n", &first);\r
9038     if (startedFromSetupPosition) {\r
9039         SendBoard(&first, forwardMostMove);\r
9040     if (appData.debugMode) {\r
9041         fprintf(debugFP, "Load Game\n");\r
9042     }\r
9043         DisplayBothClocks();\r
9044     }      \r
9045 \r
9046     /* [HGM] server: flag to write setup moves in broadcast file as one */\r
9047     loadFlag = appData.suppressLoadMoves;\r
9048 \r
9049     while (cm == Comment) {\r
9050         char *p;\r
9051         if (appData.debugMode) \r
9052           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
9053         p = yy_text;\r
9054         if (*p == '{' || *p == '[' || *p == '(') {\r
9055             p[strlen(p) - 1] = NULLCHAR;\r
9056             p++;\r
9057         }\r
9058         while (*p == '\n') p++;\r
9059         AppendComment(currentMove, p);\r
9060         yyboardindex = forwardMostMove;\r
9061         cm = (ChessMove) yylex();\r
9062     }\r
9063 \r
9064     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||\r
9065         cm == WhiteWins || cm == BlackWins ||\r
9066         cm == GameIsDrawn || cm == GameUnfinished) {\r
9067         DisplayMessage("", _("No moves in game"));\r
9068         if (cmailMsgLoaded) {\r
9069             if (appData.debugMode)\r
9070               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);\r
9071             ClearHighlights();\r
9072             flipView = FALSE;\r
9073         }\r
9074         DrawPosition(FALSE, boards[currentMove]);\r
9075         DisplayBothClocks();\r
9076         gameMode = EditGame;\r
9077         ModeHighlight();\r
9078         gameFileFP = NULL;\r
9079         cmailOldMove = 0;\r
9080         return TRUE;\r
9081     }\r
9082 \r
9083     // [HGM] PV info: routine tests if comment empty\r
9084     if (!matchMode && (pausing || appData.timeDelay != 0)) {\r
9085         DisplayComment(currentMove - 1, commentList[currentMove]);\r
9086     }\r
9087     if (!matchMode && appData.timeDelay != 0) \r
9088       DrawPosition(FALSE, boards[currentMove]);\r
9089 \r
9090     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {\r
9091       programStats.ok_to_send = 1;\r
9092     }\r
9093 \r
9094     /* if the first token after the PGN tags is a move\r
9095      * and not move number 1, retrieve it from the parser \r
9096      */\r
9097     if (cm != MoveNumberOne)\r
9098         LoadGameOneMove(cm);\r
9099 \r
9100     /* load the remaining moves from the file */\r
9101     while (LoadGameOneMove((ChessMove)0)) {\r
9102       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
9103       timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
9104     }\r
9105 \r
9106     /* rewind to the start of the game */\r
9107     currentMove = backwardMostMove;\r
9108 \r
9109     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
9110 \r
9111     if (oldGameMode == AnalyzeFile ||\r
9112         oldGameMode == AnalyzeMode) {\r
9113       AnalyzeFileEvent();\r
9114     }\r
9115 \r
9116     if (matchMode || appData.timeDelay == 0) {\r
9117       ToEndEvent();\r
9118       gameMode = EditGame;\r
9119       ModeHighlight();\r
9120     } else if (appData.timeDelay > 0) {\r
9121       AutoPlayGameLoop();\r
9122     }\r
9123 \r
9124     if (appData.debugMode) \r
9125         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);\r
9126 \r
9127     loadFlag = 0; /* [HGM] true game starts */\r
9128     return TRUE;\r
9129 }\r
9130 \r
9131 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */\r
9132 int\r
9133 ReloadPosition(offset)\r
9134      int offset;\r
9135 {\r
9136     int positionNumber = lastLoadPositionNumber + offset;\r
9137     if (lastLoadPositionFP == NULL) {\r
9138         DisplayError(_("No position has been loaded yet"), 0);\r
9139         return FALSE;\r
9140     }\r
9141     if (positionNumber <= 0) {\r
9142         DisplayError(_("Can't back up any further"), 0);\r
9143         return FALSE;\r
9144     }\r
9145     return LoadPosition(lastLoadPositionFP, positionNumber,\r
9146                         lastLoadPositionTitle);\r
9147 }\r
9148 \r
9149 /* Load the nth position from the given file */\r
9150 int\r
9151 LoadPositionFromFile(filename, n, title)\r
9152      char *filename;\r
9153      int n;\r
9154      char *title;\r
9155 {\r
9156     FILE *f;\r
9157     char buf[MSG_SIZ];\r
9158 \r
9159     if (strcmp(filename, "-") == 0) {\r
9160         return LoadPosition(stdin, n, "stdin");\r
9161     } else {\r
9162         f = fopen(filename, "rb");\r
9163         if (f == NULL) {\r
9164             sprintf(buf, _("Can't open \"%s\""), filename);\r
9165             DisplayError(buf, errno);\r
9166             return FALSE;\r
9167         } else {\r
9168             return LoadPosition(f, n, title);\r
9169         }\r
9170     }\r
9171 }\r
9172 \r
9173 /* Load the nth position from the given open file, and close it */\r
9174 int\r
9175 LoadPosition(f, positionNumber, title)\r
9176      FILE *f;\r
9177      int positionNumber;\r
9178      char *title;\r
9179 {\r
9180     char *p, line[MSG_SIZ];\r
9181     Board initial_position;\r
9182     int i, j, fenMode, pn;\r
9183     \r
9184     if (gameMode == Training )\r
9185         SetTrainingModeOff();\r
9186 \r
9187     if (gameMode != BeginningOfGame) {\r
9188         Reset(FALSE, TRUE);\r
9189     }\r
9190     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {\r
9191         fclose(lastLoadPositionFP);\r
9192     }\r
9193     if (positionNumber == 0) positionNumber = 1;\r
9194     lastLoadPositionFP = f;\r
9195     lastLoadPositionNumber = positionNumber;\r
9196     strcpy(lastLoadPositionTitle, title);\r
9197     if (first.pr == NoProc) {\r
9198       StartChessProgram(&first);\r
9199       InitChessProgram(&first, FALSE);\r
9200     }    \r
9201     pn = positionNumber;\r
9202     if (positionNumber < 0) {\r
9203         /* Negative position number means to seek to that byte offset */\r
9204         if (fseek(f, -positionNumber, 0) == -1) {\r
9205             DisplayError(_("Can't seek on position file"), 0);\r
9206             return FALSE;\r
9207         };\r
9208         pn = 1;\r
9209     } else {\r
9210         if (fseek(f, 0, 0) == -1) {\r
9211             if (f == lastLoadPositionFP ?\r
9212                 positionNumber == lastLoadPositionNumber + 1 :\r
9213                 positionNumber == 1) {\r
9214                 pn = 1;\r
9215             } else {\r
9216                 DisplayError(_("Can't seek on position file"), 0);\r
9217                 return FALSE;\r
9218             }\r
9219         }\r
9220     }\r
9221     /* See if this file is FEN or old-style xboard */\r
9222     if (fgets(line, MSG_SIZ, f) == NULL) {\r
9223         DisplayError(_("Position not found in file"), 0);\r
9224         return FALSE;\r
9225     }\r
9226 #if 0\r
9227     switch (line[0]) {\r
9228       case '#':  case 'x':\r
9229       default:\r
9230         fenMode = FALSE;\r
9231         break;\r
9232       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':\r
9233       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':\r
9234       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':\r
9235       case '7':  case '8':  case '9':\r
9236       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':\r
9237       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':\r
9238       case 'C':  case 'W':             case 'c':  case 'w': \r
9239         fenMode = TRUE;\r
9240         break;\r
9241     }\r
9242 #else\r
9243     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces\r
9244     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;\r
9245 #endif\r
9246 \r
9247     if (pn >= 2) {\r
9248         if (fenMode || line[0] == '#') pn--;\r
9249         while (pn > 0) {\r
9250             /* skip positions before number pn */\r
9251             if (fgets(line, MSG_SIZ, f) == NULL) {\r
9252                 Reset(TRUE, TRUE);\r
9253                 DisplayError(_("Position not found in file"), 0);\r
9254                 return FALSE;\r
9255             }\r
9256             if (fenMode || line[0] == '#') pn--;\r
9257         }\r
9258     }\r
9259 \r
9260     if (fenMode) {\r
9261         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {\r
9262             DisplayError(_("Bad FEN position in file"), 0);\r
9263             return FALSE;\r
9264         }\r
9265     } else {\r
9266         (void) fgets(line, MSG_SIZ, f);\r
9267         (void) fgets(line, MSG_SIZ, f);\r
9268     \r
9269         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
9270             (void) fgets(line, MSG_SIZ, f);\r
9271             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {\r
9272                 if (*p == ' ')\r
9273                   continue;\r
9274                 initial_position[i][j++] = CharToPiece(*p);\r
9275             }\r
9276         }\r
9277     \r
9278         blackPlaysFirst = FALSE;\r
9279         if (!feof(f)) {\r
9280             (void) fgets(line, MSG_SIZ, f);\r
9281             if (strncmp(line, "black", strlen("black"))==0)\r
9282               blackPlaysFirst = TRUE;\r
9283         }\r
9284     }\r
9285     startedFromSetupPosition = TRUE;\r
9286     \r
9287     SendToProgram("force\n", &first);\r
9288     CopyBoard(boards[0], initial_position);\r
9289     if (blackPlaysFirst) {\r
9290         currentMove = forwardMostMove = backwardMostMove = 1;\r
9291         strcpy(moveList[0], "");\r
9292         strcpy(parseList[0], "");\r
9293         CopyBoard(boards[1], initial_position);\r
9294         DisplayMessage("", _("Black to play"));\r
9295     } else {\r
9296         currentMove = forwardMostMove = backwardMostMove = 0;\r
9297         DisplayMessage("", _("White to play"));\r
9298     }\r
9299           /* [HGM] copy FEN attributes as well */\r
9300           {   int i;\r
9301               initialRulePlies = FENrulePlies;\r
9302               epStatus[forwardMostMove] = FENepStatus;\r
9303               for( i=0; i< nrCastlingRights; i++ )\r
9304                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];\r
9305           }\r
9306     SendBoard(&first, forwardMostMove);\r
9307     if (appData.debugMode) {\r
9308 int i, j;\r
9309   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}\r
9310   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");\r
9311         fprintf(debugFP, "Load Position\n");\r
9312     }\r
9313 \r
9314     if (positionNumber > 1) {\r
9315         sprintf(line, "%s %d", title, positionNumber);\r
9316         DisplayTitle(line);\r
9317     } else {\r
9318         DisplayTitle(title);\r
9319     }\r
9320     gameMode = EditGame;\r
9321     ModeHighlight();\r
9322     ResetClocks();\r
9323     timeRemaining[0][1] = whiteTimeRemaining;\r
9324     timeRemaining[1][1] = blackTimeRemaining;\r
9325     DrawPosition(FALSE, boards[currentMove]);\r
9326    \r
9327     return TRUE;\r
9328 }\r
9329 \r
9330 \r
9331 void\r
9332 CopyPlayerNameIntoFileName(dest, src)\r
9333      char **dest, *src;\r
9334 {\r
9335     while (*src != NULLCHAR && *src != ',') {\r
9336         if (*src == ' ') {\r
9337             *(*dest)++ = '_';\r
9338             src++;\r
9339         } else {\r
9340             *(*dest)++ = *src++;\r
9341         }\r
9342     }\r
9343 }\r
9344 \r
9345 char *DefaultFileName(ext)\r
9346      char *ext;\r
9347 {\r
9348     static char def[MSG_SIZ];\r
9349     char *p;\r
9350 \r
9351     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {\r
9352         p = def;\r
9353         CopyPlayerNameIntoFileName(&p, gameInfo.white);\r
9354         *p++ = '-';\r
9355         CopyPlayerNameIntoFileName(&p, gameInfo.black);\r
9356         *p++ = '.';\r
9357         strcpy(p, ext);\r
9358     } else {\r
9359         def[0] = NULLCHAR;\r
9360     }\r
9361     return def;\r
9362 }\r
9363 \r
9364 /* Save the current game to the given file */\r
9365 int\r
9366 SaveGameToFile(filename, append)\r
9367      char *filename;\r
9368      int append;\r
9369 {\r
9370     FILE *f;\r
9371     char buf[MSG_SIZ];\r
9372 \r
9373     if (strcmp(filename, "-") == 0) {\r
9374         return SaveGame(stdout, 0, NULL);\r
9375     } else {\r
9376         f = fopen(filename, append ? "a" : "w");\r
9377         if (f == NULL) {\r
9378             sprintf(buf, _("Can't open \"%s\""), filename);\r
9379             DisplayError(buf, errno);\r
9380             return FALSE;\r
9381         } else {\r
9382             return SaveGame(f, 0, NULL);\r
9383         }\r
9384     }\r
9385 }\r
9386 \r
9387 char *\r
9388 SavePart(str)\r
9389      char *str;\r
9390 {\r
9391     static char buf[MSG_SIZ];\r
9392     char *p;\r
9393     \r
9394     p = strchr(str, ' ');\r
9395     if (p == NULL) return str;\r
9396     strncpy(buf, str, p - str);\r
9397     buf[p - str] = NULLCHAR;\r
9398     return buf;\r
9399 }\r
9400 \r
9401 #define PGN_MAX_LINE 75\r
9402 \r
9403 #define PGN_SIDE_WHITE  0\r
9404 #define PGN_SIDE_BLACK  1\r
9405 \r
9406 /* [AS] */\r
9407 static int FindFirstMoveOutOfBook( int side )\r
9408 {\r
9409     int result = -1;\r
9410 \r
9411     if( backwardMostMove == 0 && ! startedFromSetupPosition) {\r
9412         int index = backwardMostMove;\r
9413         int has_book_hit = 0;\r
9414 \r
9415         if( (index % 2) != side ) {\r
9416             index++;\r
9417         }\r
9418 \r
9419         while( index < forwardMostMove ) {\r
9420             /* Check to see if engine is in book */\r
9421             int depth = pvInfoList[index].depth;\r
9422             int score = pvInfoList[index].score;\r
9423             int in_book = 0;\r
9424 \r
9425             if( depth <= 2 ) {\r
9426                 in_book = 1;\r
9427             }\r
9428             else if( score == 0 && depth == 63 ) {\r
9429                 in_book = 1; /* Zappa */\r
9430             }\r
9431             else if( score == 2 && depth == 99 ) {\r
9432                 in_book = 1; /* Abrok */\r
9433             }\r
9434 \r
9435             has_book_hit += in_book;\r
9436 \r
9437             if( ! in_book ) {\r
9438                 result = index;\r
9439 \r
9440                 break;\r
9441             }\r
9442 \r
9443             index += 2;\r
9444         }\r
9445     }\r
9446 \r
9447     return result;\r
9448 }\r
9449 \r
9450 /* [AS] */\r
9451 void GetOutOfBookInfo( char * buf )\r
9452 {\r
9453     int oob[2];\r
9454     int i;\r
9455     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9456 \r
9457     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );\r
9458     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );\r
9459 \r
9460     *buf = '\0';\r
9461 \r
9462     if( oob[0] >= 0 || oob[1] >= 0 ) {\r
9463         for( i=0; i<2; i++ ) {\r
9464             int idx = oob[i];\r
9465 \r
9466             if( idx >= 0 ) {\r
9467                 if( i > 0 && oob[0] >= 0 ) {\r
9468                     strcat( buf, "   " );\r
9469                 }\r
9470 \r
9471                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );\r
9472                 sprintf( buf+strlen(buf), "%s%.2f", \r
9473                     pvInfoList[idx].score >= 0 ? "+" : "",\r
9474                     pvInfoList[idx].score / 100.0 );\r
9475             }\r
9476         }\r
9477     }\r
9478 }\r
9479 \r
9480 /* Save game in PGN style and close the file */\r
9481 int\r
9482 SaveGamePGN(f)\r
9483      FILE *f;\r
9484 {\r
9485     int i, offset, linelen, newblock;\r
9486     time_t tm;\r
9487 //    char *movetext;\r
9488     char numtext[32];\r
9489     int movelen, numlen, blank;\r
9490     char move_buffer[100]; /* [AS] Buffer for move+PV info */\r
9491 \r
9492     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9493     \r
9494     tm = time((time_t *) NULL);\r
9495     \r
9496     PrintPGNTags(f, &gameInfo);\r
9497     \r
9498     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
9499         char *fen = PositionToFEN(backwardMostMove, NULL);\r
9500         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);\r
9501         fprintf(f, "\n{--------------\n");\r
9502         PrintPosition(f, backwardMostMove);\r
9503         fprintf(f, "--------------}\n");\r
9504         free(fen);\r
9505     }\r
9506     else {\r
9507         /* [AS] Out of book annotation */\r
9508         if( appData.saveOutOfBookInfo ) {\r
9509             char buf[64];\r
9510 \r
9511             GetOutOfBookInfo( buf );\r
9512 \r
9513             if( buf[0] != '\0' ) {\r
9514                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); \r
9515             }\r
9516         }\r
9517 \r
9518         fprintf(f, "\n");\r
9519     }\r
9520 \r
9521     i = backwardMostMove;\r
9522     linelen = 0;\r
9523     newblock = TRUE;\r
9524 \r
9525     while (i < forwardMostMove) {\r
9526         /* Print comments preceding this move */\r
9527         if (commentList[i] != NULL) {\r
9528             if (linelen > 0) fprintf(f, "\n");\r
9529             fprintf(f, "{\n%s}\n", commentList[i]);\r
9530             linelen = 0;\r
9531             newblock = TRUE;\r
9532         }\r
9533 \r
9534         /* Format move number */\r
9535         if ((i % 2) == 0) {\r
9536             sprintf(numtext, "%d.", (i - offset)/2 + 1);\r
9537         } else {\r
9538             if (newblock) {\r
9539                 sprintf(numtext, "%d...", (i - offset)/2 + 1);\r
9540             } else {\r
9541                 numtext[0] = NULLCHAR;\r
9542             }\r
9543         }\r
9544         numlen = strlen(numtext);\r
9545         newblock = FALSE;\r
9546 \r
9547         /* Print move number */\r
9548         blank = linelen > 0 && numlen > 0;\r
9549         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {\r
9550             fprintf(f, "\n");\r
9551             linelen = 0;\r
9552             blank = 0;\r
9553         }\r
9554         if (blank) {\r
9555             fprintf(f, " ");\r
9556             linelen++;\r
9557         }\r
9558         fprintf(f, numtext);\r
9559         linelen += numlen;\r
9560 \r
9561         /* Get move */\r
9562         strcpy(move_buffer, parseList[i]); // [HGM] pgn: print move via buffer, so it can be edited\r
9563         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */\r
9564         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {\r
9565                 int p = movelen - 1;\r
9566                 if(move_buffer[p] == ' ') p--;\r
9567                 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info\r
9568                     while(p && move_buffer[--p] != '(');\r
9569                     if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;\r
9570                 }\r
9571         }\r
9572 \r
9573         /* Print move */\r
9574         blank = linelen > 0 && movelen > 0;\r
9575         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
9576             fprintf(f, "\n");\r
9577             linelen = 0;\r
9578             blank = 0;\r
9579         }\r
9580         if (blank) {\r
9581             fprintf(f, " ");\r
9582             linelen++;\r
9583         }\r
9584         fprintf(f, move_buffer);\r
9585         linelen += movelen;\r
9586 \r
9587         /* [AS] Add PV info if present */\r
9588         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {\r
9589             /* [HGM] add time */\r
9590             char buf[MSG_SIZ]; int seconds = 0;\r
9591 \r
9592 #if 1\r
9593             if(i >= backwardMostMove) {\r
9594                 if(WhiteOnMove(i))\r
9595                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]\r
9596                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);\r
9597                 else\r
9598                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]\r
9599                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);\r
9600             }\r
9601             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest\r
9602 #else\r
9603             seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time\r
9604 #endif\r
9605 \r
9606             if( seconds <= 0) buf[0] = 0; else\r
9607             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {\r
9608                 seconds = (seconds + 4)/10; // round to full seconds\r
9609                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else\r
9610                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);\r
9611             }\r
9612 \r
9613             sprintf( move_buffer, "{%s%.2f/%d%s}", \r
9614                 pvInfoList[i].score >= 0 ? "+" : "",\r
9615                 pvInfoList[i].score / 100.0,\r
9616                 pvInfoList[i].depth,\r
9617                 buf );\r
9618 \r
9619             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */\r
9620 \r
9621             /* Print score/depth */\r
9622             blank = linelen > 0 && movelen > 0;\r
9623             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
9624                 fprintf(f, "\n");\r
9625                 linelen = 0;\r
9626                 blank = 0;\r
9627             }\r
9628             if (blank) {\r
9629                 fprintf(f, " ");\r
9630                 linelen++;\r
9631             }\r
9632             fprintf(f, move_buffer);\r
9633             linelen += movelen;\r
9634         }\r
9635 \r
9636         i++;\r
9637     }\r
9638     \r
9639     /* Start a new line */\r
9640     if (linelen > 0) fprintf(f, "\n");\r
9641 \r
9642     /* Print comments after last move */\r
9643     if (commentList[i] != NULL) {\r
9644         fprintf(f, "{\n%s}\n", commentList[i]);\r
9645     }\r
9646 \r
9647     /* Print result */\r
9648     if (gameInfo.resultDetails != NULL &&\r
9649         gameInfo.resultDetails[0] != NULLCHAR) {\r
9650         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,\r
9651                 PGNResult(gameInfo.result));\r
9652     } else {\r
9653         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
9654     }\r
9655 \r
9656     fclose(f);\r
9657     return TRUE;\r
9658 }\r
9659 \r
9660 /* Save game in old style and close the file */\r
9661 int\r
9662 SaveGameOldStyle(f)\r
9663      FILE *f;\r
9664 {\r
9665     int i, offset;\r
9666     time_t tm;\r
9667     \r
9668     tm = time((time_t *) NULL);\r
9669     \r
9670     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));\r
9671     PrintOpponents(f);\r
9672     \r
9673     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
9674         fprintf(f, "\n[--------------\n");\r
9675         PrintPosition(f, backwardMostMove);\r
9676         fprintf(f, "--------------]\n");\r
9677     } else {\r
9678         fprintf(f, "\n");\r
9679     }\r
9680 \r
9681     i = backwardMostMove;\r
9682     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9683 \r
9684     while (i < forwardMostMove) {\r
9685         if (commentList[i] != NULL) {\r
9686             fprintf(f, "[%s]\n", commentList[i]);\r
9687         }\r
9688 \r
9689         if ((i % 2) == 1) {\r
9690             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);\r
9691             i++;\r
9692         } else {\r
9693             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);\r
9694             i++;\r
9695             if (commentList[i] != NULL) {\r
9696                 fprintf(f, "\n");\r
9697                 continue;\r
9698             }\r
9699             if (i >= forwardMostMove) {\r
9700                 fprintf(f, "\n");\r
9701                 break;\r
9702             }\r
9703             fprintf(f, "%s\n", parseList[i]);\r
9704             i++;\r
9705         }\r
9706     }\r
9707     \r
9708     if (commentList[i] != NULL) {\r
9709         fprintf(f, "[%s]\n", commentList[i]);\r
9710     }\r
9711 \r
9712     /* This isn't really the old style, but it's close enough */\r
9713     if (gameInfo.resultDetails != NULL &&\r
9714         gameInfo.resultDetails[0] != NULLCHAR) {\r
9715         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),\r
9716                 gameInfo.resultDetails);\r
9717     } else {\r
9718         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
9719     }\r
9720 \r
9721     fclose(f);\r
9722     return TRUE;\r
9723 }\r
9724 \r
9725 /* Save the current game to open file f and close the file */\r
9726 int\r
9727 SaveGame(f, dummy, dummy2)\r
9728      FILE *f;\r
9729      int dummy;\r
9730      char *dummy2;\r
9731 {\r
9732     if (gameMode == EditPosition) EditPositionDone();\r
9733     if (appData.oldSaveStyle)\r
9734       return SaveGameOldStyle(f);\r
9735     else\r
9736       return SaveGamePGN(f);\r
9737 }\r
9738 \r
9739 /* Save the current position to the given file */\r
9740 int\r
9741 SavePositionToFile(filename)\r
9742      char *filename;\r
9743 {\r
9744     FILE *f;\r
9745     char buf[MSG_SIZ];\r
9746 \r
9747     if (strcmp(filename, "-") == 0) {\r
9748         return SavePosition(stdout, 0, NULL);\r
9749     } else {\r
9750         f = fopen(filename, "a");\r
9751         if (f == NULL) {\r
9752             sprintf(buf, _("Can't open \"%s\""), filename);\r
9753             DisplayError(buf, errno);\r
9754             return FALSE;\r
9755         } else {\r
9756             SavePosition(f, 0, NULL);\r
9757             return TRUE;\r
9758         }\r
9759     }\r
9760 }\r
9761 \r
9762 /* Save the current position to the given open file and close the file */\r
9763 int\r
9764 SavePosition(f, dummy, dummy2)\r
9765      FILE *f;\r
9766      int dummy;\r
9767      char *dummy2;\r
9768 {\r
9769     time_t tm;\r
9770     char *fen;\r
9771     \r
9772     if (appData.oldSaveStyle) {\r
9773         tm = time((time_t *) NULL);\r
9774     \r
9775         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));\r
9776         PrintOpponents(f);\r
9777         fprintf(f, "[--------------\n");\r
9778         PrintPosition(f, currentMove);\r
9779         fprintf(f, "--------------]\n");\r
9780     } else {\r
9781         fen = PositionToFEN(currentMove, NULL);\r
9782         fprintf(f, "%s\n", fen);\r
9783         free(fen);\r
9784     }\r
9785     fclose(f);\r
9786     return TRUE;\r
9787 }\r
9788 \r
9789 void\r
9790 ReloadCmailMsgEvent(unregister)\r
9791      int unregister;\r
9792 {\r
9793 #if !WIN32\r
9794     static char *inFilename = NULL;\r
9795     static char *outFilename;\r
9796     int i;\r
9797     struct stat inbuf, outbuf;\r
9798     int status;\r
9799     \r
9800     /* Any registered moves are unregistered if unregister is set, */\r
9801     /* i.e. invoked by the signal handler */\r
9802     if (unregister) {\r
9803         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
9804             cmailMoveRegistered[i] = FALSE;\r
9805             if (cmailCommentList[i] != NULL) {\r
9806                 free(cmailCommentList[i]);\r
9807                 cmailCommentList[i] = NULL;\r
9808             }\r
9809         }\r
9810         nCmailMovesRegistered = 0;\r
9811     }\r
9812 \r
9813     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
9814         cmailResult[i] = CMAIL_NOT_RESULT;\r
9815     }\r
9816     nCmailResults = 0;\r
9817 \r
9818     if (inFilename == NULL) {\r
9819         /* Because the filenames are static they only get malloced once  */\r
9820         /* and they never get freed                                      */\r
9821         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);\r
9822         sprintf(inFilename, "%s.game.in", appData.cmailGameName);\r
9823 \r
9824         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);\r
9825         sprintf(outFilename, "%s.out", appData.cmailGameName);\r
9826     }\r
9827     \r
9828     status = stat(outFilename, &outbuf);\r
9829     if (status < 0) {\r
9830         cmailMailedMove = FALSE;\r
9831     } else {\r
9832         status = stat(inFilename, &inbuf);\r
9833         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);\r
9834     }\r
9835     \r
9836     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE\r
9837        counts the games, notes how each one terminated, etc.\r
9838        \r
9839        It would be nice to remove this kludge and instead gather all\r
9840        the information while building the game list.  (And to keep it\r
9841        in the game list nodes instead of having a bunch of fixed-size\r
9842        parallel arrays.)  Note this will require getting each game's\r
9843        termination from the PGN tags, as the game list builder does\r
9844        not process the game moves.  --mann\r
9845        */\r
9846     cmailMsgLoaded = TRUE;\r
9847     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);\r
9848     \r
9849     /* Load first game in the file or popup game menu */\r
9850     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);\r
9851 \r
9852 #endif /* !WIN32 */\r
9853     return;\r
9854 }\r
9855 \r
9856 int\r
9857 RegisterMove()\r
9858 {\r
9859     FILE *f;\r
9860     char string[MSG_SIZ];\r
9861 \r
9862     if (   cmailMailedMove\r
9863         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {\r
9864         return TRUE;            /* Allow free viewing  */\r
9865     }\r
9866 \r
9867     /* Unregister move to ensure that we don't leave RegisterMove        */\r
9868     /* with the move registered when the conditions for registering no   */\r
9869     /* longer hold                                                       */\r
9870     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
9871         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
9872         nCmailMovesRegistered --;\r
9873 \r
9874         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) \r
9875           {\r
9876               free(cmailCommentList[lastLoadGameNumber - 1]);\r
9877               cmailCommentList[lastLoadGameNumber - 1] = NULL;\r
9878           }\r
9879     }\r
9880 \r
9881     if (cmailOldMove == -1) {\r
9882         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);\r
9883         return FALSE;\r
9884     }\r
9885 \r
9886     if (currentMove > cmailOldMove + 1) {\r
9887         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);\r
9888         return FALSE;\r
9889     }\r
9890 \r
9891     if (currentMove < cmailOldMove) {\r
9892         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);\r
9893         return FALSE;\r
9894     }\r
9895 \r
9896     if (forwardMostMove > currentMove) {\r
9897         /* Silently truncate extra moves */\r
9898         TruncateGame();\r
9899     }\r
9900 \r
9901     if (   (currentMove == cmailOldMove + 1)\r
9902         || (   (currentMove == cmailOldMove)\r
9903             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)\r
9904                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {\r
9905         if (gameInfo.result != GameUnfinished) {\r
9906             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;\r
9907         }\r
9908 \r
9909         if (commentList[currentMove] != NULL) {\r
9910             cmailCommentList[lastLoadGameNumber - 1]\r
9911               = StrSave(commentList[currentMove]);\r
9912         }\r
9913         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);\r
9914 \r
9915         if (appData.debugMode)\r
9916           fprintf(debugFP, "Saving %s for game %d\n",\r
9917                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
9918 \r
9919         sprintf(string,\r
9920                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);\r
9921         \r
9922         f = fopen(string, "w");\r
9923         if (appData.oldSaveStyle) {\r
9924             SaveGameOldStyle(f); /* also closes the file */\r
9925             \r
9926             sprintf(string, "%s.pos.out", appData.cmailGameName);\r
9927             f = fopen(string, "w");\r
9928             SavePosition(f, 0, NULL); /* also closes the file */\r
9929         } else {\r
9930             fprintf(f, "{--------------\n");\r
9931             PrintPosition(f, currentMove);\r
9932             fprintf(f, "--------------}\n\n");\r
9933             \r
9934             SaveGame(f, 0, NULL); /* also closes the file*/\r
9935         }\r
9936         \r
9937         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;\r
9938         nCmailMovesRegistered ++;\r
9939     } else if (nCmailGames == 1) {\r
9940         DisplayError(_("You have not made a move yet"), 0);\r
9941         return FALSE;\r
9942     }\r
9943 \r
9944     return TRUE;\r
9945 }\r
9946 \r
9947 void\r
9948 MailMoveEvent()\r
9949 {\r
9950 #if !WIN32\r
9951     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";\r
9952     FILE *commandOutput;\r
9953     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];\r
9954     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */\r
9955     int nBuffers;\r
9956     int i;\r
9957     int archived;\r
9958     char *arcDir;\r
9959 \r
9960     if (! cmailMsgLoaded) {\r
9961         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);\r
9962         return;\r
9963     }\r
9964 \r
9965     if (nCmailGames == nCmailResults) {\r
9966         DisplayError(_("No unfinished games"), 0);\r
9967         return;\r
9968     }\r
9969 \r
9970 #if CMAIL_PROHIBIT_REMAIL\r
9971     if (cmailMailedMove) {\r
9972         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
9973         DisplayError(msg, 0);\r
9974         return;\r
9975     }\r
9976 #endif\r
9977 \r
9978     if (! (cmailMailedMove || RegisterMove())) return;\r
9979     \r
9980     if (   cmailMailedMove\r
9981         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {\r
9982         sprintf(string, partCommandString,\r
9983                 appData.debugMode ? " -v" : "", appData.cmailGameName);\r
9984         commandOutput = popen(string, "r");\r
9985 \r
9986         if (commandOutput == NULL) {\r
9987             DisplayError(_("Failed to invoke cmail"), 0);\r
9988         } else {\r
9989             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {\r
9990                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);\r
9991             }\r
9992             if (nBuffers > 1) {\r
9993                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);\r
9994                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);\r
9995                 nBytes = MSG_SIZ - 1;\r
9996             } else {\r
9997                 (void) memcpy(msg, buffer, nBytes);\r
9998             }\r
9999             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/\r
10000 \r
10001             if(StrStr(msg, "Mailed cmail message to ") != NULL) {\r
10002                 cmailMailedMove = TRUE; /* Prevent >1 moves    */\r
10003 \r
10004                 archived = TRUE;\r
10005                 for (i = 0; i < nCmailGames; i ++) {\r
10006                     if (cmailResult[i] == CMAIL_NOT_RESULT) {\r
10007                         archived = FALSE;\r
10008                     }\r
10009                 }\r
10010                 if (   archived\r
10011                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))\r
10012                         != NULL)) {\r
10013                     sprintf(buffer, "%s/%s.%s.archive",\r
10014                             arcDir,\r
10015                             appData.cmailGameName,\r
10016                             gameInfo.date);\r
10017                     LoadGameFromFile(buffer, 1, buffer, FALSE);\r
10018                     cmailMsgLoaded = FALSE;\r
10019                 }\r
10020             }\r
10021 \r
10022             DisplayInformation(msg);\r
10023             pclose(commandOutput);\r
10024         }\r
10025     } else {\r
10026         if ((*cmailMsg) != '\0') {\r
10027             DisplayInformation(cmailMsg);\r
10028         }\r
10029     }\r
10030 \r
10031     return;\r
10032 #endif /* !WIN32 */\r
10033 }\r
10034 \r
10035 char *\r
10036 CmailMsg()\r
10037 {\r
10038 #if WIN32\r
10039     return NULL;\r
10040 #else\r
10041     int  prependComma = 0;\r
10042     char number[5];\r
10043     char string[MSG_SIZ];       /* Space for game-list */\r
10044     int  i;\r
10045     \r
10046     if (!cmailMsgLoaded) return "";\r
10047 \r
10048     if (cmailMailedMove) {\r
10049         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));\r
10050     } else {\r
10051         /* Create a list of games left */\r
10052         sprintf(string, "[");\r
10053         for (i = 0; i < nCmailGames; i ++) {\r
10054             if (! (   cmailMoveRegistered[i]\r
10055                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {\r
10056                 if (prependComma) {\r
10057                     sprintf(number, ",%d", i + 1);\r
10058                 } else {\r
10059                     sprintf(number, "%d", i + 1);\r
10060                     prependComma = 1;\r
10061                 }\r
10062                 \r
10063                 strcat(string, number);\r
10064             }\r
10065         }\r
10066         strcat(string, "]");\r
10067 \r
10068         if (nCmailMovesRegistered + nCmailResults == 0) {\r
10069             switch (nCmailGames) {\r
10070               case 1:\r
10071                 sprintf(cmailMsg,\r
10072                         _("Still need to make move for game\n"));\r
10073                 break;\r
10074                 \r
10075               case 2:\r
10076                 sprintf(cmailMsg,\r
10077                         _("Still need to make moves for both games\n"));\r
10078                 break;\r
10079                 \r
10080               default:\r
10081                 sprintf(cmailMsg,\r
10082                         _("Still need to make moves for all %d games\n"),\r
10083                         nCmailGames);\r
10084                 break;\r
10085             }\r
10086         } else {\r
10087             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {\r
10088               case 1:\r
10089                 sprintf(cmailMsg,\r
10090                         _("Still need to make a move for game %s\n"),\r
10091                         string);\r
10092                 break;\r
10093                 \r
10094               case 0:\r
10095                 if (nCmailResults == nCmailGames) {\r
10096                     sprintf(cmailMsg, _("No unfinished games\n"));\r
10097                 } else {\r
10098                     sprintf(cmailMsg, _("Ready to send mail\n"));\r
10099                 }\r
10100                 break;\r
10101                 \r
10102               default:\r
10103                 sprintf(cmailMsg,\r
10104                         _("Still need to make moves for games %s\n"),\r
10105                         string);\r
10106             }\r
10107         }\r
10108     }\r
10109     return cmailMsg;\r
10110 #endif /* WIN32 */\r
10111 }\r
10112 \r
10113 void\r
10114 ResetGameEvent()\r
10115 {\r
10116     if (gameMode == Training)\r
10117       SetTrainingModeOff();\r
10118 \r
10119     Reset(TRUE, TRUE);\r
10120     cmailMsgLoaded = FALSE;\r
10121     if (appData.icsActive) {\r
10122       SendToICS(ics_prefix);\r
10123       SendToICS("refresh\n");\r
10124     }\r
10125 }\r
10126 \r
10127 void\r
10128 ExitEvent(status)\r
10129      int status;\r
10130 {\r
10131     exiting++;\r
10132     if (exiting > 2) {\r
10133       /* Give up on clean exit */\r
10134       exit(status);\r
10135     }\r
10136     if (exiting > 1) {\r
10137       /* Keep trying for clean exit */\r
10138       return;\r
10139     }\r
10140 \r
10141     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);\r
10142 \r
10143     if (telnetISR != NULL) {\r
10144       RemoveInputSource(telnetISR);\r
10145     }\r
10146     if (icsPR != NoProc) {\r
10147       DestroyChildProcess(icsPR, TRUE);\r
10148     }\r
10149 #if 0\r
10150     /* Save game if resource set and not already saved by GameEnds() */\r
10151     if ((gameInfo.resultDetails == NULL || errorExitFlag )\r
10152                              && forwardMostMove > 0) {\r
10153       if (*appData.saveGameFile != NULLCHAR) {\r
10154         SaveGameToFile(appData.saveGameFile, TRUE);\r
10155       } else if (appData.autoSaveGames) {\r
10156         AutoSaveGame();\r
10157       }\r
10158       if (*appData.savePositionFile != NULLCHAR) {\r
10159         SavePositionToFile(appData.savePositionFile);\r
10160       }\r
10161     }\r
10162     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
10163 #else\r
10164     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */\r
10165     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);\r
10166 #endif\r
10167     /* [HGM] crash: the above GameEnds() is a dud if another one was running */\r
10168     /* make sure this other one finishes before killing it!                  */\r
10169     if(endingGame) { int count = 0;\r
10170         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");\r
10171         while(endingGame && count++ < 10) DoSleep(1);\r
10172         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");\r
10173     }\r
10174 \r
10175     /* Kill off chess programs */\r
10176     if (first.pr != NoProc) {\r
10177         ExitAnalyzeMode();\r
10178         \r
10179         DoSleep( appData.delayBeforeQuit );\r
10180         SendToProgram("quit\n", &first);\r
10181         DoSleep( appData.delayAfterQuit );\r
10182         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );\r
10183     }\r
10184     if (second.pr != NoProc) {\r
10185         DoSleep( appData.delayBeforeQuit );\r
10186         SendToProgram("quit\n", &second);\r
10187         DoSleep( appData.delayAfterQuit );\r
10188         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );\r
10189     }\r
10190     if (first.isr != NULL) {\r
10191         RemoveInputSource(first.isr);\r
10192     }\r
10193     if (second.isr != NULL) {\r
10194         RemoveInputSource(second.isr);\r
10195     }\r
10196 \r
10197     ShutDownFrontEnd();\r
10198     exit(status);\r
10199 }\r
10200 \r
10201 void\r
10202 PauseEvent()\r
10203 {\r
10204     if (appData.debugMode)\r
10205         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);\r
10206     if (pausing) {\r
10207         pausing = FALSE;\r
10208         ModeHighlight();\r
10209         if (gameMode == MachinePlaysWhite ||\r
10210             gameMode == MachinePlaysBlack) {\r
10211             StartClocks();\r
10212         } else {\r
10213             DisplayBothClocks();\r
10214         }\r
10215         if (gameMode == PlayFromGameFile) {\r
10216             if (appData.timeDelay >= 0) \r
10217                 AutoPlayGameLoop();\r
10218         } else if (gameMode == IcsExamining && pauseExamInvalid) {\r
10219             Reset(FALSE, TRUE);\r
10220             SendToICS(ics_prefix);\r
10221             SendToICS("refresh\n");\r
10222         } else if (currentMove < forwardMostMove) {\r
10223             ForwardInner(forwardMostMove);\r
10224         }\r
10225         pauseExamInvalid = FALSE;\r
10226     } else {\r
10227         switch (gameMode) {\r
10228           default:\r
10229             return;\r
10230           case IcsExamining:\r
10231             pauseExamForwardMostMove = forwardMostMove;\r
10232             pauseExamInvalid = FALSE;\r
10233             /* fall through */\r
10234           case IcsObserving:\r
10235           case IcsPlayingWhite:\r
10236           case IcsPlayingBlack:\r
10237             pausing = TRUE;\r
10238             ModeHighlight();\r
10239             return;\r
10240           case PlayFromGameFile:\r
10241             (void) StopLoadGameTimer();\r
10242             pausing = TRUE;\r
10243             ModeHighlight();\r
10244             break;\r
10245           case BeginningOfGame:\r
10246             if (appData.icsActive) return;\r
10247             /* else fall through */\r
10248           case MachinePlaysWhite:\r
10249           case MachinePlaysBlack:\r
10250           case TwoMachinesPlay:\r
10251             if (forwardMostMove == 0)\r
10252               return;           /* don't pause if no one has moved */\r
10253             if ((gameMode == MachinePlaysWhite &&\r
10254                  !WhiteOnMove(forwardMostMove)) ||\r
10255                 (gameMode == MachinePlaysBlack &&\r
10256                  WhiteOnMove(forwardMostMove))) {\r
10257                 StopClocks();\r
10258             }\r
10259             pausing = TRUE;\r
10260             ModeHighlight();\r
10261             break;\r
10262         }\r
10263     }\r
10264 }\r
10265 \r
10266 void\r
10267 EditCommentEvent()\r
10268 {\r
10269     char title[MSG_SIZ];\r
10270 \r
10271     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {\r
10272         strcpy(title, _("Edit comment"));\r
10273     } else {\r
10274         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,\r
10275                 WhiteOnMove(currentMove - 1) ? " " : ".. ",\r
10276                 parseList[currentMove - 1]);\r
10277     }\r
10278 \r
10279     EditCommentPopUp(currentMove, title, commentList[currentMove]);\r
10280 }\r
10281 \r
10282 \r
10283 void\r
10284 EditTagsEvent()\r
10285 {\r
10286     char *tags = PGNTags(&gameInfo);\r
10287     EditTagsPopUp(tags);\r
10288     free(tags);\r
10289 }\r
10290 \r
10291 void\r
10292 AnalyzeModeEvent()\r
10293 {\r
10294     if (appData.noChessProgram || gameMode == AnalyzeMode)\r
10295       return;\r
10296 \r
10297     if (gameMode != AnalyzeFile) {\r
10298         if (!appData.icsEngineAnalyze) {\r
10299                EditGameEvent();\r
10300                if (gameMode != EditGame) return;\r
10301         }\r
10302         ResurrectChessProgram();\r
10303         SendToProgram("analyze\n", &first);\r
10304         first.analyzing = TRUE;\r
10305         /*first.maybeThinking = TRUE;*/\r
10306         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10307         AnalysisPopUp(_("Analysis"),\r
10308                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));\r
10309     }\r
10310     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;\r
10311     pausing = FALSE;\r
10312     ModeHighlight();\r
10313     SetGameInfo();\r
10314 \r
10315     StartAnalysisClock();\r
10316     GetTimeMark(&lastNodeCountTime);\r
10317     lastNodeCount = 0;\r
10318 }\r
10319 \r
10320 void\r
10321 AnalyzeFileEvent()\r
10322 {\r
10323     if (appData.noChessProgram || gameMode == AnalyzeFile)\r
10324       return;\r
10325 \r
10326     if (gameMode != AnalyzeMode) {\r
10327         EditGameEvent();\r
10328         if (gameMode != EditGame) return;\r
10329         ResurrectChessProgram();\r
10330         SendToProgram("analyze\n", &first);\r
10331         first.analyzing = TRUE;\r
10332         /*first.maybeThinking = TRUE;*/\r
10333         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10334         AnalysisPopUp(_("Analysis"),\r
10335                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));\r
10336     }\r
10337     gameMode = AnalyzeFile;\r
10338     pausing = FALSE;\r
10339     ModeHighlight();\r
10340     SetGameInfo();\r
10341 \r
10342     StartAnalysisClock();\r
10343     GetTimeMark(&lastNodeCountTime);\r
10344     lastNodeCount = 0;\r
10345 }\r
10346 \r
10347 void\r
10348 MachineWhiteEvent()\r
10349 {\r
10350     char buf[MSG_SIZ];\r
10351     char *bookHit = NULL;\r
10352 \r
10353     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))\r
10354       return;\r
10355 \r
10356 \r
10357     if (gameMode == PlayFromGameFile || \r
10358         gameMode == TwoMachinesPlay  || \r
10359         gameMode == Training         || \r
10360         gameMode == AnalyzeMode      || \r
10361         gameMode == EndOfGame)\r
10362         EditGameEvent();\r
10363 \r
10364     if (gameMode == EditPosition) \r
10365         EditPositionDone();\r
10366 \r
10367     if (!WhiteOnMove(currentMove)) {\r
10368         DisplayError(_("It is not White's turn"), 0);\r
10369         return;\r
10370     }\r
10371   \r
10372     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
10373       ExitAnalyzeMode();\r
10374 \r
10375     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
10376         gameMode == AnalyzeFile)\r
10377         TruncateGame();\r
10378 \r
10379     ResurrectChessProgram();    /* in case it isn't running */\r
10380     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */\r
10381         gameMode = MachinePlaysWhite;\r
10382         ResetClocks();\r
10383     } else\r
10384     gameMode = MachinePlaysWhite;\r
10385     pausing = FALSE;\r
10386     ModeHighlight();\r
10387     SetGameInfo();\r
10388     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10389     DisplayTitle(buf);\r
10390     if (first.sendName) {\r
10391       sprintf(buf, "name %s\n", gameInfo.black);\r
10392       SendToProgram(buf, &first);\r
10393     }\r
10394     if (first.sendTime) {\r
10395       if (first.useColors) {\r
10396         SendToProgram("black\n", &first); /*gnu kludge*/\r
10397       }\r
10398       SendTimeRemaining(&first, TRUE);\r
10399     }\r
10400     if (first.useColors) {\r
10401       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately\r
10402     }\r
10403     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move\r
10404     SetMachineThinkingEnables();\r
10405     first.maybeThinking = TRUE;\r
10406     StartClocks();\r
10407 \r
10408     if (appData.autoFlipView && !flipView) {\r
10409       flipView = !flipView;\r
10410       DrawPosition(FALSE, NULL);\r
10411       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;\r
10412     }\r
10413 \r
10414     if(bookHit) { // [HGM] book: simulate book reply\r
10415         static char bookMove[MSG_SIZ]; // a bit generous?\r
10416 \r
10417         programStats.nodes = programStats.depth = programStats.time = \r
10418         programStats.score = programStats.got_only_move = 0;\r
10419         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10420 \r
10421         strcpy(bookMove, "move ");\r
10422         strcat(bookMove, bookHit);\r
10423         HandleMachineMove(bookMove, &first);\r
10424     }\r
10425 }\r
10426 \r
10427 void\r
10428 MachineBlackEvent()\r
10429 {\r
10430     char buf[MSG_SIZ];\r
10431    char *bookHit = NULL;\r
10432 \r
10433     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))\r
10434         return;\r
10435 \r
10436 \r
10437     if (gameMode == PlayFromGameFile || \r
10438         gameMode == TwoMachinesPlay  || \r
10439         gameMode == Training         || \r
10440         gameMode == AnalyzeMode      || \r
10441         gameMode == EndOfGame)\r
10442         EditGameEvent();\r
10443 \r
10444     if (gameMode == EditPosition) \r
10445         EditPositionDone();\r
10446 \r
10447     if (WhiteOnMove(currentMove)) {\r
10448         DisplayError(_("It is not Black's turn"), 0);\r
10449         return;\r
10450     }\r
10451     \r
10452     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
10453       ExitAnalyzeMode();\r
10454 \r
10455     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
10456         gameMode == AnalyzeFile)\r
10457         TruncateGame();\r
10458 \r
10459     ResurrectChessProgram();    /* in case it isn't running */\r
10460     gameMode = MachinePlaysBlack;\r
10461     pausing = FALSE;\r
10462     ModeHighlight();\r
10463     SetGameInfo();\r
10464     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10465     DisplayTitle(buf);\r
10466     if (first.sendName) {\r
10467       sprintf(buf, "name %s\n", gameInfo.white);\r
10468       SendToProgram(buf, &first);\r
10469     }\r
10470     if (first.sendTime) {\r
10471       if (first.useColors) {\r
10472         SendToProgram("white\n", &first); /*gnu kludge*/\r
10473       }\r
10474       SendTimeRemaining(&first, FALSE);\r
10475     }\r
10476     if (first.useColors) {\r
10477       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately\r
10478     }\r
10479     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move\r
10480     SetMachineThinkingEnables();\r
10481     first.maybeThinking = TRUE;\r
10482     StartClocks();\r
10483 \r
10484     if (appData.autoFlipView && flipView) {\r
10485       flipView = !flipView;\r
10486       DrawPosition(FALSE, NULL);\r
10487       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;\r
10488     }\r
10489     if(bookHit) { // [HGM] book: simulate book reply\r
10490         static char bookMove[MSG_SIZ]; // a bit generous?\r
10491 \r
10492         programStats.nodes = programStats.depth = programStats.time = \r
10493         programStats.score = programStats.got_only_move = 0;\r
10494         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10495 \r
10496         strcpy(bookMove, "move ");\r
10497         strcat(bookMove, bookHit);\r
10498         HandleMachineMove(bookMove, &first);\r
10499     }\r
10500 }\r
10501 \r
10502 \r
10503 void\r
10504 DisplayTwoMachinesTitle()\r
10505 {\r
10506     char buf[MSG_SIZ];\r
10507     if (appData.matchGames > 0) {\r
10508         if (first.twoMachinesColor[0] == 'w') {\r
10509             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
10510                     gameInfo.white, gameInfo.black,\r
10511                     first.matchWins, second.matchWins,\r
10512                     matchGame - 1 - (first.matchWins + second.matchWins));\r
10513         } else {\r
10514             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
10515                     gameInfo.white, gameInfo.black,\r
10516                     second.matchWins, first.matchWins,\r
10517                     matchGame - 1 - (first.matchWins + second.matchWins));\r
10518         }\r
10519     } else {\r
10520         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10521     }\r
10522     DisplayTitle(buf);\r
10523 }\r
10524 \r
10525 void\r
10526 TwoMachinesEvent P((void))\r
10527 {\r
10528     int i;\r
10529     char buf[MSG_SIZ];\r
10530     ChessProgramState *onmove;\r
10531     char *bookHit = NULL;\r
10532     \r
10533     if (appData.noChessProgram) return;\r
10534 \r
10535     switch (gameMode) {\r
10536       case TwoMachinesPlay:\r
10537         return;\r
10538       case MachinePlaysWhite:\r
10539       case MachinePlaysBlack:\r
10540         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
10541             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);\r
10542             return;\r
10543         }\r
10544         /* fall through */\r
10545       case BeginningOfGame:\r
10546       case PlayFromGameFile:\r
10547       case EndOfGame:\r
10548         EditGameEvent();\r
10549         if (gameMode != EditGame) return;\r
10550         break;\r
10551       case EditPosition:\r
10552         EditPositionDone();\r
10553         break;\r
10554       case AnalyzeMode:\r
10555       case AnalyzeFile:\r
10556         ExitAnalyzeMode();\r
10557         break;\r
10558       case EditGame:\r
10559       default:\r
10560         break;\r
10561     }\r
10562 \r
10563     forwardMostMove = currentMove;\r
10564     ResurrectChessProgram();    /* in case first program isn't running */\r
10565 \r
10566     if (second.pr == NULL) {\r
10567         StartChessProgram(&second);\r
10568         if (second.protocolVersion == 1) {\r
10569           TwoMachinesEventIfReady();\r
10570         } else {\r
10571           /* kludge: allow timeout for initial "feature" command */\r
10572           FreezeUI();\r
10573           DisplayMessage("", _("Starting second chess program"));\r
10574           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);\r
10575         }\r
10576         return;\r
10577     }\r
10578     DisplayMessage("", "");\r
10579     InitChessProgram(&second, FALSE);\r
10580     SendToProgram("force\n", &second);\r
10581     if (startedFromSetupPosition) {\r
10582         SendBoard(&second, backwardMostMove);\r
10583     if (appData.debugMode) {\r
10584         fprintf(debugFP, "Two Machines\n");\r
10585     }\r
10586     }\r
10587     for (i = backwardMostMove; i < forwardMostMove; i++) {\r
10588         SendMoveToProgram(i, &second);\r
10589     }\r
10590 \r
10591     gameMode = TwoMachinesPlay;\r
10592     pausing = FALSE;\r
10593     ModeHighlight();\r
10594     SetGameInfo();\r
10595     DisplayTwoMachinesTitle();\r
10596     firstMove = TRUE;\r
10597     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {\r
10598         onmove = &first;\r
10599     } else {\r
10600         onmove = &second;\r
10601     }\r
10602 \r
10603     SendToProgram(first.computerString, &first);\r
10604     if (first.sendName) {\r
10605       sprintf(buf, "name %s\n", second.tidy);\r
10606       SendToProgram(buf, &first);\r
10607     }\r
10608     SendToProgram(second.computerString, &second);\r
10609     if (second.sendName) {\r
10610       sprintf(buf, "name %s\n", first.tidy);\r
10611       SendToProgram(buf, &second);\r
10612     }\r
10613 \r
10614     ResetClocks();\r
10615     if (!first.sendTime || !second.sendTime) {\r
10616         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
10617         timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
10618     }\r
10619     if (onmove->sendTime) {\r
10620       if (onmove->useColors) {\r
10621         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/\r
10622       }\r
10623       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));\r
10624     }\r
10625     if (onmove->useColors) {\r
10626       SendToProgram(onmove->twoMachinesColor, onmove);\r
10627     }\r
10628     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move\r
10629 //    SendToProgram("go\n", onmove);\r
10630     onmove->maybeThinking = TRUE;\r
10631     SetMachineThinkingEnables();\r
10632 \r
10633     StartClocks();\r
10634 \r
10635     if(bookHit) { // [HGM] book: simulate book reply\r
10636         static char bookMove[MSG_SIZ]; // a bit generous?\r
10637 \r
10638         programStats.nodes = programStats.depth = programStats.time = \r
10639         programStats.score = programStats.got_only_move = 0;\r
10640         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10641 \r
10642         strcpy(bookMove, "move ");\r
10643         strcat(bookMove, bookHit);\r
10644         HandleMachineMove(bookMove, &first);\r
10645     }\r
10646 }\r
10647 \r
10648 void\r
10649 TrainingEvent()\r
10650 {\r
10651     if (gameMode == Training) {\r
10652       SetTrainingModeOff();\r
10653       gameMode = PlayFromGameFile;\r
10654       DisplayMessage("", _("Training mode off"));\r
10655     } else {\r
10656       gameMode = Training;\r
10657       animateTraining = appData.animate;\r
10658 \r
10659       /* make sure we are not already at the end of the game */\r
10660       if (currentMove < forwardMostMove) {\r
10661         SetTrainingModeOn();\r
10662         DisplayMessage("", _("Training mode on"));\r
10663       } else {\r
10664         gameMode = PlayFromGameFile;\r
10665         DisplayError(_("Already at end of game"), 0);\r
10666       }\r
10667     }\r
10668     ModeHighlight();\r
10669 }\r
10670 \r
10671 void\r
10672 IcsClientEvent()\r
10673 {\r
10674     if (!appData.icsActive) return;\r
10675     switch (gameMode) {\r
10676       case IcsPlayingWhite:\r
10677       case IcsPlayingBlack:\r
10678       case IcsObserving:\r
10679       case IcsIdle:\r
10680       case BeginningOfGame:\r
10681       case IcsExamining:\r
10682         return;\r
10683 \r
10684       case EditGame:\r
10685         break;\r
10686 \r
10687       case EditPosition:\r
10688         EditPositionDone();\r
10689         break;\r
10690 \r
10691       case AnalyzeMode:\r
10692       case AnalyzeFile:\r
10693         ExitAnalyzeMode();\r
10694         break;\r
10695         \r
10696       default:\r
10697         EditGameEvent();\r
10698         break;\r
10699     }\r
10700 \r
10701     gameMode = IcsIdle;\r
10702     ModeHighlight();\r
10703     return;\r
10704 }\r
10705 \r
10706 \r
10707 void\r
10708 EditGameEvent()\r
10709 {\r
10710     int i;\r
10711 \r
10712     switch (gameMode) {\r
10713       case Training:\r
10714         SetTrainingModeOff();\r
10715         break;\r
10716       case MachinePlaysWhite:\r
10717       case MachinePlaysBlack:\r
10718       case BeginningOfGame:\r
10719         SendToProgram("force\n", &first);\r
10720         SetUserThinkingEnables();\r
10721         break;\r
10722       case PlayFromGameFile:\r
10723         (void) StopLoadGameTimer();\r
10724         if (gameFileFP != NULL) {\r
10725             gameFileFP = NULL;\r
10726         }\r
10727         break;\r
10728       case EditPosition:\r
10729         EditPositionDone();\r
10730         break;\r
10731       case AnalyzeMode:\r
10732       case AnalyzeFile:\r
10733         ExitAnalyzeMode();\r
10734         SendToProgram("force\n", &first);\r
10735         break;\r
10736       case TwoMachinesPlay:\r
10737         GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
10738         ResurrectChessProgram();\r
10739         SetUserThinkingEnables();\r
10740         break;\r
10741       case EndOfGame:\r
10742         ResurrectChessProgram();\r
10743         break;\r
10744       case IcsPlayingBlack:\r
10745       case IcsPlayingWhite:\r
10746         DisplayError(_("Warning: You are still playing a game"), 0);\r
10747         break;\r
10748       case IcsObserving:\r
10749         DisplayError(_("Warning: You are still observing a game"), 0);\r
10750         break;\r
10751       case IcsExamining:\r
10752         DisplayError(_("Warning: You are still examining a game"), 0);\r
10753         break;\r
10754       case IcsIdle:\r
10755         break;\r
10756       case EditGame:\r
10757       default:\r
10758         return;\r
10759     }\r
10760     \r
10761     pausing = FALSE;\r
10762     StopClocks();\r
10763     first.offeredDraw = second.offeredDraw = 0;\r
10764 \r
10765     if (gameMode == PlayFromGameFile) {\r
10766         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10767         blackTimeRemaining = timeRemaining[1][currentMove];\r
10768         DisplayTitle("");\r
10769     }\r
10770 \r
10771     if (gameMode == MachinePlaysWhite ||\r
10772         gameMode == MachinePlaysBlack ||\r
10773         gameMode == TwoMachinesPlay ||\r
10774         gameMode == EndOfGame) {\r
10775         i = forwardMostMove;\r
10776         while (i > currentMove) {\r
10777             SendToProgram("undo\n", &first);\r
10778             i--;\r
10779         }\r
10780         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10781         blackTimeRemaining = timeRemaining[1][currentMove];\r
10782         DisplayBothClocks();\r
10783         if (whiteFlag || blackFlag) {\r
10784             whiteFlag = blackFlag = 0;\r
10785         }\r
10786         DisplayTitle("");\r
10787     }           \r
10788     \r
10789     gameMode = EditGame;\r
10790     ModeHighlight();\r
10791     SetGameInfo();\r
10792 }\r
10793 \r
10794 \r
10795 void\r
10796 EditPositionEvent()\r
10797 {\r
10798     if (gameMode == EditPosition) {\r
10799         EditGameEvent();\r
10800         return;\r
10801     }\r
10802     \r
10803     EditGameEvent();\r
10804     if (gameMode != EditGame) return;\r
10805     \r
10806     gameMode = EditPosition;\r
10807     ModeHighlight();\r
10808     SetGameInfo();\r
10809     if (currentMove > 0)\r
10810       CopyBoard(boards[0], boards[currentMove]);\r
10811     \r
10812     blackPlaysFirst = !WhiteOnMove(currentMove);\r
10813     ResetClocks();\r
10814     currentMove = forwardMostMove = backwardMostMove = 0;\r
10815     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
10816     DisplayMove(-1);\r
10817 }\r
10818 \r
10819 void\r
10820 ExitAnalyzeMode()\r
10821 {\r
10822     /* [DM] icsEngineAnalyze - possible call from other functions */\r
10823     if (appData.icsEngineAnalyze) {\r
10824         appData.icsEngineAnalyze = FALSE;\r
10825 \r
10826         DisplayMessage("",_("Close ICS engine analyze..."));\r
10827     }\r
10828     if (first.analysisSupport && first.analyzing) {\r
10829       SendToProgram("exit\n", &first);\r
10830       first.analyzing = FALSE;\r
10831     }\r
10832     AnalysisPopDown();\r
10833     thinkOutput[0] = NULLCHAR;\r
10834 }\r
10835 \r
10836 void\r
10837 EditPositionDone()\r
10838 {\r
10839     startedFromSetupPosition = TRUE;\r
10840     InitChessProgram(&first, FALSE);\r
10841     SendToProgram("force\n", &first);\r
10842     if (blackPlaysFirst) {\r
10843         strcpy(moveList[0], "");\r
10844         strcpy(parseList[0], "");\r
10845         currentMove = forwardMostMove = backwardMostMove = 1;\r
10846         CopyBoard(boards[1], boards[0]);\r
10847         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */\r
10848         { int i;\r
10849           epStatus[1] = epStatus[0];\r
10850           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];\r
10851         }\r
10852     } else {\r
10853         currentMove = forwardMostMove = backwardMostMove = 0;\r
10854     }\r
10855     SendBoard(&first, forwardMostMove);\r
10856     if (appData.debugMode) {\r
10857         fprintf(debugFP, "EditPosDone\n");\r
10858     }\r
10859     DisplayTitle("");\r
10860     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
10861     timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
10862     gameMode = EditGame;\r
10863     ModeHighlight();\r
10864     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
10865     ClearHighlights(); /* [AS] */\r
10866 }\r
10867 \r
10868 /* Pause for `ms' milliseconds */\r
10869 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
10870 void\r
10871 TimeDelay(ms)\r
10872      long ms;\r
10873 {\r
10874     TimeMark m1, m2;\r
10875 \r
10876     GetTimeMark(&m1);\r
10877     do {\r
10878         GetTimeMark(&m2);\r
10879     } while (SubtractTimeMarks(&m2, &m1) < ms);\r
10880 }\r
10881 \r
10882 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
10883 void\r
10884 SendMultiLineToICS(buf)\r
10885      char *buf;\r
10886 {\r
10887     char temp[MSG_SIZ+1], *p;\r
10888     int len;\r
10889 \r
10890     len = strlen(buf);\r
10891     if (len > MSG_SIZ)\r
10892       len = MSG_SIZ;\r
10893   \r
10894     strncpy(temp, buf, len);\r
10895     temp[len] = 0;\r
10896 \r
10897     p = temp;\r
10898     while (*p) {\r
10899         if (*p == '\n' || *p == '\r')\r
10900           *p = ' ';\r
10901         ++p;\r
10902     }\r
10903 \r
10904     strcat(temp, "\n");\r
10905     SendToICS(temp);\r
10906     SendToPlayer(temp, strlen(temp));\r
10907 }\r
10908 \r
10909 void\r
10910 SetWhiteToPlayEvent()\r
10911 {\r
10912     if (gameMode == EditPosition) {\r
10913         blackPlaysFirst = FALSE;\r
10914         DisplayBothClocks();    /* works because currentMove is 0 */\r
10915     } else if (gameMode == IcsExamining) {\r
10916         SendToICS(ics_prefix);\r
10917         SendToICS("tomove white\n");\r
10918     }\r
10919 }\r
10920 \r
10921 void\r
10922 SetBlackToPlayEvent()\r
10923 {\r
10924     if (gameMode == EditPosition) {\r
10925         blackPlaysFirst = TRUE;\r
10926         currentMove = 1;        /* kludge */\r
10927         DisplayBothClocks();\r
10928         currentMove = 0;\r
10929     } else if (gameMode == IcsExamining) {\r
10930         SendToICS(ics_prefix);\r
10931         SendToICS("tomove black\n");\r
10932     }\r
10933 }\r
10934 \r
10935 void\r
10936 EditPositionMenuEvent(selection, x, y)\r
10937      ChessSquare selection;\r
10938      int x, y;\r
10939 {\r
10940     char buf[MSG_SIZ];\r
10941     ChessSquare piece = boards[0][y][x];\r
10942 \r
10943     if (gameMode != EditPosition && gameMode != IcsExamining) return;\r
10944 \r
10945     switch (selection) {\r
10946       case ClearBoard:\r
10947         if (gameMode == IcsExamining && ics_type == ICS_FICS) {\r
10948             SendToICS(ics_prefix);\r
10949             SendToICS("bsetup clear\n");\r
10950         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {\r
10951             SendToICS(ics_prefix);\r
10952             SendToICS("clearboard\n");\r
10953         } else {\r
10954             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;\r
10955                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */\r
10956                 for (y = 0; y < BOARD_HEIGHT; y++) {\r
10957                     if (gameMode == IcsExamining) {\r
10958                         if (boards[currentMove][y][x] != EmptySquare) {\r
10959                             sprintf(buf, "%sx@%c%c\n", ics_prefix,\r
10960                                     AAA + x, ONE + y);\r
10961                             SendToICS(buf);\r
10962                         }\r
10963                     } else {\r
10964                         boards[0][y][x] = p;\r
10965                     }\r
10966                 }\r
10967             }\r
10968         }\r
10969         if (gameMode == EditPosition) {\r
10970             DrawPosition(FALSE, boards[0]);\r
10971         }\r
10972         break;\r
10973 \r
10974       case WhitePlay:\r
10975         SetWhiteToPlayEvent();\r
10976         break;\r
10977 \r
10978       case BlackPlay:\r
10979         SetBlackToPlayEvent();\r
10980         break;\r
10981 \r
10982       case EmptySquare:\r
10983         if (gameMode == IcsExamining) {\r
10984             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);\r
10985             SendToICS(buf);\r
10986         } else {\r
10987             boards[0][y][x] = EmptySquare;\r
10988             DrawPosition(FALSE, boards[0]);\r
10989         }\r
10990         break;\r
10991 \r
10992       case PromotePiece:\r
10993         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||\r
10994            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {\r
10995             selection = (ChessSquare) (PROMOTED piece);\r
10996         } else if(piece == EmptySquare) selection = WhiteSilver;\r
10997         else selection = (ChessSquare)((int)piece - 1);\r
10998         goto defaultlabel;\r
10999 \r
11000       case DemotePiece:\r
11001         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||\r
11002            piece > (int)BlackMan && piece <= (int)BlackKing   ) {\r
11003             selection = (ChessSquare) (DEMOTED piece);\r
11004         } else if(piece == EmptySquare) selection = BlackSilver;\r
11005         else selection = (ChessSquare)((int)piece + 1);       \r
11006         goto defaultlabel;\r
11007 \r
11008       case WhiteQueen:\r
11009       case BlackQueen:\r
11010         if(gameInfo.variant == VariantShatranj ||\r
11011            gameInfo.variant == VariantXiangqi  ||\r
11012            gameInfo.variant == VariantCourier    )\r
11013             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);\r
11014         goto defaultlabel;\r
11015 \r
11016       case WhiteKing:\r
11017       case BlackKing:\r
11018         if(gameInfo.variant == VariantXiangqi)\r
11019             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);\r
11020         if(gameInfo.variant == VariantKnightmate)\r
11021             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);\r
11022       default:\r
11023         defaultlabel:\r
11024         if (gameMode == IcsExamining) {\r
11025             sprintf(buf, "%s%c@%c%c\n", ics_prefix,\r
11026                     PieceToChar(selection), AAA + x, ONE + y);\r
11027             SendToICS(buf);\r
11028         } else {\r
11029             boards[0][y][x] = selection;\r
11030             DrawPosition(FALSE, boards[0]);\r
11031         }\r
11032         break;\r
11033     }\r
11034 }\r
11035 \r
11036 \r
11037 void\r
11038 DropMenuEvent(selection, x, y)\r
11039      ChessSquare selection;\r
11040      int x, y;\r
11041 {\r
11042     ChessMove moveType;\r
11043 \r
11044     switch (gameMode) {\r
11045       case IcsPlayingWhite:\r
11046       case MachinePlaysBlack:\r
11047         if (!WhiteOnMove(currentMove)) {\r
11048             DisplayMoveError(_("It is Black's turn"));\r
11049             return;\r
11050         }\r
11051         moveType = WhiteDrop;\r
11052         break;\r
11053       case IcsPlayingBlack:\r
11054       case MachinePlaysWhite:\r
11055         if (WhiteOnMove(currentMove)) {\r
11056             DisplayMoveError(_("It is White's turn"));\r
11057             return;\r
11058         }\r
11059         moveType = BlackDrop;\r
11060         break;\r
11061       case EditGame:\r
11062         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
11063         break;\r
11064       default:\r
11065         return;\r
11066     }\r
11067 \r
11068     if (moveType == BlackDrop && selection < BlackPawn) {\r
11069       selection = (ChessSquare) ((int) selection\r
11070                                  + (int) BlackPawn - (int) WhitePawn);\r
11071     }\r
11072     if (boards[currentMove][y][x] != EmptySquare) {\r
11073         DisplayMoveError(_("That square is occupied"));\r
11074         return;\r
11075     }\r
11076 \r
11077     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);\r
11078 }\r
11079 \r
11080 void\r
11081 AcceptEvent()\r
11082 {\r
11083     /* Accept a pending offer of any kind from opponent */\r
11084     \r
11085     if (appData.icsActive) {\r
11086         SendToICS(ics_prefix);\r
11087         SendToICS("accept\n");\r
11088     } else if (cmailMsgLoaded) {\r
11089         if (currentMove == cmailOldMove &&\r
11090             commentList[cmailOldMove] != NULL &&\r
11091             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11092                    "Black offers a draw" : "White offers a draw")) {\r
11093             TruncateGame();\r
11094             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
11095             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
11096         } else {\r
11097             DisplayError(_("There is no pending offer on this move"), 0);\r
11098             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
11099         }\r
11100     } else {\r
11101         /* Not used for offers from chess program */\r
11102     }\r
11103 }\r
11104 \r
11105 void\r
11106 DeclineEvent()\r
11107 {\r
11108     /* Decline a pending offer of any kind from opponent */\r
11109     \r
11110     if (appData.icsActive) {\r
11111         SendToICS(ics_prefix);\r
11112         SendToICS("decline\n");\r
11113     } else if (cmailMsgLoaded) {\r
11114         if (currentMove == cmailOldMove &&\r
11115             commentList[cmailOldMove] != NULL &&\r
11116             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11117                    "Black offers a draw" : "White offers a draw")) {\r
11118 #ifdef NOTDEF\r
11119             AppendComment(cmailOldMove, "Draw declined");\r
11120             DisplayComment(cmailOldMove - 1, "Draw declined");\r
11121 #endif /*NOTDEF*/\r
11122         } else {\r
11123             DisplayError(_("There is no pending offer on this move"), 0);\r
11124         }\r
11125     } else {\r
11126         /* Not used for offers from chess program */\r
11127     }\r
11128 }\r
11129 \r
11130 void\r
11131 RematchEvent()\r
11132 {\r
11133     /* Issue ICS rematch command */\r
11134     if (appData.icsActive) {\r
11135         SendToICS(ics_prefix);\r
11136         SendToICS("rematch\n");\r
11137     }\r
11138 }\r
11139 \r
11140 void\r
11141 CallFlagEvent()\r
11142 {\r
11143     /* Call your opponent's flag (claim a win on time) */\r
11144     if (appData.icsActive) {\r
11145         SendToICS(ics_prefix);\r
11146         SendToICS("flag\n");\r
11147     } else {\r
11148         switch (gameMode) {\r
11149           default:\r
11150             return;\r
11151           case MachinePlaysWhite:\r
11152             if (whiteFlag) {\r
11153                 if (blackFlag)\r
11154                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
11155                            GE_PLAYER);\r
11156                 else\r
11157                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);\r
11158             } else {\r
11159                 DisplayError(_("Your opponent is not out of time"), 0);\r
11160             }\r
11161             break;\r
11162           case MachinePlaysBlack:\r
11163             if (blackFlag) {\r
11164                 if (whiteFlag)\r
11165                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
11166                            GE_PLAYER);\r
11167                 else\r
11168                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);\r
11169             } else {\r
11170                 DisplayError(_("Your opponent is not out of time"), 0);\r
11171             }\r
11172             break;\r
11173         }\r
11174     }\r
11175 }\r
11176 \r
11177 void\r
11178 DrawEvent()\r
11179 {\r
11180     /* Offer draw or accept pending draw offer from opponent */\r
11181     \r
11182     if (appData.icsActive) {\r
11183         /* Note: tournament rules require draw offers to be\r
11184            made after you make your move but before you punch\r
11185            your clock.  Currently ICS doesn't let you do that;\r
11186            instead, you immediately punch your clock after making\r
11187            a move, but you can offer a draw at any time. */\r
11188         \r
11189         SendToICS(ics_prefix);\r
11190         SendToICS("draw\n");\r
11191     } else if (cmailMsgLoaded) {\r
11192         if (currentMove == cmailOldMove &&\r
11193             commentList[cmailOldMove] != NULL &&\r
11194             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11195                    "Black offers a draw" : "White offers a draw")) {\r
11196             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
11197             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
11198         } else if (currentMove == cmailOldMove + 1) {\r
11199             char *offer = WhiteOnMove(cmailOldMove) ?\r
11200               "White offers a draw" : "Black offers a draw";\r
11201             AppendComment(currentMove, offer);\r
11202             DisplayComment(currentMove - 1, offer);\r
11203             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;\r
11204         } else {\r
11205             DisplayError(_("You must make your move before offering a draw"), 0);\r
11206             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
11207         }\r
11208     } else if (first.offeredDraw) {\r
11209         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
11210     } else {\r
11211         if (first.sendDrawOffers) {\r
11212             SendToProgram("draw\n", &first);\r
11213             userOfferedDraw = TRUE;\r
11214         }\r
11215     }\r
11216 }\r
11217 \r
11218 void\r
11219 AdjournEvent()\r
11220 {\r
11221     /* Offer Adjourn or accept pending Adjourn offer from opponent */\r
11222     \r
11223     if (appData.icsActive) {\r
11224         SendToICS(ics_prefix);\r
11225         SendToICS("adjourn\n");\r
11226     } else {\r
11227         /* Currently GNU Chess doesn't offer or accept Adjourns */\r
11228     }\r
11229 }\r
11230 \r
11231 \r
11232 void\r
11233 AbortEvent()\r
11234 {\r
11235     /* Offer Abort or accept pending Abort offer from opponent */\r
11236     \r
11237     if (appData.icsActive) {\r
11238         SendToICS(ics_prefix);\r
11239         SendToICS("abort\n");\r
11240     } else {\r
11241         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);\r
11242     }\r
11243 }\r
11244 \r
11245 void\r
11246 ResignEvent()\r
11247 {\r
11248     /* Resign.  You can do this even if it's not your turn. */\r
11249     \r
11250     if (appData.icsActive) {\r
11251         SendToICS(ics_prefix);\r
11252         SendToICS("resign\n");\r
11253     } else {\r
11254         switch (gameMode) {\r
11255           case MachinePlaysWhite:\r
11256             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
11257             break;\r
11258           case MachinePlaysBlack:\r
11259             GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
11260             break;\r
11261           case EditGame:\r
11262             if (cmailMsgLoaded) {\r
11263                 TruncateGame();\r
11264                 if (WhiteOnMove(cmailOldMove)) {\r
11265                     GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
11266                 } else {\r
11267                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
11268                 }\r
11269                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;\r
11270             }\r
11271             break;\r
11272           default:\r
11273             break;\r
11274         }\r
11275     }\r
11276 }\r
11277 \r
11278 \r
11279 void\r
11280 StopObservingEvent()\r
11281 {\r
11282     /* Stop observing current games */\r
11283     SendToICS(ics_prefix);\r
11284     SendToICS("unobserve\n");\r
11285 }\r
11286 \r
11287 void\r
11288 StopExaminingEvent()\r
11289 {\r
11290     /* Stop observing current game */\r
11291     SendToICS(ics_prefix);\r
11292     SendToICS("unexamine\n");\r
11293 }\r
11294 \r
11295 void\r
11296 ForwardInner(target)\r
11297      int target;\r
11298 {\r
11299     int limit;\r
11300 \r
11301     if (appData.debugMode)\r
11302         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",\r
11303                 target, currentMove, forwardMostMove);\r
11304 \r
11305     if (gameMode == EditPosition)\r
11306       return;\r
11307 \r
11308     if (gameMode == PlayFromGameFile && !pausing)\r
11309       PauseEvent();\r
11310     \r
11311     if (gameMode == IcsExamining && pausing)\r
11312       limit = pauseExamForwardMostMove;\r
11313     else\r
11314       limit = forwardMostMove;\r
11315     \r
11316     if (target > limit) target = limit;\r
11317 \r
11318     if (target > 0 && moveList[target - 1][0]) {\r
11319         int fromX, fromY, toX, toY;\r
11320         toX = moveList[target - 1][2] - AAA;\r
11321         toY = moveList[target - 1][3] - ONE;\r
11322         if (moveList[target - 1][1] == '@') {\r
11323             if (appData.highlightLastMove) {\r
11324                 SetHighlights(-1, -1, toX, toY);\r
11325             }\r
11326         } else {\r
11327             fromX = moveList[target - 1][0] - AAA;\r
11328             fromY = moveList[target - 1][1] - ONE;\r
11329             if (target == currentMove + 1) {\r
11330                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
11331             }\r
11332             if (appData.highlightLastMove) {\r
11333                 SetHighlights(fromX, fromY, toX, toY);\r
11334             }\r
11335         }\r
11336     }\r
11337     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
11338         gameMode == Training || gameMode == PlayFromGameFile || \r
11339         gameMode == AnalyzeFile) {\r
11340         while (currentMove < target) {\r
11341             SendMoveToProgram(currentMove++, &first);\r
11342         }\r
11343     } else {\r
11344         currentMove = target;\r
11345     }\r
11346     \r
11347     if (gameMode == EditGame || gameMode == EndOfGame) {\r
11348         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11349         blackTimeRemaining = timeRemaining[1][currentMove];\r
11350     }\r
11351     DisplayBothClocks();\r
11352     DisplayMove(currentMove - 1);\r
11353     DrawPosition(FALSE, boards[currentMove]);\r
11354     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
11355     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty\r
11356         DisplayComment(currentMove - 1, commentList[currentMove]);\r
11357     }\r
11358 }\r
11359 \r
11360 \r
11361 void\r
11362 ForwardEvent()\r
11363 {\r
11364     if (gameMode == IcsExamining && !pausing) {\r
11365         SendToICS(ics_prefix);\r
11366         SendToICS("forward\n");\r
11367     } else {\r
11368         ForwardInner(currentMove + 1);\r
11369     }\r
11370 }\r
11371 \r
11372 void\r
11373 ToEndEvent()\r
11374 {\r
11375     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11376         /* to optimze, we temporarily turn off analysis mode while we feed\r
11377          * the remaining moves to the engine. Otherwise we get analysis output\r
11378          * after each move.\r
11379          */ \r
11380         if (first.analysisSupport) {\r
11381           SendToProgram("exit\nforce\n", &first);\r
11382           first.analyzing = FALSE;\r
11383         }\r
11384     }\r
11385         \r
11386     if (gameMode == IcsExamining && !pausing) {\r
11387         SendToICS(ics_prefix);\r
11388         SendToICS("forward 999999\n");\r
11389     } else {\r
11390         ForwardInner(forwardMostMove);\r
11391     }\r
11392 \r
11393     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11394         /* we have fed all the moves, so reactivate analysis mode */\r
11395         SendToProgram("analyze\n", &first);\r
11396         first.analyzing = TRUE;\r
11397         /*first.maybeThinking = TRUE;*/\r
11398         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
11399     }\r
11400 }\r
11401 \r
11402 void\r
11403 BackwardInner(target)\r
11404      int target;\r
11405 {\r
11406     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */\r
11407 \r
11408     if (appData.debugMode)\r
11409         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",\r
11410                 target, currentMove, forwardMostMove);\r
11411 \r
11412     if (gameMode == EditPosition) return;\r
11413     if (currentMove <= backwardMostMove) {\r
11414         ClearHighlights();\r
11415         DrawPosition(full_redraw, boards[currentMove]);\r
11416         return;\r
11417     }\r
11418     if (gameMode == PlayFromGameFile && !pausing)\r
11419       PauseEvent();\r
11420     \r
11421     if (moveList[target][0]) {\r
11422         int fromX, fromY, toX, toY;\r
11423         toX = moveList[target][2] - AAA;\r
11424         toY = moveList[target][3] - ONE;\r
11425         if (moveList[target][1] == '@') {\r
11426             if (appData.highlightLastMove) {\r
11427                 SetHighlights(-1, -1, toX, toY);\r
11428             }\r
11429         } else {\r
11430             fromX = moveList[target][0] - AAA;\r
11431             fromY = moveList[target][1] - ONE;\r
11432             if (target == currentMove - 1) {\r
11433                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);\r
11434             }\r
11435             if (appData.highlightLastMove) {\r
11436                 SetHighlights(fromX, fromY, toX, toY);\r
11437             }\r
11438         }\r
11439     }\r
11440     if (gameMode == EditGame || gameMode==AnalyzeMode ||\r
11441         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
11442         while (currentMove > target) {\r
11443             SendToProgram("undo\n", &first);\r
11444             currentMove--;\r
11445         }\r
11446     } else {\r
11447         currentMove = target;\r
11448     }\r
11449     \r
11450     if (gameMode == EditGame || gameMode == EndOfGame) {\r
11451         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11452         blackTimeRemaining = timeRemaining[1][currentMove];\r
11453     }\r
11454     DisplayBothClocks();\r
11455     DisplayMove(currentMove - 1);\r
11456     DrawPosition(full_redraw, boards[currentMove]);\r
11457     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
11458     // [HGM] PV info: routine tests if comment empty\r
11459     DisplayComment(currentMove - 1, commentList[currentMove]);\r
11460 }\r
11461 \r
11462 void\r
11463 BackwardEvent()\r
11464 {\r
11465     if (gameMode == IcsExamining && !pausing) {\r
11466         SendToICS(ics_prefix);\r
11467         SendToICS("backward\n");\r
11468     } else {\r
11469         BackwardInner(currentMove - 1);\r
11470     }\r
11471 }\r
11472 \r
11473 void\r
11474 ToStartEvent()\r
11475 {\r
11476     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11477         /* to optimze, we temporarily turn off analysis mode while we undo\r
11478          * all the moves. Otherwise we get analysis output after each undo.\r
11479          */ \r
11480         if (first.analysisSupport) {\r
11481           SendToProgram("exit\nforce\n", &first);\r
11482           first.analyzing = FALSE;\r
11483         }\r
11484     }\r
11485 \r
11486     if (gameMode == IcsExamining && !pausing) {\r
11487         SendToICS(ics_prefix);\r
11488         SendToICS("backward 999999\n");\r
11489     } else {\r
11490         BackwardInner(backwardMostMove);\r
11491     }\r
11492 \r
11493     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11494         /* we have fed all the moves, so reactivate analysis mode */\r
11495         SendToProgram("analyze\n", &first);\r
11496         first.analyzing = TRUE;\r
11497         /*first.maybeThinking = TRUE;*/\r
11498         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
11499     }\r
11500 }\r
11501 \r
11502 void\r
11503 ToNrEvent(int to)\r
11504 {\r
11505   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();\r
11506   if (to >= forwardMostMove) to = forwardMostMove;\r
11507   if (to <= backwardMostMove) to = backwardMostMove;\r
11508   if (to < currentMove) {\r
11509     BackwardInner(to);\r
11510   } else {\r
11511     ForwardInner(to);\r
11512   }\r
11513 }\r
11514 \r
11515 void\r
11516 RevertEvent()\r
11517 {\r
11518     if (gameMode != IcsExamining) {\r
11519         DisplayError(_("You are not examining a game"), 0);\r
11520         return;\r
11521     }\r
11522     if (pausing) {\r
11523         DisplayError(_("You can't revert while pausing"), 0);\r
11524         return;\r
11525     }\r
11526     SendToICS(ics_prefix);\r
11527     SendToICS("revert\n");\r
11528 }\r
11529 \r
11530 void\r
11531 RetractMoveEvent()\r
11532 {\r
11533     switch (gameMode) {\r
11534       case MachinePlaysWhite:\r
11535       case MachinePlaysBlack:\r
11536         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
11537             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);\r
11538             return;\r
11539         }\r
11540         if (forwardMostMove < 2) return;\r
11541         currentMove = forwardMostMove = forwardMostMove - 2;\r
11542         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11543         blackTimeRemaining = timeRemaining[1][currentMove];\r
11544         DisplayBothClocks();\r
11545         DisplayMove(currentMove - 1);\r
11546         ClearHighlights();/*!! could figure this out*/\r
11547         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */\r
11548         SendToProgram("remove\n", &first);\r
11549         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */\r
11550         break;\r
11551 \r
11552       case BeginningOfGame:\r
11553       default:\r
11554         break;\r
11555 \r
11556       case IcsPlayingWhite:\r
11557       case IcsPlayingBlack:\r
11558         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {\r
11559             SendToICS(ics_prefix);\r
11560             SendToICS("takeback 2\n");\r
11561         } else {\r
11562             SendToICS(ics_prefix);\r
11563             SendToICS("takeback 1\n");\r
11564         }\r
11565         break;\r
11566     }\r
11567 }\r
11568 \r
11569 void\r
11570 MoveNowEvent()\r
11571 {\r
11572     ChessProgramState *cps;\r
11573 \r
11574     switch (gameMode) {\r
11575       case MachinePlaysWhite:\r
11576         if (!WhiteOnMove(forwardMostMove)) {\r
11577             DisplayError(_("It is your turn"), 0);\r
11578             return;\r
11579         }\r
11580         cps = &first;\r
11581         break;\r
11582       case MachinePlaysBlack:\r
11583         if (WhiteOnMove(forwardMostMove)) {\r
11584             DisplayError(_("It is your turn"), 0);\r
11585             return;\r
11586         }\r
11587         cps = &first;\r
11588         break;\r
11589       case TwoMachinesPlay:\r
11590         if (WhiteOnMove(forwardMostMove) ==\r
11591             (first.twoMachinesColor[0] == 'w')) {\r
11592             cps = &first;\r
11593         } else {\r
11594             cps = &second;\r
11595         }\r
11596         break;\r
11597       case BeginningOfGame:\r
11598       default:\r
11599         return;\r
11600     }\r
11601     SendToProgram("?\n", cps);\r
11602 }\r
11603 \r
11604 void\r
11605 TruncateGameEvent()\r
11606 {\r
11607     EditGameEvent();\r
11608     if (gameMode != EditGame) return;\r
11609     TruncateGame();\r
11610 }\r
11611 \r
11612 void\r
11613 TruncateGame()\r
11614 {\r
11615     if (forwardMostMove > currentMove) {\r
11616         if (gameInfo.resultDetails != NULL) {\r
11617             free(gameInfo.resultDetails);\r
11618             gameInfo.resultDetails = NULL;\r
11619             gameInfo.result = GameUnfinished;\r
11620         }\r
11621         forwardMostMove = currentMove;\r
11622         HistorySet(parseList, backwardMostMove, forwardMostMove,\r
11623                    currentMove-1);\r
11624     }\r
11625 }\r
11626 \r
11627 void\r
11628 HintEvent()\r
11629 {\r
11630     if (appData.noChessProgram) return;\r
11631     switch (gameMode) {\r
11632       case MachinePlaysWhite:\r
11633         if (WhiteOnMove(forwardMostMove)) {\r
11634             DisplayError(_("Wait until your turn"), 0);\r
11635             return;\r
11636         }\r
11637         break;\r
11638       case BeginningOfGame:\r
11639       case MachinePlaysBlack:\r
11640         if (!WhiteOnMove(forwardMostMove)) {\r
11641             DisplayError(_("Wait until your turn"), 0);\r
11642             return;\r
11643         }\r
11644         break;\r
11645       default:\r
11646         DisplayError(_("No hint available"), 0);\r
11647         return;\r
11648     }\r
11649     SendToProgram("hint\n", &first);\r
11650     hintRequested = TRUE;\r
11651 }\r
11652 \r
11653 void\r
11654 BookEvent()\r
11655 {\r
11656     if (appData.noChessProgram) return;\r
11657     switch (gameMode) {\r
11658       case MachinePlaysWhite:\r
11659         if (WhiteOnMove(forwardMostMove)) {\r
11660             DisplayError(_("Wait until your turn"), 0);\r
11661             return;\r
11662         }\r
11663         break;\r
11664       case BeginningOfGame:\r
11665       case MachinePlaysBlack:\r
11666         if (!WhiteOnMove(forwardMostMove)) {\r
11667             DisplayError(_("Wait until your turn"), 0);\r
11668             return;\r
11669         }\r
11670         break;\r
11671       case EditPosition:\r
11672         EditPositionDone();\r
11673         break;\r
11674       case TwoMachinesPlay:\r
11675         return;\r
11676       default:\r
11677         break;\r
11678     }\r
11679     SendToProgram("bk\n", &first);\r
11680     bookOutput[0] = NULLCHAR;\r
11681     bookRequested = TRUE;\r
11682 }\r
11683 \r
11684 void\r
11685 AboutGameEvent()\r
11686 {\r
11687     char *tags = PGNTags(&gameInfo);\r
11688     TagsPopUp(tags, CmailMsg());\r
11689     free(tags);\r
11690 }\r
11691 \r
11692 /* end button procedures */\r
11693 \r
11694 void\r
11695 PrintPosition(fp, move)\r
11696      FILE *fp;\r
11697      int move;\r
11698 {\r
11699     int i, j;\r
11700     \r
11701     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
11702         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
11703             char c = PieceToChar(boards[move][i][j]);\r
11704             fputc(c == 'x' ? '.' : c, fp);\r
11705             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);\r
11706         }\r
11707     }\r
11708     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))\r
11709       fprintf(fp, "white to play\n");\r
11710     else\r
11711       fprintf(fp, "black to play\n");\r
11712 }\r
11713 \r
11714 void\r
11715 PrintOpponents(fp)\r
11716      FILE *fp;\r
11717 {\r
11718     if (gameInfo.white != NULL) {\r
11719         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);\r
11720     } else {\r
11721         fprintf(fp, "\n");\r
11722     }\r
11723 }\r
11724 \r
11725 /* Find last component of program's own name, using some heuristics */\r
11726 void\r
11727 TidyProgramName(prog, host, buf)\r
11728      char *prog, *host, buf[MSG_SIZ];\r
11729 {\r
11730     char *p, *q;\r
11731     int local = (strcmp(host, "localhost") == 0);\r
11732     while (!local && (p = strchr(prog, ';')) != NULL) {\r
11733         p++;\r
11734         while (*p == ' ') p++;\r
11735         prog = p;\r
11736     }\r
11737     if (*prog == '"' || *prog == '\'') {\r
11738         q = strchr(prog + 1, *prog);\r
11739     } else {\r
11740         q = strchr(prog, ' ');\r
11741     }\r
11742     if (q == NULL) q = prog + strlen(prog);\r
11743     p = q;\r
11744     while (p >= prog && *p != '/' && *p != '\\') p--;\r
11745     p++;\r
11746     if(p == prog && *p == '"') p++;\r
11747     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;\r
11748     memcpy(buf, p, q - p);\r
11749     buf[q - p] = NULLCHAR;\r
11750     if (!local) {\r
11751         strcat(buf, "@");\r
11752         strcat(buf, host);\r
11753     }\r
11754 }\r
11755 \r
11756 char *\r
11757 TimeControlTagValue()\r
11758 {\r
11759     char buf[MSG_SIZ];\r
11760     if (!appData.clockMode) {\r
11761         strcpy(buf, "-");\r
11762     } else if (movesPerSession > 0) {\r
11763         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);\r
11764     } else if (timeIncrement == 0) {\r
11765         sprintf(buf, "%ld", timeControl/1000);\r
11766     } else {\r
11767         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);\r
11768     }\r
11769     return StrSave(buf);\r
11770 }\r
11771 \r
11772 void\r
11773 SetGameInfo()\r
11774 {\r
11775     /* This routine is used only for certain modes */\r
11776     VariantClass v = gameInfo.variant;\r
11777     ClearGameInfo(&gameInfo);\r
11778     gameInfo.variant = v;\r
11779 \r
11780     switch (gameMode) {\r
11781       case MachinePlaysWhite:\r
11782         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11783         gameInfo.site = StrSave(HostName());\r
11784         gameInfo.date = PGNDate();\r
11785         gameInfo.round = StrSave("-");\r
11786         gameInfo.white = StrSave(first.tidy);\r
11787         gameInfo.black = StrSave(UserName());\r
11788         gameInfo.timeControl = TimeControlTagValue();\r
11789         break;\r
11790 \r
11791       case MachinePlaysBlack:\r
11792         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11793         gameInfo.site = StrSave(HostName());\r
11794         gameInfo.date = PGNDate();\r
11795         gameInfo.round = StrSave("-");\r
11796         gameInfo.white = StrSave(UserName());\r
11797         gameInfo.black = StrSave(first.tidy);\r
11798         gameInfo.timeControl = TimeControlTagValue();\r
11799         break;\r
11800 \r
11801       case TwoMachinesPlay:\r
11802         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11803         gameInfo.site = StrSave(HostName());\r
11804         gameInfo.date = PGNDate();\r
11805         if (matchGame > 0) {\r
11806             char buf[MSG_SIZ];\r
11807             sprintf(buf, "%d", matchGame);\r
11808             gameInfo.round = StrSave(buf);\r
11809         } else {\r
11810             gameInfo.round = StrSave("-");\r
11811         }\r
11812         if (first.twoMachinesColor[0] == 'w') {\r
11813             gameInfo.white = StrSave(first.tidy);\r
11814             gameInfo.black = StrSave(second.tidy);\r
11815         } else {\r
11816             gameInfo.white = StrSave(second.tidy);\r
11817             gameInfo.black = StrSave(first.tidy);\r
11818         }\r
11819         gameInfo.timeControl = TimeControlTagValue();\r
11820         break;\r
11821 \r
11822       case EditGame:\r
11823         gameInfo.event = StrSave("Edited game");\r
11824         gameInfo.site = StrSave(HostName());\r
11825         gameInfo.date = PGNDate();\r
11826         gameInfo.round = StrSave("-");\r
11827         gameInfo.white = StrSave("-");\r
11828         gameInfo.black = StrSave("-");\r
11829         break;\r
11830 \r
11831       case EditPosition:\r
11832         gameInfo.event = StrSave("Edited position");\r
11833         gameInfo.site = StrSave(HostName());\r
11834         gameInfo.date = PGNDate();\r
11835         gameInfo.round = StrSave("-");\r
11836         gameInfo.white = StrSave("-");\r
11837         gameInfo.black = StrSave("-");\r
11838         break;\r
11839 \r
11840       case IcsPlayingWhite:\r
11841       case IcsPlayingBlack:\r
11842       case IcsObserving:\r
11843       case IcsExamining:\r
11844         break;\r
11845 \r
11846       case PlayFromGameFile:\r
11847         gameInfo.event = StrSave("Game from non-PGN file");\r
11848         gameInfo.site = StrSave(HostName());\r
11849         gameInfo.date = PGNDate();\r
11850         gameInfo.round = StrSave("-");\r
11851         gameInfo.white = StrSave("?");\r
11852         gameInfo.black = StrSave("?");\r
11853         break;\r
11854 \r
11855       default:\r
11856         break;\r
11857     }\r
11858 }\r
11859 \r
11860 void\r
11861 ReplaceComment(index, text)\r
11862      int index;\r
11863      char *text;\r
11864 {\r
11865     int len;\r
11866 \r
11867     while (*text == '\n') text++;\r
11868     len = strlen(text);\r
11869     while (len > 0 && text[len - 1] == '\n') len--;\r
11870 \r
11871     if (commentList[index] != NULL)\r
11872       free(commentList[index]);\r
11873 \r
11874     if (len == 0) {\r
11875         commentList[index] = NULL;\r
11876         return;\r
11877     }\r
11878     commentList[index] = (char *) malloc(len + 2);\r
11879     strncpy(commentList[index], text, len);\r
11880     commentList[index][len] = '\n';\r
11881     commentList[index][len + 1] = NULLCHAR;\r
11882 }\r
11883 \r
11884 void\r
11885 CrushCRs(text)\r
11886      char *text;\r
11887 {\r
11888   char *p = text;\r
11889   char *q = text;\r
11890   char ch;\r
11891 \r
11892   do {\r
11893     ch = *p++;\r
11894     if (ch == '\r') continue;\r
11895     *q++ = ch;\r
11896   } while (ch != '\0');\r
11897 }\r
11898 \r
11899 void\r
11900 AppendComment(index, text)\r
11901      int index;\r
11902      char *text;\r
11903 {\r
11904     int oldlen, len;\r
11905     char *old;\r
11906 \r
11907     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */\r
11908 \r
11909     CrushCRs(text);\r
11910     while (*text == '\n') text++;\r
11911     len = strlen(text);\r
11912     while (len > 0 && text[len - 1] == '\n') len--;\r
11913 \r
11914     if (len == 0) return;\r
11915 \r
11916     if (commentList[index] != NULL) {\r
11917         old = commentList[index];\r
11918         oldlen = strlen(old);\r
11919         commentList[index] = (char *) malloc(oldlen + len + 2);\r
11920         strcpy(commentList[index], old);\r
11921         free(old);\r
11922         strncpy(&commentList[index][oldlen], text, len);\r
11923         commentList[index][oldlen + len] = '\n';\r
11924         commentList[index][oldlen + len + 1] = NULLCHAR;\r
11925     } else {\r
11926         commentList[index] = (char *) malloc(len + 2);\r
11927         strncpy(commentList[index], text, len);\r
11928         commentList[index][len] = '\n';\r
11929         commentList[index][len + 1] = NULLCHAR;\r
11930     }\r
11931 }\r
11932 \r
11933 static char * FindStr( char * text, char * sub_text )\r
11934 {\r
11935     char * result = strstr( text, sub_text );\r
11936 \r
11937     if( result != NULL ) {\r
11938         result += strlen( sub_text );\r
11939     }\r
11940 \r
11941     return result;\r
11942 }\r
11943 \r
11944 /* [AS] Try to extract PV info from PGN comment */\r
11945 /* [HGM] PV time: and then remove it, to prevent it appearing twice */\r
11946 char *GetInfoFromComment( int index, char * text )\r
11947 {\r
11948     char * sep = text;\r
11949 \r
11950     if( text != NULL && index > 0 ) {\r
11951         int score = 0;\r
11952         int depth = 0;\r
11953         int time = -1, sec = 0, deci;\r
11954         char * s_eval = FindStr( text, "[%eval " );\r
11955         char * s_emt = FindStr( text, "[%emt " );\r
11956 \r
11957         if( s_eval != NULL || s_emt != NULL ) {\r
11958             /* New style */\r
11959             char delim;\r
11960 \r
11961             if( s_eval != NULL ) {\r
11962                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {\r
11963                     return text;\r
11964                 }\r
11965 \r
11966                 if( delim != ']' ) {\r
11967                     return text;\r
11968                 }\r
11969             }\r
11970 \r
11971             if( s_emt != NULL ) {\r
11972             }\r
11973         }\r
11974         else {\r
11975             /* We expect something like: [+|-]nnn.nn/dd */\r
11976             int score_lo = 0;\r
11977 \r
11978             sep = strchr( text, '/' );\r
11979             if( sep == NULL || sep < (text+4) ) {\r
11980                 return text;\r
11981             }\r
11982 \r
11983             time = -1; sec = -1; deci = -1;\r
11984             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&\r
11985                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&\r
11986                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&\r
11987                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {\r
11988                 return text;\r
11989             }\r
11990 \r
11991             if( score_lo < 0 || score_lo >= 100 ) {\r
11992                 return text;\r
11993             }\r
11994 \r
11995             if(sec >= 0) time = 600*time + 10*sec; else\r
11996             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec\r
11997 \r
11998             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;\r
11999 \r
12000             /* [HGM] PV time: now locate end of PV info */\r
12001             while( *++sep >= '0' && *sep <= '9'); // strip depth\r
12002             if(time >= 0)\r
12003             while( *++sep >= '0' && *sep <= '9'); // strip time\r
12004             if(sec >= 0)\r
12005             while( *++sep >= '0' && *sep <= '9'); // strip seconds\r
12006             if(deci >= 0)\r
12007             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds\r
12008             while(*sep == ' ') sep++;\r
12009         }\r
12010 \r
12011         if( depth <= 0 ) {\r
12012             return text;\r
12013         }\r
12014 \r
12015         if( time < 0 ) {\r
12016             time = -1;\r
12017         }\r
12018 \r
12019         pvInfoList[index-1].depth = depth;\r
12020         pvInfoList[index-1].score = score;\r
12021         pvInfoList[index-1].time  = 10*time; // centi-sec\r
12022     }\r
12023     return sep;\r
12024 }\r
12025 \r
12026 void\r
12027 SendToProgram(message, cps)\r
12028      char *message;\r
12029      ChessProgramState *cps;\r
12030 {\r
12031     int count, outCount, error;\r
12032     char buf[MSG_SIZ];\r
12033 \r
12034     if (cps->pr == NULL) return;\r
12035     Attention(cps);\r
12036     \r
12037     if (appData.debugMode) {\r
12038         TimeMark now;\r
12039         GetTimeMark(&now);\r
12040         fprintf(debugFP, "%ld >%-6s: %s", \r
12041                 SubtractTimeMarks(&now, &programStartTime),\r
12042                 cps->which, message);\r
12043     }\r
12044     \r
12045     count = strlen(message);\r
12046     outCount = OutputToProcess(cps->pr, message, count, &error);\r
12047     if (outCount < count && !exiting \r
12048                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */\r
12049         sprintf(buf, _("Error writing to %s chess program"), cps->which);\r
12050         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
12051             if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
12052                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
12053                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);\r
12054             } else {\r
12055                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
12056             }\r
12057             gameInfo.resultDetails = buf;\r
12058         }\r
12059         DisplayFatalError(buf, error, 1);\r
12060     }\r
12061 }\r
12062 \r
12063 void\r
12064 ReceiveFromProgram(isr, closure, message, count, error)\r
12065      InputSourceRef isr;\r
12066      VOIDSTAR closure;\r
12067      char *message;\r
12068      int count;\r
12069      int error;\r
12070 {\r
12071     char *end_str;\r
12072     char buf[MSG_SIZ];\r
12073     ChessProgramState *cps = (ChessProgramState *)closure;\r
12074 \r
12075     if (isr != cps->isr) return; /* Killed intentionally */\r
12076     if (count <= 0) {\r
12077         if (count == 0) {\r
12078             sprintf(buf,\r
12079                     _("Error: %s chess program (%s) exited unexpectedly"),\r
12080                     cps->which, cps->program);\r
12081         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
12082                 if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
12083                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
12084                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);\r
12085                 } else {\r
12086                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
12087                 }\r
12088                 gameInfo.resultDetails = buf;\r
12089             }\r
12090             RemoveInputSource(cps->isr);\r
12091             DisplayFatalError(buf, 0, 1);\r
12092         } else {\r
12093             sprintf(buf,\r
12094                     _("Error reading from %s chess program (%s)"),\r
12095                     cps->which, cps->program);\r
12096             RemoveInputSource(cps->isr);\r
12097 \r
12098             /* [AS] Program is misbehaving badly... kill it */\r
12099             if( count == -2 ) {\r
12100                 DestroyChildProcess( cps->pr, 9 );\r
12101                 cps->pr = NoProc;\r
12102             }\r
12103 \r
12104             DisplayFatalError(buf, error, 1);\r
12105         }\r
12106         return;\r
12107     }\r
12108     \r
12109     if ((end_str = strchr(message, '\r')) != NULL)\r
12110       *end_str = NULLCHAR;\r
12111     if ((end_str = strchr(message, '\n')) != NULL)\r
12112       *end_str = NULLCHAR;\r
12113     \r
12114     if (appData.debugMode) {\r
12115         TimeMark now; int print = 1;\r
12116         char *quote = ""; char c; int i;\r
12117 \r
12118         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */\r
12119                 char start = message[0];\r
12120                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing\r
12121                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && \r
12122                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&\r
12123                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&\r
12124                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&\r
12125                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&\r
12126                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')\r
12127                         { quote = "# "; print = (appData.engineComments == 2); }\r
12128                 message[0] = start; // restore original message\r
12129         }\r
12130         if(print) {\r
12131                 GetTimeMark(&now);\r
12132                 fprintf(debugFP, "%ld <%-6s: %s%s\n", \r
12133                         SubtractTimeMarks(&now, &programStartTime), cps->which, \r
12134                         quote,\r
12135                         message);\r
12136         }\r
12137     }\r
12138 \r
12139     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */\r
12140     if (appData.icsEngineAnalyze) {\r
12141         if (strstr(message, "whisper") != NULL ||\r
12142              strstr(message, "kibitz") != NULL || \r
12143             strstr(message, "tellics") != NULL) return;\r
12144     }\r
12145 \r
12146     HandleMachineMove(message, cps);\r
12147 }\r
12148 \r
12149 \r
12150 void\r
12151 SendTimeControl(cps, mps, tc, inc, sd, st)\r
12152      ChessProgramState *cps;\r
12153      int mps, inc, sd, st;\r
12154      long tc;\r
12155 {\r
12156     char buf[MSG_SIZ];\r
12157     int seconds;\r
12158 \r
12159     if( timeControl_2 > 0 ) {\r
12160         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {\r
12161             tc = timeControl_2;\r
12162         }\r
12163     }\r
12164     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */\r
12165     inc /= cps->timeOdds;\r
12166     st  /= cps->timeOdds;\r
12167 \r
12168     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */\r
12169 \r
12170     if (st > 0) {\r
12171       /* Set exact time per move, normally using st command */\r
12172       if (cps->stKludge) {\r
12173         /* GNU Chess 4 has no st command; uses level in a nonstandard way */\r
12174         seconds = st % 60;\r
12175         if (seconds == 0) {\r
12176           sprintf(buf, "level 1 %d\n", st/60);\r
12177         } else {\r
12178           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);\r
12179         }\r
12180       } else {\r
12181         sprintf(buf, "st %d\n", st);\r
12182       }\r
12183     } else {\r
12184       /* Set conventional or incremental time control, using level command */\r
12185       if (seconds == 0) {\r
12186         /* Note old gnuchess bug -- minutes:seconds used to not work.\r
12187            Fixed in later versions, but still avoid :seconds\r
12188            when seconds is 0. */\r
12189         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);\r
12190       } else {\r
12191         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,\r
12192                 seconds, inc/1000);\r
12193       }\r
12194     }\r
12195     SendToProgram(buf, cps);\r
12196 \r
12197     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */\r
12198     /* Orthogonally, limit search to given depth */\r
12199     if (sd > 0) {\r
12200       if (cps->sdKludge) {\r
12201         sprintf(buf, "depth\n%d\n", sd);\r
12202       } else {\r
12203         sprintf(buf, "sd %d\n", sd);\r
12204       }\r
12205       SendToProgram(buf, cps);\r
12206     }\r
12207 \r
12208     if(cps->nps > 0) { /* [HGM] nps */\r
12209         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!\r
12210         else {\r
12211                 sprintf(buf, "nps %d\n", cps->nps);\r
12212               SendToProgram(buf, cps);\r
12213         }\r
12214     }\r
12215 }\r
12216 \r
12217 ChessProgramState *WhitePlayer()\r
12218 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */\r
12219 {\r
12220     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || \r
12221        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)\r
12222         return &second;\r
12223     return &first;\r
12224 }\r
12225 \r
12226 void\r
12227 SendTimeRemaining(cps, machineWhite)\r
12228      ChessProgramState *cps;\r
12229      int /*boolean*/ machineWhite;\r
12230 {\r
12231     char message[MSG_SIZ];\r
12232     long time, otime;\r
12233 \r
12234     /* Note: this routine must be called when the clocks are stopped\r
12235        or when they have *just* been set or switched; otherwise\r
12236        it will be off by the time since the current tick started.\r
12237     */\r
12238     if (machineWhite) {\r
12239         time = whiteTimeRemaining / 10;\r
12240         otime = blackTimeRemaining / 10;\r
12241     } else {\r
12242         time = blackTimeRemaining / 10;\r
12243         otime = whiteTimeRemaining / 10;\r
12244     }\r
12245     /* [HGM] translate opponent's time by time-odds factor */\r
12246     otime = (otime * cps->other->timeOdds) / cps->timeOdds;\r
12247     if (appData.debugMode) {\r
12248         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);\r
12249     }\r
12250 \r
12251     if (time <= 0) time = 1;\r
12252     if (otime <= 0) otime = 1;\r
12253     \r
12254     sprintf(message, "time %ld\n", time);\r
12255     SendToProgram(message, cps);\r
12256 \r
12257     sprintf(message, "otim %ld\n", otime);\r
12258     SendToProgram(message, cps);\r
12259 }\r
12260 \r
12261 int\r
12262 BoolFeature(p, name, loc, cps)\r
12263      char **p;\r
12264      char *name;\r
12265      int *loc;\r
12266      ChessProgramState *cps;\r
12267 {\r
12268   char buf[MSG_SIZ];\r
12269   int len = strlen(name);\r
12270   int val;\r
12271   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
12272     (*p) += len + 1;\r
12273     sscanf(*p, "%d", &val);\r
12274     *loc = (val != 0);\r
12275     while (**p && **p != ' ') (*p)++;\r
12276     sprintf(buf, "accepted %s\n", name);\r
12277     SendToProgram(buf, cps);\r
12278     return TRUE;\r
12279   }\r
12280   return FALSE;\r
12281 }\r
12282 \r
12283 int\r
12284 IntFeature(p, name, loc, cps)\r
12285      char **p;\r
12286      char *name;\r
12287      int *loc;\r
12288      ChessProgramState *cps;\r
12289 {\r
12290   char buf[MSG_SIZ];\r
12291   int len = strlen(name);\r
12292   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
12293     (*p) += len + 1;\r
12294     sscanf(*p, "%d", loc);\r
12295     while (**p && **p != ' ') (*p)++;\r
12296     sprintf(buf, "accepted %s\n", name);\r
12297     SendToProgram(buf, cps);\r
12298     return TRUE;\r
12299   }\r
12300   return FALSE;\r
12301 }\r
12302 \r
12303 int\r
12304 StringFeature(p, name, loc, cps)\r
12305      char **p;\r
12306      char *name;\r
12307      char loc[];\r
12308      ChessProgramState *cps;\r
12309 {\r
12310   char buf[MSG_SIZ];\r
12311   int len = strlen(name);\r
12312   if (strncmp((*p), name, len) == 0\r
12313       && (*p)[len] == '=' && (*p)[len+1] == '\"') {\r
12314     (*p) += len + 2;\r
12315     sscanf(*p, "%[^\"]", loc);\r
12316     while (**p && **p != '\"') (*p)++;\r
12317     if (**p == '\"') (*p)++;\r
12318     sprintf(buf, "accepted %s\n", name);\r
12319     SendToProgram(buf, cps);\r
12320     return TRUE;\r
12321   }\r
12322   return FALSE;\r
12323 }\r
12324 \r
12325 int \r
12326 ParseOption(Option *opt, ChessProgramState *cps)\r
12327 // [HGM] options: process the string that defines an engine option, and determine\r
12328 // name, type, default value, and allowed value range\r
12329 {\r
12330         char *p, *q, buf[MSG_SIZ];\r
12331         int n, min = (-1)<<31, max = 1<<31, def;\r
12332 \r
12333         if(p = strstr(opt->name, " -spin ")) {\r
12334             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;\r
12335             if(max < min) max = min; // enforce consistency\r
12336             if(def < min) def = min;\r
12337             if(def > max) def = max;\r
12338             opt->value = def;\r
12339             opt->min = min;\r
12340             opt->max = max;\r
12341             opt->type = Spin;\r
12342         } else if(p = strstr(opt->name, " -string ")) {\r
12343             opt->textValue = p+9;\r
12344             opt->type = TextBox;\r
12345         } else if(p = strstr(opt->name, " -check ")) {\r
12346             if(sscanf(p, " -check %d", &def) < 1) return FALSE;\r
12347             opt->value = (def != 0);\r
12348             opt->type = CheckBox;\r
12349         } else if(p = strstr(opt->name, " -combo ")) {\r
12350             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type\r
12351             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices\r
12352             opt->value = n = 0;\r
12353             while(q = StrStr(q, " /// ")) {\r
12354                 n++; *q = 0;    // count choices, and null-terminate each of them\r
12355                 q += 5;\r
12356                 if(*q == '*') { // remember default, which is marked with * prefix\r
12357                     q++;\r
12358                     opt->value = n;\r
12359                 }\r
12360                 cps->comboList[cps->comboCnt++] = q;\r
12361             }\r
12362             cps->comboList[cps->comboCnt++] = NULL;\r
12363             opt->max = n + 1;\r
12364             opt->type = ComboBox;\r
12365         } else if(p = strstr(opt->name, " -button")) {\r
12366             opt->type = Button;\r
12367         } else if(p = strstr(opt->name, " -save")) {\r
12368             opt->type = SaveButton;\r
12369         } else return FALSE;\r
12370         *p = 0; // terminate option name\r
12371         // now look if the command-line options define a setting for this engine option.\r
12372         if(cps->optionSettings && cps->optionSettings[0])\r
12373             p = strstr(cps->optionSettings, opt->name); else p = NULL;\r
12374         if(p && (p == cps->optionSettings || p[-1] == ',')) {\r
12375                 sprintf(buf, "option %s", p);\r
12376                 if(p = strstr(buf, ",")) *p = 0;\r
12377                 strcat(buf, "\n");\r
12378                 SendToProgram(buf, cps);\r
12379         }\r
12380         return TRUE;\r
12381 }\r
12382 \r
12383 void\r
12384 FeatureDone(cps, val)\r
12385      ChessProgramState* cps;\r
12386      int val;\r
12387 {\r
12388   DelayedEventCallback cb = GetDelayedEvent();\r
12389   if ((cb == InitBackEnd3 && cps == &first) ||\r
12390       (cb == TwoMachinesEventIfReady && cps == &second)) {\r
12391     CancelDelayedEvent();\r
12392     ScheduleDelayedEvent(cb, val ? 1 : 3600000);\r
12393   }\r
12394   cps->initDone = val;\r
12395 }\r
12396 \r
12397 /* Parse feature command from engine */\r
12398 void\r
12399 ParseFeatures(args, cps)\r
12400      char* args;\r
12401      ChessProgramState *cps;  \r
12402 {\r
12403   char *p = args;\r
12404   char *q;\r
12405   int val;\r
12406   char buf[MSG_SIZ];\r
12407 \r
12408   for (;;) {\r
12409     while (*p == ' ') p++;\r
12410     if (*p == NULLCHAR) return;\r
12411 \r
12412     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;\r
12413     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    \r
12414     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    \r
12415     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    \r
12416     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    \r
12417     if (BoolFeature(&p, "reuse", &val, cps)) {\r
12418       /* Engine can disable reuse, but can't enable it if user said no */\r
12419       if (!val) cps->reuse = FALSE;\r
12420       continue;\r
12421     }\r
12422     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;\r
12423     if (StringFeature(&p, "myname", &cps->tidy, cps)) {\r
12424       if (gameMode == TwoMachinesPlay) {\r
12425         DisplayTwoMachinesTitle();\r
12426       } else {\r
12427         DisplayTitle("");\r
12428       }\r
12429       continue;\r
12430     }\r
12431     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;\r
12432     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;\r
12433     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;\r
12434     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;\r
12435     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;\r
12436     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;\r
12437     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;\r
12438     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;\r
12439     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */\r
12440     if (IntFeature(&p, "done", &val, cps)) {\r
12441       FeatureDone(cps, val);\r
12442       continue;\r
12443     }\r
12444     /* Added by Tord: */\r
12445     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;\r
12446     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;\r
12447     /* End of additions by Tord */\r
12448 \r
12449     /* [HGM] added features: */\r
12450     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;\r
12451     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;\r
12452     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;\r
12453     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;\r
12454     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;\r
12455     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;\r
12456     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {\r
12457         ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature\r
12458         if(cps->nrOptions >= MAX_OPTIONS) {\r
12459             cps->nrOptions--;\r
12460             sprintf(buf, "%s engine has too many options\n", cps->which);\r
12461             DisplayError(buf, 0);\r
12462         }\r
12463         continue;\r
12464     }\r
12465     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;\r
12466     /* End of additions by HGM */\r
12467 \r
12468     /* unknown feature: complain and skip */\r
12469     q = p;\r
12470     while (*q && *q != '=') q++;\r
12471     sprintf(buf, "rejected %.*s\n", q-p, p);\r
12472     SendToProgram(buf, cps);\r
12473     p = q;\r
12474     if (*p == '=') {\r
12475       p++;\r
12476       if (*p == '\"') {\r
12477         p++;\r
12478         while (*p && *p != '\"') p++;\r
12479         if (*p == '\"') p++;\r
12480       } else {\r
12481         while (*p && *p != ' ') p++;\r
12482       }\r
12483     }\r
12484   }\r
12485 \r
12486 }\r
12487 \r
12488 void\r
12489 PeriodicUpdatesEvent(newState)\r
12490      int newState;\r
12491 {\r
12492     if (newState == appData.periodicUpdates)\r
12493       return;\r
12494 \r
12495     appData.periodicUpdates=newState;\r
12496 \r
12497     /* Display type changes, so update it now */\r
12498     DisplayAnalysis();\r
12499 \r
12500     /* Get the ball rolling again... */\r
12501     if (newState) {\r
12502         AnalysisPeriodicEvent(1);\r
12503         StartAnalysisClock();\r
12504     }\r
12505 }\r
12506 \r
12507 void\r
12508 PonderNextMoveEvent(newState)\r
12509      int newState;\r
12510 {\r
12511     if (newState == appData.ponderNextMove) return;\r
12512     if (gameMode == EditPosition) EditPositionDone();\r
12513     if (newState) {\r
12514         SendToProgram("hard\n", &first);\r
12515         if (gameMode == TwoMachinesPlay) {\r
12516             SendToProgram("hard\n", &second);\r
12517         }\r
12518     } else {\r
12519         SendToProgram("easy\n", &first);\r
12520         thinkOutput[0] = NULLCHAR;\r
12521         if (gameMode == TwoMachinesPlay) {\r
12522             SendToProgram("easy\n", &second);\r
12523         }\r
12524     }\r
12525     appData.ponderNextMove = newState;\r
12526 }\r
12527 \r
12528 void\r
12529 NewSettingEvent(option, command, value)\r
12530      char *command;\r
12531      int option, value;\r
12532 {\r
12533     char buf[MSG_SIZ];\r
12534 \r
12535     if (gameMode == EditPosition) EditPositionDone();\r
12536     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);\r
12537     SendToProgram(buf, &first);\r
12538     if (gameMode == TwoMachinesPlay) {\r
12539         SendToProgram(buf, &second);\r
12540     }\r
12541 }\r
12542 \r
12543 void\r
12544 ShowThinkingEvent()\r
12545 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup\r
12546 {\r
12547     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated\r
12548     int newState = appData.showThinking\r
12549         // [HGM] thinking: other features now need thinking output as well\r
12550         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();\r
12551     \r
12552     if (oldState == newState) return;\r
12553     oldState = newState;\r
12554     if (gameMode == EditPosition) EditPositionDone();\r
12555     if (oldState) {\r
12556         SendToProgram("post\n", &first);\r
12557         if (gameMode == TwoMachinesPlay) {\r
12558             SendToProgram("post\n", &second);\r
12559         }\r
12560     } else {\r
12561         SendToProgram("nopost\n", &first);\r
12562         thinkOutput[0] = NULLCHAR;\r
12563         if (gameMode == TwoMachinesPlay) {\r
12564             SendToProgram("nopost\n", &second);\r
12565         }\r
12566     }\r
12567 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!\r
12568 }\r
12569 \r
12570 void\r
12571 AskQuestionEvent(title, question, replyPrefix, which)\r
12572      char *title; char *question; char *replyPrefix; char *which;\r
12573 {\r
12574   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;\r
12575   if (pr == NoProc) return;\r
12576   AskQuestion(title, question, replyPrefix, pr);\r
12577 }\r
12578 \r
12579 void\r
12580 DisplayMove(moveNumber)\r
12581      int moveNumber;\r
12582 {\r
12583     char message[MSG_SIZ];\r
12584     char res[MSG_SIZ];\r
12585     char cpThinkOutput[MSG_SIZ];\r
12586 \r
12587     if(appData.noGUI) return; // [HGM] fast: suppress display of moves\r
12588     \r
12589     if (moveNumber == forwardMostMove - 1 || \r
12590         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
12591 \r
12592         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));\r
12593 \r
12594         if (strchr(cpThinkOutput, '\n')) {\r
12595             *strchr(cpThinkOutput, '\n') = NULLCHAR;\r
12596         }\r
12597     } else {\r
12598         *cpThinkOutput = NULLCHAR;\r
12599     }\r
12600 \r
12601     /* [AS] Hide thinking from human user */\r
12602     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {\r
12603         *cpThinkOutput = NULLCHAR;\r
12604         if( thinkOutput[0] != NULLCHAR ) {\r
12605             int i;\r
12606 \r
12607             for( i=0; i<=hiddenThinkOutputState; i++ ) {\r
12608                 cpThinkOutput[i] = '.';\r
12609             }\r
12610             cpThinkOutput[i] = NULLCHAR;\r
12611             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;\r
12612         }\r
12613     }\r
12614 \r
12615     if (moveNumber == forwardMostMove - 1 &&\r
12616         gameInfo.resultDetails != NULL) {\r
12617         if (gameInfo.resultDetails[0] == NULLCHAR) {\r
12618             sprintf(res, " %s", PGNResult(gameInfo.result));\r
12619         } else {\r
12620             sprintf(res, " {%s} %s",\r
12621                     gameInfo.resultDetails, PGNResult(gameInfo.result));\r
12622         }\r
12623     } else {\r
12624         res[0] = NULLCHAR;\r
12625     }\r
12626 \r
12627     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
12628         DisplayMessage(res, cpThinkOutput);\r
12629     } else {\r
12630         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,\r
12631                 WhiteOnMove(moveNumber) ? " " : ".. ",\r
12632                 parseList[moveNumber], res);\r
12633         DisplayMessage(message, cpThinkOutput);\r
12634     }\r
12635 }\r
12636 \r
12637 void\r
12638 DisplayAnalysisText(text)\r
12639      char *text;\r
12640 {\r
12641     char buf[MSG_SIZ];\r
12642 \r
12643     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile \r
12644                || appData.icsEngineAnalyze) {\r
12645         sprintf(buf, "Analysis (%s)", first.tidy);\r
12646         AnalysisPopUp(buf, text);\r
12647     }\r
12648 }\r
12649 \r
12650 static int\r
12651 only_one_move(str)\r
12652      char *str;\r
12653 {\r
12654     while (*str && isspace(*str)) ++str;\r
12655     while (*str && !isspace(*str)) ++str;\r
12656     if (!*str) return 1;\r
12657     while (*str && isspace(*str)) ++str;\r
12658     if (!*str) return 1;\r
12659     return 0;\r
12660 }\r
12661 \r
12662 void\r
12663 DisplayAnalysis()\r
12664 {\r
12665     char buf[MSG_SIZ];\r
12666     char lst[MSG_SIZ / 2];\r
12667     double nps;\r
12668     static char *xtra[] = { "", " (--)", " (++)" };\r
12669     int h, m, s, cs;\r
12670   \r
12671     if (programStats.time == 0) {\r
12672         programStats.time = 1;\r
12673     }\r
12674   \r
12675     if (programStats.got_only_move) {\r
12676         safeStrCpy(buf, programStats.movelist, sizeof(buf));\r
12677     } else {\r
12678         safeStrCpy( lst, programStats.movelist, sizeof(lst));\r
12679 \r
12680         nps = (u64ToDouble(programStats.nodes) /\r
12681              ((double)programStats.time /100.0));\r
12682 \r
12683         cs = programStats.time % 100;\r
12684         s = programStats.time / 100;\r
12685         h = (s / (60*60));\r
12686         s = s - h*60*60;\r
12687         m = (s/60);\r
12688         s = s - m*60;\r
12689 \r
12690         if (programStats.moves_left > 0 && appData.periodicUpdates) {\r
12691           if (programStats.move_name[0] != NULLCHAR) {\r
12692             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12693                     programStats.depth,\r
12694                     programStats.nr_moves-programStats.moves_left,\r
12695                     programStats.nr_moves, programStats.move_name,\r
12696                     ((float)programStats.score)/100.0, lst,\r
12697                     only_one_move(lst)?\r
12698                     xtra[programStats.got_fail] : "",\r
12699                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12700           } else {\r
12701             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12702                     programStats.depth,\r
12703                     programStats.nr_moves-programStats.moves_left,\r
12704                     programStats.nr_moves, ((float)programStats.score)/100.0,\r
12705                     lst,\r
12706                     only_one_move(lst)?\r
12707                     xtra[programStats.got_fail] : "",\r
12708                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12709           }\r
12710         } else {\r
12711             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12712                     programStats.depth,\r
12713                     ((float)programStats.score)/100.0,\r
12714                     lst,\r
12715                     only_one_move(lst)?\r
12716                     xtra[programStats.got_fail] : "",\r
12717                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12718         }\r
12719     }\r
12720     DisplayAnalysisText(buf);\r
12721 }\r
12722 \r
12723 void\r
12724 DisplayComment(moveNumber, text)\r
12725      int moveNumber;\r
12726      char *text;\r
12727 {\r
12728     char title[MSG_SIZ];\r
12729     char buf[8000]; // comment can be long!\r
12730     int score, depth;\r
12731 \r
12732     if( appData.autoDisplayComment ) {\r
12733         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
12734             strcpy(title, "Comment");\r
12735         } else {\r
12736             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,\r
12737                     WhiteOnMove(moveNumber) ? " " : ".. ",\r
12738                     parseList[moveNumber]);\r
12739         }\r
12740     } else title[0] = 0;\r
12741 \r
12742     // [HGM] PV info: display PV info together with (or as) comment\r
12743     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {\r
12744         if(text == NULL) text = "";                                           \r
12745         score = pvInfoList[moveNumber].score;\r
12746         sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,\r
12747                               depth, (pvInfoList[moveNumber].time+50)/100, text);\r
12748         CommentPopUp(title, buf);\r
12749     } else\r
12750     if (text != NULL)\r
12751         CommentPopUp(title, text);\r
12752 }\r
12753 \r
12754 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it\r
12755  * might be busy thinking or pondering.  It can be omitted if your\r
12756  * gnuchess is configured to stop thinking immediately on any user\r
12757  * input.  However, that gnuchess feature depends on the FIONREAD\r
12758  * ioctl, which does not work properly on some flavors of Unix.\r
12759  */\r
12760 void\r
12761 Attention(cps)\r
12762      ChessProgramState *cps;\r
12763 {\r
12764 #if ATTENTION\r
12765     if (!cps->useSigint) return;\r
12766     if (appData.noChessProgram || (cps->pr == NoProc)) return;\r
12767     switch (gameMode) {\r
12768       case MachinePlaysWhite:\r
12769       case MachinePlaysBlack:\r
12770       case TwoMachinesPlay:\r
12771       case IcsPlayingWhite:\r
12772       case IcsPlayingBlack:\r
12773       case AnalyzeMode:\r
12774       case AnalyzeFile:\r
12775         /* Skip if we know it isn't thinking */\r
12776         if (!cps->maybeThinking) return;\r
12777         if (appData.debugMode)\r
12778           fprintf(debugFP, "Interrupting %s\n", cps->which);\r
12779         InterruptChildProcess(cps->pr);\r
12780         cps->maybeThinking = FALSE;\r
12781         break;\r
12782       default:\r
12783         break;\r
12784     }\r
12785 #endif /*ATTENTION*/\r
12786 }\r
12787 \r
12788 int\r
12789 CheckFlags()\r
12790 {\r
12791     if (whiteTimeRemaining <= 0) {\r
12792         if (!whiteFlag) {\r
12793             whiteFlag = TRUE;\r
12794             if (appData.icsActive) {\r
12795                 if (appData.autoCallFlag &&\r
12796                     gameMode == IcsPlayingBlack && !blackFlag) {\r
12797                   SendToICS(ics_prefix);\r
12798                   SendToICS("flag\n");\r
12799                 }\r
12800             } else {\r
12801                 if (blackFlag) {\r
12802                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));\r
12803                 } else {\r
12804                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));\r
12805                     if (appData.autoCallFlag) {\r
12806                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);\r
12807                         return TRUE;\r
12808                     }\r
12809                 }\r
12810             }\r
12811         }\r
12812     }\r
12813     if (blackTimeRemaining <= 0) {\r
12814         if (!blackFlag) {\r
12815             blackFlag = TRUE;\r
12816             if (appData.icsActive) {\r
12817                 if (appData.autoCallFlag &&\r
12818                     gameMode == IcsPlayingWhite && !whiteFlag) {\r
12819                   SendToICS(ics_prefix);\r
12820                   SendToICS("flag\n");\r
12821                 }\r
12822             } else {\r
12823                 if (whiteFlag) {\r
12824                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));\r
12825                 } else {\r
12826                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));\r
12827                     if (appData.autoCallFlag) {\r
12828                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);\r
12829                         return TRUE;\r
12830                     }\r
12831                 }\r
12832             }\r
12833         }\r
12834     }\r
12835     return FALSE;\r
12836 }\r
12837 \r
12838 void\r
12839 CheckTimeControl()\r
12840 {\r
12841     if (!appData.clockMode || appData.icsActive ||\r
12842         gameMode == PlayFromGameFile || forwardMostMove == 0) return;\r
12843 \r
12844     /*\r
12845      * add time to clocks when time control is achieved ([HGM] now also used for increment)\r
12846      */\r
12847     if ( !WhiteOnMove(forwardMostMove) )\r
12848         /* White made time control */\r
12849         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
12850         /* [HGM] time odds: correct new time quota for time odds! */\r
12851                                             / WhitePlayer()->timeOdds;\r
12852       else\r
12853         /* Black made time control */\r
12854         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
12855                                             / WhitePlayer()->other->timeOdds;\r
12856 }\r
12857 \r
12858 void\r
12859 DisplayBothClocks()\r
12860 {\r
12861     int wom = gameMode == EditPosition ?\r
12862       !blackPlaysFirst : WhiteOnMove(currentMove);\r
12863     DisplayWhiteClock(whiteTimeRemaining, wom);\r
12864     DisplayBlackClock(blackTimeRemaining, !wom);\r
12865 }\r
12866 \r
12867 \r
12868 /* Timekeeping seems to be a portability nightmare.  I think everyone\r
12869    has ftime(), but I'm really not sure, so I'm including some ifdefs\r
12870    to use other calls if you don't.  Clocks will be less accurate if\r
12871    you have neither ftime nor gettimeofday.\r
12872 */\r
12873 \r
12874 /* VS 2008 requires the #include outside of the function */\r
12875 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME\r
12876 #include <sys/timeb.h>\r
12877 #endif\r
12878 \r
12879 /* Get the current time as a TimeMark */\r
12880 void\r
12881 GetTimeMark(tm)\r
12882      TimeMark *tm;\r
12883 {\r
12884 #if HAVE_GETTIMEOFDAY\r
12885 \r
12886     struct timeval timeVal;\r
12887     struct timezone timeZone;\r
12888 \r
12889     gettimeofday(&timeVal, &timeZone);\r
12890     tm->sec = (long) timeVal.tv_sec; \r
12891     tm->ms = (int) (timeVal.tv_usec / 1000L);\r
12892 \r
12893 #else /*!HAVE_GETTIMEOFDAY*/\r
12894 #if HAVE_FTIME\r
12895 \r
12896 // include <sys/timeb.h> / moved to just above start of function\r
12897     struct timeb timeB;\r
12898 \r
12899     ftime(&timeB);\r
12900     tm->sec = (long) timeB.time;\r
12901     tm->ms = (int) timeB.millitm;\r
12902 \r
12903 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/\r
12904     tm->sec = (long) time(NULL);\r
12905     tm->ms = 0;\r
12906 #endif\r
12907 #endif\r
12908 }\r
12909 \r
12910 /* Return the difference in milliseconds between two\r
12911    time marks.  We assume the difference will fit in a long!\r
12912 */\r
12913 long\r
12914 SubtractTimeMarks(tm2, tm1)\r
12915      TimeMark *tm2, *tm1;\r
12916 {\r
12917     return 1000L*(tm2->sec - tm1->sec) +\r
12918            (long) (tm2->ms - tm1->ms);\r
12919 }\r
12920 \r
12921 \r
12922 /*\r
12923  * Code to manage the game clocks.\r
12924  *\r
12925  * In tournament play, black starts the clock and then white makes a move.\r
12926  * We give the human user a slight advantage if he is playing white---the\r
12927  * clocks don't run until he makes his first move, so it takes zero time.\r
12928  * Also, we don't account for network lag, so we could get out of sync\r
12929  * with GNU Chess's clock -- but then, referees are always right.  \r
12930  */\r
12931 \r
12932 static TimeMark tickStartTM;\r
12933 static long intendedTickLength;\r
12934 \r
12935 long\r
12936 NextTickLength(timeRemaining)\r
12937      long timeRemaining;\r
12938 {\r
12939     long nominalTickLength, nextTickLength;\r
12940 \r
12941     if (timeRemaining > 0L && timeRemaining <= 10000L)\r
12942       nominalTickLength = 100L;\r
12943     else\r
12944       nominalTickLength = 1000L;\r
12945     nextTickLength = timeRemaining % nominalTickLength;\r
12946     if (nextTickLength <= 0) nextTickLength += nominalTickLength;\r
12947 \r
12948     return nextTickLength;\r
12949 }\r
12950 \r
12951 /* Adjust clock one minute up or down */\r
12952 void\r
12953 AdjustClock(Boolean which, int dir)\r
12954 {\r
12955     if(which) blackTimeRemaining += 60000*dir;\r
12956     else      whiteTimeRemaining += 60000*dir;\r
12957     DisplayBothClocks();\r
12958 }\r
12959 \r
12960 /* Stop clocks and reset to a fresh time control */\r
12961 void\r
12962 ResetClocks() \r
12963 {\r
12964     (void) StopClockTimer();\r
12965     if (appData.icsActive) {\r
12966         whiteTimeRemaining = blackTimeRemaining = 0;\r
12967     } else { /* [HGM] correct new time quote for time odds */\r
12968         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;\r
12969         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;\r
12970     }\r
12971     if (whiteFlag || blackFlag) {\r
12972         DisplayTitle("");\r
12973         whiteFlag = blackFlag = FALSE;\r
12974     }\r
12975     DisplayBothClocks();\r
12976 }\r
12977 \r
12978 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */\r
12979 \r
12980 /* Decrement running clock by amount of time that has passed */\r
12981 void\r
12982 DecrementClocks()\r
12983 {\r
12984     long timeRemaining;\r
12985     long lastTickLength, fudge;\r
12986     TimeMark now;\r
12987 \r
12988     if (!appData.clockMode) return;\r
12989     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;\r
12990         \r
12991     GetTimeMark(&now);\r
12992 \r
12993     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
12994 \r
12995     /* Fudge if we woke up a little too soon */\r
12996     fudge = intendedTickLength - lastTickLength;\r
12997     if (fudge < 0 || fudge > FUDGE) fudge = 0;\r
12998 \r
12999     if (WhiteOnMove(forwardMostMove)) {\r
13000         if(whiteNPS >= 0) lastTickLength = 0;\r
13001         timeRemaining = whiteTimeRemaining -= lastTickLength;\r
13002         DisplayWhiteClock(whiteTimeRemaining - fudge,\r
13003                           WhiteOnMove(currentMove));\r
13004     } else {\r
13005         if(blackNPS >= 0) lastTickLength = 0;\r
13006         timeRemaining = blackTimeRemaining -= lastTickLength;\r
13007         DisplayBlackClock(blackTimeRemaining - fudge,\r
13008                           !WhiteOnMove(currentMove));\r
13009     }\r
13010 \r
13011     if (CheckFlags()) return;\r
13012         \r
13013     tickStartTM = now;\r
13014     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;\r
13015     StartClockTimer(intendedTickLength);\r
13016 \r
13017     /* if the time remaining has fallen below the alarm threshold, sound the\r
13018      * alarm. if the alarm has sounded and (due to a takeback or time control\r
13019      * with increment) the time remaining has increased to a level above the\r
13020      * threshold, reset the alarm so it can sound again. \r
13021      */\r
13022     \r
13023     if (appData.icsActive && appData.icsAlarm) {\r
13024 \r
13025         /* make sure we are dealing with the user's clock */\r
13026         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||\r
13027                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))\r
13028            )) return;\r
13029 \r
13030         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {\r
13031             alarmSounded = FALSE;\r
13032         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { \r
13033             PlayAlarmSound();\r
13034             alarmSounded = TRUE;\r
13035         }\r
13036     }\r
13037 }\r
13038 \r
13039 \r
13040 /* A player has just moved, so stop the previously running\r
13041    clock and (if in clock mode) start the other one.\r
13042    We redisplay both clocks in case we're in ICS mode, because\r
13043    ICS gives us an update to both clocks after every move.\r
13044    Note that this routine is called *after* forwardMostMove\r
13045    is updated, so the last fractional tick must be subtracted\r
13046    from the color that is *not* on move now.\r
13047 */\r
13048 void\r
13049 SwitchClocks()\r
13050 {\r
13051     long lastTickLength;\r
13052     TimeMark now;\r
13053     int flagged = FALSE;\r
13054 \r
13055     GetTimeMark(&now);\r
13056 \r
13057     if (StopClockTimer() && appData.clockMode) {\r
13058         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13059         if (WhiteOnMove(forwardMostMove)) {\r
13060             if(blackNPS >= 0) lastTickLength = 0;\r
13061             blackTimeRemaining -= lastTickLength;\r
13062            /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
13063 //         if(pvInfoList[forwardMostMove-1].time == -1)\r
13064                  pvInfoList[forwardMostMove-1].time =               // use GUI time\r
13065                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;\r
13066         } else {\r
13067            if(whiteNPS >= 0) lastTickLength = 0;\r
13068            whiteTimeRemaining -= lastTickLength;\r
13069            /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
13070 //         if(pvInfoList[forwardMostMove-1].time == -1)\r
13071                  pvInfoList[forwardMostMove-1].time = \r
13072                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;\r
13073         }\r
13074         flagged = CheckFlags();\r
13075     }\r
13076     CheckTimeControl();\r
13077 \r
13078     if (flagged || !appData.clockMode) return;\r
13079 \r
13080     switch (gameMode) {\r
13081       case MachinePlaysBlack:\r
13082       case MachinePlaysWhite:\r
13083       case BeginningOfGame:\r
13084         if (pausing) return;\r
13085         break;\r
13086 \r
13087       case EditGame:\r
13088       case PlayFromGameFile:\r
13089       case IcsExamining:\r
13090         return;\r
13091 \r
13092       default:\r
13093         break;\r
13094     }\r
13095 \r
13096     tickStartTM = now;\r
13097     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
13098       whiteTimeRemaining : blackTimeRemaining);\r
13099     StartClockTimer(intendedTickLength);\r
13100 }\r
13101         \r
13102 \r
13103 /* Stop both clocks */\r
13104 void\r
13105 StopClocks()\r
13106 {       \r
13107     long lastTickLength;\r
13108     TimeMark now;\r
13109 \r
13110     if (!StopClockTimer()) return;\r
13111     if (!appData.clockMode) return;\r
13112 \r
13113     GetTimeMark(&now);\r
13114 \r
13115     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13116     if (WhiteOnMove(forwardMostMove)) {\r
13117         if(whiteNPS >= 0) lastTickLength = 0;\r
13118         whiteTimeRemaining -= lastTickLength;\r
13119         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));\r
13120     } else {\r
13121         if(blackNPS >= 0) lastTickLength = 0;\r
13122         blackTimeRemaining -= lastTickLength;\r
13123         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));\r
13124     }\r
13125     CheckFlags();\r
13126 }\r
13127         \r
13128 /* Start clock of player on move.  Time may have been reset, so\r
13129    if clock is already running, stop and restart it. */\r
13130 void\r
13131 StartClocks()\r
13132 {\r
13133     (void) StopClockTimer(); /* in case it was running already */\r
13134     DisplayBothClocks();\r
13135     if (CheckFlags()) return;\r
13136 \r
13137     if (!appData.clockMode) return;\r
13138     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;\r
13139 \r
13140     GetTimeMark(&tickStartTM);\r
13141     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
13142       whiteTimeRemaining : blackTimeRemaining);\r
13143 \r
13144    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */\r
13145     whiteNPS = blackNPS = -1; \r
13146     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'\r
13147        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white\r
13148         whiteNPS = first.nps;\r
13149     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'\r
13150        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black\r
13151         blackNPS = first.nps;\r
13152     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode\r
13153         whiteNPS = second.nps;\r
13154     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')\r
13155         blackNPS = second.nps;\r
13156     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);\r
13157 \r
13158     StartClockTimer(intendedTickLength);\r
13159 }\r
13160 \r
13161 char *\r
13162 TimeString(ms)\r
13163      long ms;\r
13164 {\r
13165     long second, minute, hour, day;\r
13166     char *sign = "";\r
13167     static char buf[32];\r
13168     \r
13169     if (ms > 0 && ms <= 9900) {\r
13170       /* convert milliseconds to tenths, rounding up */\r
13171       double tenths = floor( ((double)(ms + 99L)) / 100.00 );\r
13172 \r
13173       sprintf(buf, " %03.1f ", tenths/10.0);\r
13174       return buf;\r
13175     }\r
13176 \r
13177     /* convert milliseconds to seconds, rounding up */\r
13178     /* use floating point to avoid strangeness of integer division\r
13179        with negative dividends on many machines */\r
13180     second = (long) floor(((double) (ms + 999L)) / 1000.0);\r
13181 \r
13182     if (second < 0) {\r
13183         sign = "-";\r
13184         second = -second;\r
13185     }\r
13186     \r
13187     day = second / (60 * 60 * 24);\r
13188     second = second % (60 * 60 * 24);\r
13189     hour = second / (60 * 60);\r
13190     second = second % (60 * 60);\r
13191     minute = second / 60;\r
13192     second = second % 60;\r
13193     \r
13194     if (day > 0)\r
13195       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",\r
13196               sign, day, hour, minute, second);\r
13197     else if (hour > 0)\r
13198       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);\r
13199     else\r
13200       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);\r
13201     \r
13202     return buf;\r
13203 }\r
13204 \r
13205 \r
13206 /*\r
13207  * This is necessary because some C libraries aren't ANSI C compliant yet.\r
13208  */\r
13209 char *\r
13210 StrStr(string, match)\r
13211      char *string, *match;\r
13212 {\r
13213     int i, length;\r
13214     \r
13215     length = strlen(match);\r
13216     \r
13217     for (i = strlen(string) - length; i >= 0; i--, string++)\r
13218       if (!strncmp(match, string, length))\r
13219         return string;\r
13220     \r
13221     return NULL;\r
13222 }\r
13223 \r
13224 char *\r
13225 StrCaseStr(string, match)\r
13226      char *string, *match;\r
13227 {\r
13228     int i, j, length;\r
13229     \r
13230     length = strlen(match);\r
13231     \r
13232     for (i = strlen(string) - length; i >= 0; i--, string++) {\r
13233         for (j = 0; j < length; j++) {\r
13234             if (ToLower(match[j]) != ToLower(string[j]))\r
13235               break;\r
13236         }\r
13237         if (j == length) return string;\r
13238     }\r
13239 \r
13240     return NULL;\r
13241 }\r
13242 \r
13243 #ifndef _amigados\r
13244 int\r
13245 StrCaseCmp(s1, s2)\r
13246      char *s1, *s2;\r
13247 {\r
13248     char c1, c2;\r
13249     \r
13250     for (;;) {\r
13251         c1 = ToLower(*s1++);\r
13252         c2 = ToLower(*s2++);\r
13253         if (c1 > c2) return 1;\r
13254         if (c1 < c2) return -1;\r
13255         if (c1 == NULLCHAR) return 0;\r
13256     }\r
13257 }\r
13258 \r
13259 \r
13260 int\r
13261 ToLower(c)\r
13262      int c;\r
13263 {\r
13264     return isupper(c) ? tolower(c) : c;\r
13265 }\r
13266 \r
13267 \r
13268 int\r
13269 ToUpper(c)\r
13270      int c;\r
13271 {\r
13272     return islower(c) ? toupper(c) : c;\r
13273 }\r
13274 #endif /* !_amigados    */\r
13275 \r
13276 char *\r
13277 StrSave(s)\r
13278      char *s;\r
13279 {\r
13280     char *ret;\r
13281 \r
13282     if ((ret = (char *) malloc(strlen(s) + 1))) {\r
13283         strcpy(ret, s);\r
13284     }\r
13285     return ret;\r
13286 }\r
13287 \r
13288 char *\r
13289 StrSavePtr(s, savePtr)\r
13290      char *s, **savePtr;\r
13291 {\r
13292     if (*savePtr) {\r
13293         free(*savePtr);\r
13294     }\r
13295     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {\r
13296         strcpy(*savePtr, s);\r
13297     }\r
13298     return(*savePtr);\r
13299 }\r
13300 \r
13301 char *\r
13302 PGNDate()\r
13303 {\r
13304     time_t clock;\r
13305     struct tm *tm;\r
13306     char buf[MSG_SIZ];\r
13307 \r
13308     clock = time((time_t *)NULL);\r
13309     tm = localtime(&clock);\r
13310     sprintf(buf, "%04d.%02d.%02d",\r
13311             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);\r
13312     return StrSave(buf);\r
13313 }\r
13314 \r
13315 \r
13316 char *\r
13317 PositionToFEN(move, overrideCastling)\r
13318      int move;\r
13319      char *overrideCastling;\r
13320 {\r
13321     int i, j, fromX, fromY, toX, toY;\r
13322     int whiteToPlay;\r
13323     char buf[128];\r
13324     char *p, *q;\r
13325     int emptycount;\r
13326     ChessSquare piece;\r
13327 \r
13328     whiteToPlay = (gameMode == EditPosition) ?\r
13329       !blackPlaysFirst : (move % 2 == 0);\r
13330     p = buf;\r
13331 \r
13332     /* Piece placement data */\r
13333     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
13334         emptycount = 0;\r
13335         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
13336             if (boards[move][i][j] == EmptySquare) {\r
13337                 emptycount++;\r
13338             } else { ChessSquare piece = boards[move][i][j];\r
13339                 if (emptycount > 0) {\r
13340                     if(emptycount<10) /* [HGM] can be >= 10 */\r
13341                         *p++ = '0' + emptycount;\r
13342                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
13343                     emptycount = 0;\r
13344                 }\r
13345                 if(PieceToChar(piece) == '+') {\r
13346                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */\r
13347                     *p++ = '+';\r
13348                     piece = (ChessSquare)(DEMOTED piece);\r
13349                 } \r
13350                 *p++ = PieceToChar(piece);\r
13351                 if(p[-1] == '~') {\r
13352                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */\r
13353                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));\r
13354                     *p++ = '~';\r
13355                 }\r
13356             }\r
13357         }\r
13358         if (emptycount > 0) {\r
13359             if(emptycount<10) /* [HGM] can be >= 10 */\r
13360                 *p++ = '0' + emptycount;\r
13361             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
13362             emptycount = 0;\r
13363         }\r
13364         *p++ = '/';\r
13365     }\r
13366     *(p - 1) = ' ';\r
13367 \r
13368     /* [HGM] print Crazyhouse or Shogi holdings */\r
13369     if( gameInfo.holdingsWidth ) {\r
13370         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */\r
13371         q = p;\r
13372         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */\r
13373             piece = boards[move][i][BOARD_WIDTH-1];\r
13374             if( piece != EmptySquare )\r
13375               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)\r
13376                   *p++ = PieceToChar(piece);\r
13377         }\r
13378         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */\r
13379             piece = boards[move][BOARD_HEIGHT-i-1][0];\r
13380             if( piece != EmptySquare )\r
13381               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)\r
13382                   *p++ = PieceToChar(piece);\r
13383         }\r
13384 \r
13385         if( q == p ) *p++ = '-';\r
13386         *p++ = ']';\r
13387         *p++ = ' ';\r
13388     }\r
13389 \r
13390     /* Active color */\r
13391     *p++ = whiteToPlay ? 'w' : 'b';\r
13392     *p++ = ' ';\r
13393 \r
13394   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines\r
13395     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';\r
13396   } else {\r
13397   if(nrCastlingRights) {\r
13398      q = p;\r
13399      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {\r
13400        /* [HGM] write directly from rights */\r
13401            if(castlingRights[move][2] >= 0 &&\r
13402               castlingRights[move][0] >= 0   )\r
13403                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';\r
13404            if(castlingRights[move][2] >= 0 &&\r
13405               castlingRights[move][1] >= 0   )\r
13406                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';\r
13407            if(castlingRights[move][5] >= 0 &&\r
13408               castlingRights[move][3] >= 0   )\r
13409                 *p++ = castlingRights[move][3] + AAA;\r
13410            if(castlingRights[move][5] >= 0 &&\r
13411               castlingRights[move][4] >= 0   )\r
13412                 *p++ = castlingRights[move][4] + AAA;\r
13413      } else {\r
13414 \r
13415         /* [HGM] write true castling rights */\r
13416         if( nrCastlingRights == 6 ) {\r
13417             if(castlingRights[move][0] == BOARD_RGHT-1 &&\r
13418                castlingRights[move][2] >= 0  ) *p++ = 'K';\r
13419             if(castlingRights[move][1] == BOARD_LEFT &&\r
13420                castlingRights[move][2] >= 0  ) *p++ = 'Q';\r
13421             if(castlingRights[move][3] == BOARD_RGHT-1 &&\r
13422                castlingRights[move][5] >= 0  ) *p++ = 'k';\r
13423             if(castlingRights[move][4] == BOARD_LEFT &&\r
13424                castlingRights[move][5] >= 0  ) *p++ = 'q';\r
13425         }\r
13426      }\r
13427      if (q == p) *p++ = '-'; /* No castling rights */\r
13428      *p++ = ' ';\r
13429   }\r
13430 \r
13431   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
13432      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
13433     /* En passant target square */\r
13434     if (move > backwardMostMove) {\r
13435         fromX = moveList[move - 1][0] - AAA;\r
13436         fromY = moveList[move - 1][1] - ONE;\r
13437         toX = moveList[move - 1][2] - AAA;\r
13438         toY = moveList[move - 1][3] - ONE;\r
13439         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&\r
13440             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&\r
13441             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&\r
13442             fromX == toX) {\r
13443             /* 2-square pawn move just happened */\r
13444             *p++ = toX + AAA;\r
13445             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';\r
13446         } else {\r
13447             *p++ = '-';\r
13448         }\r
13449     } else {\r
13450         *p++ = '-';\r
13451     }\r
13452     *p++ = ' ';\r
13453   }\r
13454   }\r
13455 \r
13456     /* [HGM] find reversible plies */\r
13457     {   int i = 0, j=move;\r
13458 \r
13459         if (appData.debugMode) { int k;\r
13460             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);\r
13461             for(k=backwardMostMove; k<=forwardMostMove; k++)\r
13462                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);\r
13463 \r
13464         }\r
13465 \r
13466         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;\r
13467         if( j == backwardMostMove ) i += initialRulePlies;\r
13468         sprintf(p, "%d ", i);\r
13469         p += i>=100 ? 4 : i >= 10 ? 3 : 2;\r
13470     }\r
13471     /* Fullmove number */\r
13472     sprintf(p, "%d", (move / 2) + 1);\r
13473     \r
13474     return StrSave(buf);\r
13475 }\r
13476 \r
13477 Boolean\r
13478 ParseFEN(board, blackPlaysFirst, fen)\r
13479     Board board;\r
13480      int *blackPlaysFirst;\r
13481      char *fen;\r
13482 {\r
13483     int i, j;\r
13484     char *p;\r
13485     int emptycount;\r
13486     ChessSquare piece;\r
13487 \r
13488     p = fen;\r
13489 \r
13490     /* [HGM] by default clear Crazyhouse holdings, if present */\r
13491     if(gameInfo.holdingsWidth) {\r
13492        for(i=0; i<BOARD_HEIGHT; i++) {\r
13493            board[i][0]             = EmptySquare; /* black holdings */\r
13494            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */\r
13495            board[i][1]             = (ChessSquare) 0; /* black counts */\r
13496            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */\r
13497        }\r
13498     }\r
13499 \r
13500     /* Piece placement data */\r
13501     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
13502         j = 0;\r
13503         for (;;) {\r
13504             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {\r
13505                 if (*p == '/') p++;\r
13506                 emptycount = gameInfo.boardWidth - j;\r
13507                 while (emptycount--)\r
13508                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13509                 break;\r
13510 #if(BOARD_SIZE >= 10)\r
13511             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */\r
13512                 p++; emptycount=10;\r
13513                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
13514                 while (emptycount--)\r
13515                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13516 #endif\r
13517             } else if (isdigit(*p)) {\r
13518                 emptycount = *p++ - '0';\r
13519                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */\r
13520                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
13521                 while (emptycount--)\r
13522                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13523             } else if (*p == '+' || isalpha(*p)) {\r
13524                 if (j >= gameInfo.boardWidth) return FALSE;\r
13525                 if(*p=='+') {\r
13526                     piece = CharToPiece(*++p);\r
13527                     if(piece == EmptySquare) return FALSE; /* unknown piece */\r
13528                     piece = (ChessSquare) (PROMOTED piece ); p++;\r
13529                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */\r
13530                 } else piece = CharToPiece(*p++);\r
13531 \r
13532                 if(piece==EmptySquare) return FALSE; /* unknown piece */\r
13533                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */\r
13534                     piece = (ChessSquare) (PROMOTED piece);\r
13535                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */\r
13536                     p++;\r
13537                 }\r
13538                 board[i][(j++)+gameInfo.holdingsWidth] = piece;\r
13539             } else {\r
13540                 return FALSE;\r
13541             }\r
13542         }\r
13543     }\r
13544     while (*p == '/' || *p == ' ') p++;\r
13545 \r
13546     /* [HGM] look for Crazyhouse holdings here */\r
13547     while(*p==' ') p++;\r
13548     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {\r
13549         if(*p == '[') p++;\r
13550         if(*p == '-' ) *p++; /* empty holdings */ else {\r
13551             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */\r
13552             /* if we would allow FEN reading to set board size, we would   */\r
13553             /* have to add holdings and shift the board read so far here   */\r
13554             while( (piece = CharToPiece(*p) ) != EmptySquare ) {\r
13555                 *p++;\r
13556                 if((int) piece >= (int) BlackPawn ) {\r
13557                     i = (int)piece - (int)BlackPawn;\r
13558                     i = PieceToNumber((ChessSquare)i);\r
13559                     if( i >= gameInfo.holdingsSize ) return FALSE;\r
13560                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */\r
13561                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */\r
13562                 } else {\r
13563                     i = (int)piece - (int)WhitePawn;\r
13564                     i = PieceToNumber((ChessSquare)i);\r
13565                     if( i >= gameInfo.holdingsSize ) return FALSE;\r
13566                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */\r
13567                     board[i][BOARD_WIDTH-2]++;          /* black holdings */\r
13568                 }\r
13569             }\r
13570         }\r
13571         if(*p == ']') *p++;\r
13572     }\r
13573 \r
13574     while(*p == ' ') p++;\r
13575 \r
13576     /* Active color */\r
13577     switch (*p++) {\r
13578       case 'w':\r
13579         *blackPlaysFirst = FALSE;\r
13580         break;\r
13581       case 'b': \r
13582         *blackPlaysFirst = TRUE;\r
13583         break;\r
13584       default:\r
13585         return FALSE;\r
13586     }\r
13587 \r
13588     /* [HGM] We NO LONGER ignore the rest of the FEN notation */\r
13589     /* return the extra info in global variiables             */\r
13590 \r
13591     /* set defaults in case FEN is incomplete */\r
13592     FENepStatus = EP_UNKNOWN;\r
13593     for(i=0; i<nrCastlingRights; i++ ) {\r
13594         FENcastlingRights[i] =\r
13595             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];\r
13596     }   /* assume possible unless obviously impossible */\r
13597     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;\r
13598     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;\r
13599     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;\r
13600     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;\r
13601     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;\r
13602     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;\r
13603     FENrulePlies = 0;\r
13604 \r
13605     while(*p==' ') p++;\r
13606     if(nrCastlingRights) {\r
13607       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {\r
13608           /* castling indicator present, so default becomes no castlings */\r
13609           for(i=0; i<nrCastlingRights; i++ ) {\r
13610                  FENcastlingRights[i] = -1;\r
13611           }\r
13612       }\r
13613       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||\r
13614              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&\r
13615              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||\r
13616              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {\r
13617         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;\r
13618 \r
13619         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {\r
13620             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;\r
13621             if(board[0             ][i] == WhiteKing) whiteKingFile = i;\r
13622         }\r
13623         switch(c) {\r
13624           case'K':\r
13625               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);\r
13626               FENcastlingRights[0] = i != whiteKingFile ? i : -1;\r
13627               FENcastlingRights[2] = whiteKingFile;\r
13628               break;\r
13629           case'Q':\r
13630               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);\r
13631               FENcastlingRights[1] = i != whiteKingFile ? i : -1;\r
13632               FENcastlingRights[2] = whiteKingFile;\r
13633               break;\r
13634           case'k':\r
13635               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);\r
13636               FENcastlingRights[3] = i != blackKingFile ? i : -1;\r
13637               FENcastlingRights[5] = blackKingFile;\r
13638               break;\r
13639           case'q':\r
13640               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);\r
13641               FENcastlingRights[4] = i != blackKingFile ? i : -1;\r
13642               FENcastlingRights[5] = blackKingFile;\r
13643           case '-':\r
13644               break;\r
13645           default: /* FRC castlings */\r
13646               if(c >= 'a') { /* black rights */\r
13647                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
13648                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;\r
13649                   if(i == BOARD_RGHT) break;\r
13650                   FENcastlingRights[5] = i;\r
13651                   c -= AAA;\r
13652                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||\r
13653                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;\r
13654                   if(c > i)\r
13655                       FENcastlingRights[3] = c;\r
13656                   else\r
13657                       FENcastlingRights[4] = c;\r
13658               } else { /* white rights */\r
13659                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
13660                     if(board[0][i] == WhiteKing) break;\r
13661                   if(i == BOARD_RGHT) break;\r
13662                   FENcastlingRights[2] = i;\r
13663                   c -= AAA - 'a' + 'A';\r
13664                   if(board[0][c] >= WhiteKing) break;\r
13665                   if(c > i)\r
13666                       FENcastlingRights[0] = c;\r
13667                   else\r
13668                       FENcastlingRights[1] = c;\r
13669               }\r
13670         }\r
13671       }\r
13672     if (appData.debugMode) {\r
13673         fprintf(debugFP, "FEN castling rights:");\r
13674         for(i=0; i<nrCastlingRights; i++)\r
13675         fprintf(debugFP, " %d", FENcastlingRights[i]);\r
13676         fprintf(debugFP, "\n");\r
13677     }\r
13678 \r
13679       while(*p==' ') p++;\r
13680     }\r
13681 \r
13682     /* read e.p. field in games that know e.p. capture */\r
13683     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
13684        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
13685       if(*p=='-') {\r
13686         p++; FENepStatus = EP_NONE;\r
13687       } else {\r
13688          char c = *p++ - AAA;\r
13689 \r
13690          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;\r
13691          if(*p >= '0' && *p <='9') *p++;\r
13692          FENepStatus = c;\r
13693       }\r
13694     }\r
13695 \r
13696 \r
13697     if(sscanf(p, "%d", &i) == 1) {\r
13698         FENrulePlies = i; /* 50-move ply counter */\r
13699         /* (The move number is still ignored)    */\r
13700     }\r
13701 \r
13702     return TRUE;\r
13703 }\r
13704       \r
13705 void\r
13706 EditPositionPasteFEN(char *fen)\r
13707 {\r
13708   if (fen != NULL) {\r
13709     Board initial_position;\r
13710 \r
13711     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {\r
13712       DisplayError(_("Bad FEN position in clipboard"), 0);\r
13713       return ;\r
13714     } else {\r
13715       int savedBlackPlaysFirst = blackPlaysFirst;\r
13716       EditPositionEvent();\r
13717       blackPlaysFirst = savedBlackPlaysFirst;\r
13718       CopyBoard(boards[0], initial_position);\r
13719           /* [HGM] copy FEN attributes as well */\r
13720           {   int i;\r
13721               initialRulePlies = FENrulePlies;\r
13722               epStatus[0] = FENepStatus;\r
13723               for( i=0; i<nrCastlingRights; i++ )\r
13724                   castlingRights[0][i] = FENcastlingRights[i];\r
13725           }\r
13726       EditPositionDone();\r
13727       DisplayBothClocks();\r
13728       DrawPosition(FALSE, boards[currentMove]);\r
13729     }\r
13730   }\r
13731 }\r