Refined stalemate adjudication in Suicide, some cleanup
[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               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate\r
3754                 strcat(parseList[moveNum - 1], "#");\r
3755                 break;\r
3756             }\r
3757             strcat(parseList[moveNum - 1], " ");\r
3758             strcat(parseList[moveNum - 1], elapsed_time);\r
3759             /* currentMoveString is set as a side-effect of ParseOneMove */\r
3760             strcpy(moveList[moveNum - 1], currentMoveString);\r
3761             strcat(moveList[moveNum - 1], "\n");\r
3762           } else {\r
3763             /* Move from ICS was illegal!?  Punt. */\r
3764   if (appData.debugMode) {\r
3765     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);\r
3766     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
3767   }\r
3768 #if 0\r
3769             if (appData.testLegality && appData.debugMode) {\r
3770                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);\r
3771                 DisplayError(str, 0);\r
3772             }\r
3773 #endif\r
3774             strcpy(parseList[moveNum - 1], move_str);\r
3775             strcat(parseList[moveNum - 1], " ");\r
3776             strcat(parseList[moveNum - 1], elapsed_time);\r
3777             moveList[moveNum - 1][0] = NULLCHAR;\r
3778             fromX = fromY = toX = toY = -1;\r
3779           }\r
3780         }\r
3781   if (appData.debugMode) {\r
3782     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);\r
3783     setbuf(debugFP, NULL);\r
3784   }\r
3785 \r
3786 #if ZIPPY\r
3787         /* Send move to chess program (BEFORE animating it). */\r
3788         if (appData.zippyPlay && !newGame && newMove && \r
3789            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {\r
3790 \r
3791             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||\r
3792                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {\r
3793                 if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3794                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),\r
3795                             move_str);\r
3796                     DisplayError(str, 0);\r
3797                 } else {\r
3798                     if (first.sendTime) {\r
3799                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);\r
3800                     }\r
3801                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book\r
3802                     if (firstMove && !bookHit) {\r
3803                         firstMove = FALSE;\r
3804                         if (first.useColors) {\r
3805                           SendToProgram(gameMode == IcsPlayingWhite ?\r
3806                                         "white\ngo\n" :\r
3807                                         "black\ngo\n", &first);\r
3808                         } else {\r
3809                           SendToProgram("go\n", &first);\r
3810                         }\r
3811                         first.maybeThinking = TRUE;\r
3812                     }\r
3813                 }\r
3814             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {\r
3815               if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3816                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);\r
3817                 DisplayError(str, 0);\r
3818               } else {\r
3819                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!\r
3820                 SendMoveToProgram(moveNum - 1, &first);\r
3821               }\r
3822             }\r
3823         }\r
3824 #endif\r
3825     }\r
3826 \r
3827     if (moveNum > 0 && !gotPremove) {\r
3828         /* If move comes from a remote source, animate it.  If it\r
3829            isn't remote, it will have already been animated. */\r
3830         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {\r
3831             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);\r
3832         }\r
3833         if (!pausing && appData.highlightLastMove) {\r
3834             SetHighlights(fromX, fromY, toX, toY);\r
3835         }\r
3836     }\r
3837     \r
3838     /* Start the clocks */\r
3839     whiteFlag = blackFlag = FALSE;\r
3840     appData.clockMode = !(basetime == 0 && increment == 0);\r
3841     if (ticking == 0) {\r
3842       ics_clock_paused = TRUE;\r
3843       StopClocks();\r
3844     } else if (ticking == 1) {\r
3845       ics_clock_paused = FALSE;\r
3846     }\r
3847     if (gameMode == IcsIdle ||\r
3848         relation == RELATION_OBSERVING_STATIC ||\r
3849         relation == RELATION_EXAMINING ||\r
3850         ics_clock_paused)\r
3851       DisplayBothClocks();\r
3852     else\r
3853       StartClocks();\r
3854     \r
3855     /* Display opponents and material strengths */\r
3856     if (gameInfo.variant != VariantBughouse &&\r
3857         gameInfo.variant != VariantCrazyhouse) {\r
3858         if (tinyLayout || smallLayout) {\r
3859             if(gameInfo.variant == VariantNormal)\r
3860                 sprintf(str, "%s(%d) %s(%d) {%d %d}", \r
3861                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3862                     basetime, increment);\r
3863             else\r
3864                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", \r
3865                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3866                     basetime, increment, (int) gameInfo.variant);\r
3867         } else {\r
3868             if(gameInfo.variant == VariantNormal)\r
3869                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", \r
3870                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3871                     basetime, increment);\r
3872             else\r
3873                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", \r
3874                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3875                     basetime, increment, VariantName(gameInfo.variant));\r
3876         }\r
3877         DisplayTitle(str);\r
3878   if (appData.debugMode) {\r
3879     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);\r
3880   }\r
3881     }\r
3882 \r
3883    \r
3884     /* Display the board */\r
3885     if (!pausing) {\r
3886       \r
3887       if (appData.premove)\r
3888           if (!gotPremove || \r
3889              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||\r
3890              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))\r
3891               ClearPremoveHighlights();\r
3892 \r
3893       DrawPosition(FALSE, boards[currentMove]);\r
3894       DisplayMove(moveNum - 1);\r
3895       if (appData.ringBellAfterMoves && !ics_user_moved)\r
3896         RingBell();\r
3897     }\r
3898 \r
3899     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
3900 #if ZIPPY\r
3901     if(bookHit) { // [HGM] book: simulate book reply\r
3902         static char bookMove[MSG_SIZ]; // a bit generous?\r
3903 \r
3904         programStats.nodes = programStats.depth = programStats.time = \r
3905         programStats.score = programStats.got_only_move = 0;\r
3906         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
3907 \r
3908         strcpy(bookMove, "move ");\r
3909         strcat(bookMove, bookHit);\r
3910         HandleMachineMove(bookMove, &first);\r
3911     }\r
3912 #endif\r
3913 }\r
3914 \r
3915 void\r
3916 GetMoveListEvent()\r
3917 {\r
3918     char buf[MSG_SIZ];\r
3919     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {\r
3920         ics_getting_history = H_REQUESTED;\r
3921         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);\r
3922         SendToICS(buf);\r
3923     }\r
3924 }\r
3925 \r
3926 void\r
3927 AnalysisPeriodicEvent(force)\r
3928      int force;\r
3929 {\r
3930     if (((programStats.ok_to_send == 0 || programStats.line_is_book)\r
3931          && !force) || !appData.periodicUpdates)\r
3932       return;\r
3933 \r
3934     /* Send . command to Crafty to collect stats */\r
3935     SendToProgram(".\n", &first);\r
3936 \r
3937     /* Don't send another until we get a response (this makes\r
3938        us stop sending to old Crafty's which don't understand\r
3939        the "." command (sending illegal cmds resets node count & time,\r
3940        which looks bad)) */\r
3941     programStats.ok_to_send = 0;\r
3942 }\r
3943 \r
3944 void\r
3945 SendMoveToProgram(moveNum, cps)\r
3946      int moveNum;\r
3947      ChessProgramState *cps;\r
3948 {\r
3949     char buf[MSG_SIZ];\r
3950 \r
3951     if (cps->useUsermove) {\r
3952       SendToProgram("usermove ", cps);\r
3953     }\r
3954     if (cps->useSAN) {\r
3955       char *space;\r
3956       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {\r
3957         int len = space - parseList[moveNum];\r
3958         memcpy(buf, parseList[moveNum], len);\r
3959         buf[len++] = '\n';\r
3960         buf[len] = NULLCHAR;\r
3961       } else {\r
3962         sprintf(buf, "%s\n", parseList[moveNum]);\r
3963       }\r
3964       SendToProgram(buf, cps);\r
3965     } else {\r
3966       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */\r
3967         AlphaRank(moveList[moveNum], 4);\r
3968         SendToProgram(moveList[moveNum], cps);\r
3969         AlphaRank(moveList[moveNum], 4); // and back\r
3970       } else\r
3971       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by\r
3972        * the engine. It would be nice to have a better way to identify castle \r
3973        * moves here. */\r
3974       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)\r
3975                                                                          && cps->useOOCastle) {\r
3976         int fromX = moveList[moveNum][0] - AAA; \r
3977         int fromY = moveList[moveNum][1] - ONE;\r
3978         int toX = moveList[moveNum][2] - AAA; \r
3979         int toY = moveList[moveNum][3] - ONE;\r
3980         if((boards[moveNum][fromY][fromX] == WhiteKing \r
3981             && boards[moveNum][toY][toX] == WhiteRook)\r
3982            || (boards[moveNum][fromY][fromX] == BlackKing \r
3983                && boards[moveNum][toY][toX] == BlackRook)) {\r
3984           if(toX > fromX) SendToProgram("O-O\n", cps);\r
3985           else SendToProgram("O-O-O\n", cps);\r
3986         }\r
3987         else SendToProgram(moveList[moveNum], cps);\r
3988       }\r
3989       else SendToProgram(moveList[moveNum], cps);\r
3990       /* End of additions by Tord */\r
3991     }\r
3992 \r
3993     /* [HGM] setting up the opening has brought engine in force mode! */\r
3994     /*       Send 'go' if we are in a mode where machine should play. */\r
3995     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&\r
3996         (gameMode == TwoMachinesPlay   ||\r
3997 #ifdef ZIPPY\r
3998          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||\r
3999 #endif\r
4000          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {\r
4001         SendToProgram("go\n", cps);\r
4002   if (appData.debugMode) {\r
4003     fprintf(debugFP, "(extra)\n");\r
4004   }\r
4005     }\r
4006     setboardSpoiledMachineBlack = 0;\r
4007 }\r
4008 \r
4009 void\r
4010 SendMoveToICS(moveType, fromX, fromY, toX, toY)\r
4011      ChessMove moveType;\r
4012      int fromX, fromY, toX, toY;\r
4013 {\r
4014     char user_move[MSG_SIZ];\r
4015 \r
4016     switch (moveType) {\r
4017       default:\r
4018         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),\r
4019                 (int)moveType, fromX, fromY, toX, toY);\r
4020         DisplayError(user_move + strlen("say "), 0);\r
4021         break;\r
4022       case WhiteKingSideCastle:\r
4023       case BlackKingSideCastle:\r
4024       case WhiteQueenSideCastleWild:\r
4025       case BlackQueenSideCastleWild:\r
4026       /* PUSH Fabien */\r
4027       case WhiteHSideCastleFR:\r
4028       case BlackHSideCastleFR:\r
4029       /* POP Fabien */\r
4030         sprintf(user_move, "o-o\n");\r
4031         break;\r
4032       case WhiteQueenSideCastle:\r
4033       case BlackQueenSideCastle:\r
4034       case WhiteKingSideCastleWild:\r
4035       case BlackKingSideCastleWild:\r
4036       /* PUSH Fabien */\r
4037       case WhiteASideCastleFR:\r
4038       case BlackASideCastleFR:\r
4039       /* POP Fabien */\r
4040         sprintf(user_move, "o-o-o\n");\r
4041         break;\r
4042       case WhitePromotionQueen:\r
4043       case BlackPromotionQueen:\r
4044       case WhitePromotionRook:\r
4045       case BlackPromotionRook:\r
4046       case WhitePromotionBishop:\r
4047       case BlackPromotionBishop:\r
4048       case WhitePromotionKnight:\r
4049       case BlackPromotionKnight:\r
4050       case WhitePromotionKing:\r
4051       case BlackPromotionKing:\r
4052       case WhitePromotionChancellor:\r
4053       case BlackPromotionChancellor:\r
4054       case WhitePromotionArchbishop:\r
4055       case BlackPromotionArchbishop:\r
4056         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)\r
4057             sprintf(user_move, "%c%c%c%c=%c\n",\r
4058                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4059                 PieceToChar(WhiteFerz));\r
4060         else if(gameInfo.variant == VariantGreat)\r
4061             sprintf(user_move, "%c%c%c%c=%c\n",\r
4062                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4063                 PieceToChar(WhiteMan));\r
4064         else\r
4065             sprintf(user_move, "%c%c%c%c=%c\n",\r
4066                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4067                 PieceToChar(PromoPiece(moveType)));\r
4068         break;\r
4069       case WhiteDrop:\r
4070       case BlackDrop:\r
4071         sprintf(user_move, "%c@%c%c\n",\r
4072                 ToUpper(PieceToChar((ChessSquare) fromX)),\r
4073                 AAA + toX, ONE + toY);\r
4074         break;\r
4075       case NormalMove:\r
4076       case WhiteCapturesEnPassant:\r
4077       case BlackCapturesEnPassant:\r
4078       case IllegalMove:  /* could be a variant we don't quite understand */\r
4079         sprintf(user_move, "%c%c%c%c\n",\r
4080                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);\r
4081         break;\r
4082     }\r
4083     SendToICS(user_move);\r
4084 }\r
4085 \r
4086 void\r
4087 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)\r
4088      int rf, ff, rt, ft;\r
4089      char promoChar;\r
4090      char move[7];\r
4091 {\r
4092     if (rf == DROP_RANK) {\r
4093         sprintf(move, "%c@%c%c\n",\r
4094                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);\r
4095     } else {\r
4096         if (promoChar == 'x' || promoChar == NULLCHAR) {\r
4097             sprintf(move, "%c%c%c%c\n",\r
4098                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);\r
4099         } else {\r
4100             sprintf(move, "%c%c%c%c%c\n",\r
4101                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);\r
4102         }\r
4103     }\r
4104 }\r
4105 \r
4106 void\r
4107 ProcessICSInitScript(f)\r
4108      FILE *f;\r
4109 {\r
4110     char buf[MSG_SIZ];\r
4111 \r
4112     while (fgets(buf, MSG_SIZ, f)) {\r
4113         SendToICSDelayed(buf,(long)appData.msLoginDelay);\r
4114     }\r
4115 \r
4116     fclose(f);\r
4117 }\r
4118 \r
4119 \r
4120 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */\r
4121 void\r
4122 AlphaRank(char *move, int n)\r
4123 {\r
4124 //    char *p = move, c; int x, y;\r
4125 \r
4126     if (appData.debugMode) {\r
4127         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);\r
4128     }\r
4129 \r
4130     if(move[1]=='*' && \r
4131        move[2]>='0' && move[2]<='9' &&\r
4132        move[3]>='a' && move[3]<='x'    ) {\r
4133         move[1] = '@';\r
4134         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;\r
4135         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;\r
4136     } else\r
4137     if(move[0]>='0' && move[0]<='9' &&\r
4138        move[1]>='a' && move[1]<='x' &&\r
4139        move[2]>='0' && move[2]<='9' &&\r
4140        move[3]>='a' && move[3]<='x'    ) {\r
4141         /* input move, Shogi -> normal */\r
4142         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;\r
4143         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;\r
4144         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;\r
4145         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;\r
4146     } else\r
4147     if(move[1]=='@' &&\r
4148        move[3]>='0' && move[3]<='9' &&\r
4149        move[2]>='a' && move[2]<='x'    ) {\r
4150         move[1] = '*';\r
4151         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';\r
4152         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';\r
4153     } else\r
4154     if(\r
4155        move[0]>='a' && move[0]<='x' &&\r
4156        move[3]>='0' && move[3]<='9' &&\r
4157        move[2]>='a' && move[2]<='x'    ) {\r
4158          /* output move, normal -> Shogi */\r
4159         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';\r
4160         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';\r
4161         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';\r
4162         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';\r
4163         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';\r
4164     }\r
4165     if (appData.debugMode) {\r
4166         fprintf(debugFP, "   out = '%s'\n", move);\r
4167     }\r
4168 }\r
4169 \r
4170 /* Parser for moves from gnuchess, ICS, or user typein box */\r
4171 Boolean\r
4172 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)\r
4173      char *move;\r
4174      int moveNum;\r
4175      ChessMove *moveType;\r
4176      int *fromX, *fromY, *toX, *toY;\r
4177      char *promoChar;\r
4178 {       \r
4179     if (appData.debugMode) {\r
4180         fprintf(debugFP, "move to parse: %s\n", move);\r
4181     }\r
4182     *moveType = yylexstr(moveNum, move);\r
4183 \r
4184     switch (*moveType) {\r
4185       case WhitePromotionChancellor:\r
4186       case BlackPromotionChancellor:\r
4187       case WhitePromotionArchbishop:\r
4188       case BlackPromotionArchbishop:\r
4189       case WhitePromotionQueen:\r
4190       case BlackPromotionQueen:\r
4191       case WhitePromotionRook:\r
4192       case BlackPromotionRook:\r
4193       case WhitePromotionBishop:\r
4194       case BlackPromotionBishop:\r
4195       case WhitePromotionKnight:\r
4196       case BlackPromotionKnight:\r
4197       case WhitePromotionKing:\r
4198       case BlackPromotionKing:\r
4199       case NormalMove:\r
4200       case WhiteCapturesEnPassant:\r
4201       case BlackCapturesEnPassant:\r
4202       case WhiteKingSideCastle:\r
4203       case WhiteQueenSideCastle:\r
4204       case BlackKingSideCastle:\r
4205       case BlackQueenSideCastle:\r
4206       case WhiteKingSideCastleWild:\r
4207       case WhiteQueenSideCastleWild:\r
4208       case BlackKingSideCastleWild:\r
4209       case BlackQueenSideCastleWild:\r
4210       /* Code added by Tord: */\r
4211       case WhiteHSideCastleFR:\r
4212       case WhiteASideCastleFR:\r
4213       case BlackHSideCastleFR:\r
4214       case BlackASideCastleFR:\r
4215       /* End of code added by Tord */\r
4216       case IllegalMove:         /* bug or odd chess variant */\r
4217         *fromX = currentMoveString[0] - AAA;\r
4218         *fromY = currentMoveString[1] - ONE;\r
4219         *toX = currentMoveString[2] - AAA;\r
4220         *toY = currentMoveString[3] - ONE;\r
4221         *promoChar = currentMoveString[4];\r
4222         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||\r
4223             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {\r
4224     if (appData.debugMode) {\r
4225         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);\r
4226     }\r
4227             *fromX = *fromY = *toX = *toY = 0;\r
4228             return FALSE;\r
4229         }\r
4230         if (appData.testLegality) {\r
4231           return (*moveType != IllegalMove);\r
4232         } else {\r
4233           return !(fromX == fromY && toX == toY);\r
4234         }\r
4235 \r
4236       case WhiteDrop:\r
4237       case BlackDrop:\r
4238         *fromX = *moveType == WhiteDrop ?\r
4239           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
4240           (int) CharToPiece(ToLower(currentMoveString[0]));\r
4241         *fromY = DROP_RANK;\r
4242         *toX = currentMoveString[2] - AAA;\r
4243         *toY = currentMoveString[3] - ONE;\r
4244         *promoChar = NULLCHAR;\r
4245         return TRUE;\r
4246 \r
4247       case AmbiguousMove:\r
4248       case ImpossibleMove:\r
4249       case (ChessMove) 0:       /* end of file */\r
4250       case ElapsedTime:\r
4251       case Comment:\r
4252       case PGNTag:\r
4253       case NAG:\r
4254       case WhiteWins:\r
4255       case BlackWins:\r
4256       case GameIsDrawn:\r
4257       default:\r
4258     if (appData.debugMode) {\r
4259         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);\r
4260     }\r
4261         /* bug? */\r
4262         *fromX = *fromY = *toX = *toY = 0;\r
4263         *promoChar = NULLCHAR;\r
4264         return FALSE;\r
4265     }\r
4266 }\r
4267 \r
4268 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.\r
4269 // All positions will have equal probability, but the current method will not provide a unique\r
4270 // numbering scheme for arrays that contain 3 or more pieces of the same kind.\r
4271 #define DARK 1\r
4272 #define LITE 2\r
4273 #define ANY 3\r
4274 \r
4275 int squaresLeft[4];\r
4276 int piecesLeft[(int)BlackPawn];\r
4277 int seed, nrOfShuffles;\r
4278 \r
4279 void GetPositionNumber()\r
4280 {       // sets global variable seed\r
4281         int i;\r
4282 \r
4283         seed = appData.defaultFrcPosition;\r
4284         if(seed < 0) { // randomize based on time for negative FRC position numbers\r
4285                 for(i=0; i<50; i++) seed += random();\r
4286                 seed = random() ^ random() >> 8 ^ random() << 8;\r
4287                 if(seed<0) seed = -seed;\r
4288         }\r
4289 }\r
4290 \r
4291 int put(Board board, int pieceType, int rank, int n, int shade)\r
4292 // put the piece on the (n-1)-th empty squares of the given shade\r
4293 {\r
4294         int i;\r
4295 \r
4296         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {\r
4297                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {\r
4298                         board[rank][i] = (ChessSquare) pieceType;\r
4299                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;\r
4300                         squaresLeft[ANY]--;\r
4301                         piecesLeft[pieceType]--; \r
4302                         return i;\r
4303                 }\r
4304         }\r
4305         return -1;\r
4306 }\r
4307 \r
4308 \r
4309 void AddOnePiece(Board board, int pieceType, int rank, int shade)\r
4310 // calculate where the next piece goes, (any empty square), and put it there\r
4311 {\r
4312         int i;\r
4313 \r
4314         i = seed % squaresLeft[shade];\r
4315         nrOfShuffles *= squaresLeft[shade];\r
4316         seed /= squaresLeft[shade];\r
4317         put(board, pieceType, rank, i, shade);\r
4318 }\r
4319 \r
4320 void AddTwoPieces(Board board, int pieceType, int rank)\r
4321 // calculate where the next 2 identical pieces go, (any empty square), and put it there\r
4322 {\r
4323         int i, n=squaresLeft[ANY], j=n-1, k;\r
4324 \r
4325         k = n*(n-1)/2; // nr of possibilities, not counting permutations\r
4326         i = seed % k;  // pick one\r
4327         nrOfShuffles *= k;\r
4328         seed /= k;\r
4329         while(i >= j) i -= j--;\r
4330         j = n - 1 - j; i += j;\r
4331         put(board, pieceType, rank, j, ANY);\r
4332         put(board, pieceType, rank, i, ANY);\r
4333 }\r
4334 \r
4335 void SetUpShuffle(Board board, int number)\r
4336 {\r
4337         int i, p, first=1;\r
4338 \r
4339         GetPositionNumber(); nrOfShuffles = 1;\r
4340 \r
4341         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;\r
4342         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;\r
4343         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];\r
4344 \r
4345         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;\r
4346 \r
4347         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board\r
4348             p = (int) board[0][i];\r
4349             if(p < (int) BlackPawn) piecesLeft[p] ++;\r
4350             board[0][i] = EmptySquare;\r
4351         }\r
4352 \r
4353         if(PosFlags(0) & F_ALL_CASTLE_OK) {\r
4354             // shuffles restricted to allow normal castling put KRR first\r
4355             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle\r
4356                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);\r
4357             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles\r
4358                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);\r
4359             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling\r
4360                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);\r
4361             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling\r
4362                 put(board, WhiteRook, 0, 0, ANY);\r
4363             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle\r
4364         }\r
4365 \r
4366         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)\r
4367             // only for even boards make effort to put pairs of colorbound pieces on opposite colors\r
4368             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {\r
4369                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;\r
4370                 while(piecesLeft[p] >= 2) {\r
4371                     AddOnePiece(board, p, 0, LITE);\r
4372                     AddOnePiece(board, p, 0, DARK);\r
4373                 }\r
4374                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)\r
4375             }\r
4376 \r
4377         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {\r
4378             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere\r
4379             // but we leave King and Rooks for last, to possibly obey FRC restriction\r
4380             if(p == (int)WhiteRook) continue;\r
4381             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations\r
4382             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece\r
4383         }\r
4384 \r
4385         // now everything is placed, except perhaps King (Unicorn) and Rooks\r
4386 \r
4387         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {\r
4388             // Last King gets castling rights\r
4389             while(piecesLeft[(int)WhiteUnicorn]) {\r
4390                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);\r
4391                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;\r
4392             }\r
4393 \r
4394             while(piecesLeft[(int)WhiteKing]) {\r
4395                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);\r
4396                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;\r
4397             }\r
4398 \r
4399 \r
4400         } else {\r
4401             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);\r
4402             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);\r
4403         }\r
4404 \r
4405         // Only Rooks can be left; simply place them all\r
4406         while(piecesLeft[(int)WhiteRook]) {\r
4407                 i = put(board, WhiteRook, 0, 0, ANY);\r
4408                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights\r
4409                         if(first) {\r
4410                                 first=0;\r
4411                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;\r
4412                         }\r
4413                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;\r
4414                 }\r
4415         }\r
4416         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white\r
4417             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;\r
4418         }\r
4419 \r
4420         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize\r
4421 }\r
4422 \r
4423 int SetCharTable( char *table, const char * map )\r
4424 /* [HGM] moved here from winboard.c because of its general usefulness */\r
4425 /*       Basically a safe strcpy that uses the last character as King */\r
4426 {\r
4427     int result = FALSE; int NrPieces;\r
4428 \r
4429     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare \r
4430                     && NrPieces >= 12 && !(NrPieces&1)) {\r
4431         int i; /* [HGM] Accept even length from 12 to 34 */\r
4432 \r
4433         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';\r
4434         for( i=0; i<NrPieces/2-1; i++ ) {\r
4435             table[i] = map[i];\r
4436             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];\r
4437         }\r
4438         table[(int) WhiteKing]  = map[NrPieces/2-1];\r
4439         table[(int) BlackKing]  = map[NrPieces-1];\r
4440 \r
4441         result = TRUE;\r
4442     }\r
4443 \r
4444     return result;\r
4445 }\r
4446 \r
4447 void Prelude(Board board)\r
4448 {       // [HGM] superchess: random selection of exo-pieces\r
4449         int i, j, k; ChessSquare p; \r
4450         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };\r
4451 \r
4452         GetPositionNumber(); // use FRC position number\r
4453 \r
4454         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table\r
4455             SetCharTable(pieceToChar, appData.pieceToCharTable);\r
4456             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) \r
4457                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;\r
4458         }\r
4459 \r
4460         j = seed%4;                 seed /= 4; \r
4461         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);\r
4462         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4463         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4464         j = seed%3 + (seed%3 >= j); seed /= 3; \r
4465         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);\r
4466         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4467         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4468         j = seed%3;                 seed /= 3; \r
4469         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);\r
4470         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4471         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4472         j = seed%2 + (seed%2 >= j); seed /= 2; \r
4473         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);\r
4474         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4475         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4476         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);\r
4477         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);\r
4478         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);\r
4479         put(board, exoPieces[0],    0, 0, ANY);\r
4480         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];\r
4481 }\r
4482 \r
4483 void\r
4484 InitPosition(redraw)\r
4485      int redraw;\r
4486 {\r
4487     ChessSquare (* pieces)[BOARD_SIZE];\r
4488     int i, j, pawnRow, overrule,\r
4489     oldx = gameInfo.boardWidth,\r
4490     oldy = gameInfo.boardHeight,\r
4491     oldh = gameInfo.holdingsWidth,\r
4492     oldv = gameInfo.variant;\r
4493 \r
4494     currentMove = forwardMostMove = backwardMostMove = 0;\r
4495     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request\r
4496 \r
4497     /* [AS] Initialize pv info list [HGM] and game status */\r
4498     {\r
4499         for( i=0; i<MAX_MOVES; i++ ) {\r
4500             pvInfoList[i].depth = 0;\r
4501             epStatus[i]=EP_NONE;\r
4502             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
4503         }\r
4504 \r
4505         initialRulePlies = 0; /* 50-move counter start */\r
4506 \r
4507         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;\r
4508         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;\r
4509     }\r
4510 \r
4511     \r
4512     /* [HGM] logic here is completely changed. In stead of full positions */\r
4513     /* the initialized data only consist of the two backranks. The switch */\r
4514     /* selects which one we will use, which is than copied to the Board   */\r
4515     /* initialPosition, which for the rest is initialized by Pawns and    */\r
4516     /* empty squares. This initial position is then copied to boards[0],  */\r
4517     /* possibly after shuffling, so that it remains available.            */\r
4518 \r
4519     gameInfo.holdingsWidth = 0; /* default board sizes */\r
4520     gameInfo.boardWidth    = 8;\r
4521     gameInfo.boardHeight   = 8;\r
4522     gameInfo.holdingsSize  = 0;\r
4523     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */\r
4524     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */\r
4525     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); \r
4526 \r
4527     switch (gameInfo.variant) {\r
4528     case VariantFischeRandom:\r
4529       shuffleOpenings = TRUE;\r
4530     default:\r
4531       pieces = FIDEArray;\r
4532       break;\r
4533     case VariantShatranj:\r
4534       pieces = ShatranjArray;\r
4535       nrCastlingRights = 0;\r
4536       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); \r
4537       break;\r
4538     case VariantTwoKings:\r
4539       pieces = twoKingsArray;\r
4540       break;\r
4541     case VariantCapaRandom:\r
4542       shuffleOpenings = TRUE;\r
4543     case VariantCapablanca:\r
4544       pieces = CapablancaArray;\r
4545       gameInfo.boardWidth = 10;\r
4546       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
4547       break;\r
4548     case VariantGothic:\r
4549       pieces = GothicArray;\r
4550       gameInfo.boardWidth = 10;\r
4551       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
4552       break;\r
4553     case VariantJanus:\r
4554       pieces = JanusArray;\r
4555       gameInfo.boardWidth = 10;\r
4556       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); \r
4557       nrCastlingRights = 6;\r
4558         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
4559         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
4560         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;\r
4561         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
4562         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4563         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;\r
4564       break;\r
4565     case VariantFalcon:\r
4566       pieces = FalconArray;\r
4567       gameInfo.boardWidth = 10;\r
4568       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); \r
4569       break;\r
4570     case VariantXiangqi:\r
4571       pieces = XiangqiArray;\r
4572       gameInfo.boardWidth  = 9;\r
4573       gameInfo.boardHeight = 10;\r
4574       nrCastlingRights = 0;\r
4575       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); \r
4576       break;\r
4577     case VariantShogi:\r
4578       pieces = ShogiArray;\r
4579       gameInfo.boardWidth  = 9;\r
4580       gameInfo.boardHeight = 9;\r
4581       gameInfo.holdingsSize = 7;\r
4582       nrCastlingRights = 0;\r
4583       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); \r
4584       break;\r
4585     case VariantCourier:\r
4586       pieces = CourierArray;\r
4587       gameInfo.boardWidth  = 12;\r
4588       nrCastlingRights = 0;\r
4589       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); \r
4590       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
4591       break;\r
4592     case VariantKnightmate:\r
4593       pieces = KnightmateArray;\r
4594       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); \r
4595       break;\r
4596     case VariantFairy:\r
4597       pieces = fairyArray;\r
4598       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); \r
4599       break;\r
4600     case VariantGreat:\r
4601       pieces = GreatArray;\r
4602       gameInfo.boardWidth = 10;\r
4603       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");\r
4604       gameInfo.holdingsSize = 8;\r
4605       break;\r
4606     case VariantSuper:\r
4607       pieces = FIDEArray;\r
4608       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");\r
4609       gameInfo.holdingsSize = 8;\r
4610       startedFromSetupPosition = TRUE;\r
4611       break;\r
4612     case VariantCrazyhouse:\r
4613     case VariantBughouse:\r
4614       pieces = FIDEArray;\r
4615       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); \r
4616       gameInfo.holdingsSize = 5;\r
4617       break;\r
4618     case VariantWildCastle:\r
4619       pieces = FIDEArray;\r
4620       /* !!?shuffle with kings guaranteed to be on d or e file */\r
4621       shuffleOpenings = 1;\r
4622       break;\r
4623     case VariantNoCastle:\r
4624       pieces = FIDEArray;\r
4625       nrCastlingRights = 0;\r
4626       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
4627       /* !!?unconstrained back-rank shuffle */\r
4628       shuffleOpenings = 1;\r
4629       break;\r
4630     }\r
4631 \r
4632     overrule = 0;\r
4633     if(appData.NrFiles >= 0) {\r
4634         if(gameInfo.boardWidth != appData.NrFiles) overrule++;\r
4635         gameInfo.boardWidth = appData.NrFiles;\r
4636     }\r
4637     if(appData.NrRanks >= 0) {\r
4638         gameInfo.boardHeight = appData.NrRanks;\r
4639     }\r
4640     if(appData.holdingsSize >= 0) {\r
4641         i = appData.holdingsSize;\r
4642         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;\r
4643         gameInfo.holdingsSize = i;\r
4644     }\r
4645     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;\r
4646     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)\r
4647         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);\r
4648 \r
4649     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */\r
4650     if(pawnRow < 1) pawnRow = 1;\r
4651 \r
4652     /* User pieceToChar list overrules defaults */\r
4653     if(appData.pieceToCharTable != NULL)\r
4654         SetCharTable(pieceToChar, appData.pieceToCharTable);\r
4655 \r
4656     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;\r
4657 \r
4658         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)\r
4659             s = (ChessSquare) 0; /* account holding counts in guard band */\r
4660         for( i=0; i<BOARD_HEIGHT; i++ )\r
4661             initialPosition[i][j] = s;\r
4662 \r
4663         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;\r
4664         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];\r
4665         initialPosition[pawnRow][j] = WhitePawn;\r
4666         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;\r
4667         if(gameInfo.variant == VariantXiangqi) {\r
4668             if(j&1) {\r
4669                 initialPosition[pawnRow][j] = \r
4670                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;\r
4671                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {\r
4672                    initialPosition[2][j] = WhiteCannon;\r
4673                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;\r
4674                 }\r
4675             }\r
4676         }\r
4677         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];\r
4678     }\r
4679     if( (gameInfo.variant == VariantShogi) && !overrule ) {\r
4680 \r
4681             j=BOARD_LEFT+1;\r
4682             initialPosition[1][j] = WhiteBishop;\r
4683             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;\r
4684             j=BOARD_RGHT-2;\r
4685             initialPosition[1][j] = WhiteRook;\r
4686             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;\r
4687     }\r
4688 \r
4689     if( nrCastlingRights == -1) {\r
4690         /* [HGM] Build normal castling rights (must be done after board sizing!) */\r
4691         /*       This sets default castling rights from none to normal corners   */\r
4692         /* Variants with other castling rights must set them themselves above    */\r
4693         nrCastlingRights = 6;\r
4694        \r
4695         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
4696         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
4697         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;\r
4698         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
4699         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4700         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;\r
4701      }\r
4702 \r
4703      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);\r
4704      if(gameInfo.variant == VariantGreat) { // promotion commoners\r
4705         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;\r
4706         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;\r
4707         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;\r
4708         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;\r
4709      }\r
4710 #if 0\r
4711     if(gameInfo.variant == VariantFischeRandom) {\r
4712       if( appData.defaultFrcPosition < 0 ) {\r
4713         ShuffleFRC( initialPosition );\r
4714       }\r
4715       else {\r
4716         SetupFRC( initialPosition, appData.defaultFrcPosition );\r
4717       }\r
4718       startedFromSetupPosition = TRUE;\r
4719     } else \r
4720 #else\r
4721   if (appData.debugMode) {\r
4722     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);\r
4723   }\r
4724     if(shuffleOpenings) {\r
4725         SetUpShuffle(initialPosition, appData.defaultFrcPosition);\r
4726         startedFromSetupPosition = TRUE;\r
4727     }\r
4728 #endif\r
4729     if(startedFromPositionFile) {\r
4730       /* [HGM] loadPos: use PositionFile for every new game */\r
4731       CopyBoard(initialPosition, filePosition);\r
4732       for(i=0; i<nrCastlingRights; i++)\r
4733           castlingRights[0][i] = initialRights[i] = fileRights[i];\r
4734       startedFromSetupPosition = TRUE;\r
4735     }\r
4736 \r
4737     CopyBoard(boards[0], initialPosition);\r
4738 \r
4739     if(oldx != gameInfo.boardWidth ||\r
4740        oldy != gameInfo.boardHeight ||\r
4741        oldh != gameInfo.holdingsWidth\r
4742 #ifdef GOTHIC\r
4743        || oldv == VariantGothic ||        // For licensing popups\r
4744        gameInfo.variant == VariantGothic\r
4745 #endif\r
4746 #ifdef FALCON\r
4747        || oldv == VariantFalcon ||\r
4748        gameInfo.variant == VariantFalcon\r
4749 #endif\r
4750                                          )\r
4751             InitDrawingSizes(-2 ,0);\r
4752 \r
4753     if (redraw)\r
4754       DrawPosition(TRUE, boards[currentMove]);\r
4755 }\r
4756 \r
4757 void\r
4758 SendBoard(cps, moveNum)\r
4759      ChessProgramState *cps;\r
4760      int moveNum;\r
4761 {\r
4762     char message[MSG_SIZ];\r
4763     \r
4764     if (cps->useSetboard) {\r
4765       char* fen = PositionToFEN(moveNum, cps->fenOverride);\r
4766       sprintf(message, "setboard %s\n", fen);\r
4767       SendToProgram(message, cps);\r
4768       free(fen);\r
4769 \r
4770     } else {\r
4771       ChessSquare *bp;\r
4772       int i, j;\r
4773       /* Kludge to set black to move, avoiding the troublesome and now\r
4774        * deprecated "black" command.\r
4775        */\r
4776       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);\r
4777 \r
4778       SendToProgram("edit\n", cps);\r
4779       SendToProgram("#\n", cps);\r
4780       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4781         bp = &boards[moveNum][i][BOARD_LEFT];\r
4782         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4783           if ((int) *bp < (int) BlackPawn) {\r
4784             sprintf(message, "%c%c%c\n", PieceToChar(*bp), \r
4785                     AAA + j, ONE + i);\r
4786             if(message[0] == '+' || message[0] == '~') {\r
4787                 sprintf(message, "%c%c%c+\n",\r
4788                         PieceToChar((ChessSquare)(DEMOTED *bp)),\r
4789                         AAA + j, ONE + i);\r
4790             }\r
4791             if(cps->alphaRank) { /* [HGM] shogi: translate coords */\r
4792                 message[1] = BOARD_RGHT   - 1 - j + '1';\r
4793                 message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
4794             }\r
4795             SendToProgram(message, cps);\r
4796           }\r
4797         }\r
4798       }\r
4799     \r
4800       SendToProgram("c\n", cps);\r
4801       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4802         bp = &boards[moveNum][i][BOARD_LEFT];\r
4803         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4804           if (((int) *bp != (int) EmptySquare)\r
4805               && ((int) *bp >= (int) BlackPawn)) {\r
4806             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),\r
4807                     AAA + j, ONE + i);\r
4808             if(message[0] == '+' || message[0] == '~') {\r
4809                 sprintf(message, "%c%c%c+\n",\r
4810                         PieceToChar((ChessSquare)(DEMOTED *bp)),\r
4811                         AAA + j, ONE + i);\r
4812             }\r
4813             if(cps->alphaRank) { /* [HGM] shogi: translate coords */\r
4814                 message[1] = BOARD_RGHT   - 1 - j + '1';\r
4815                 message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
4816             }\r
4817             SendToProgram(message, cps);\r
4818           }\r
4819         }\r
4820       }\r
4821     \r
4822       SendToProgram(".\n", cps);\r
4823     }\r
4824     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */\r
4825 }\r
4826 \r
4827 int\r
4828 IsPromotion(fromX, fromY, toX, toY)\r
4829      int fromX, fromY, toX, toY;\r
4830 {\r
4831     /* [HGM] add Shogi promotions */\r
4832     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;\r
4833     ChessSquare piece;\r
4834 \r
4835     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||\r
4836       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;\r
4837    /* [HGM] Note to self: line above also weeds out drops */\r
4838     piece = boards[currentMove][fromY][fromX];\r
4839     if(gameInfo.variant == VariantShogi) {\r
4840         promotionZoneSize = 3;\r
4841         highestPromotingPiece = (int)WhiteKing;\r
4842         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,\r
4843            and if in normal chess we then allow promotion to King, why not\r
4844            allow promotion of other piece in Shogi?                         */\r
4845     }\r
4846     if((int)piece >= BlackPawn) {\r
4847         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)\r
4848              return FALSE;\r
4849         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;\r
4850     } else {\r
4851         if(  toY < BOARD_HEIGHT - promotionZoneSize &&\r
4852            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;\r
4853     }\r
4854     return ( (int)piece <= highestPromotingPiece );\r
4855 }\r
4856 \r
4857 int\r
4858 InPalace(row, column)\r
4859      int row, column;\r
4860 {   /* [HGM] for Xiangqi */\r
4861     if( (row < 3 || row > BOARD_HEIGHT-4) &&\r
4862          column < (BOARD_WIDTH + 4)/2 &&\r
4863          column > (BOARD_WIDTH - 5)/2 ) return TRUE;\r
4864     return FALSE;\r
4865 }\r
4866 \r
4867 int\r
4868 PieceForSquare (x, y)\r
4869      int x;\r
4870      int y;\r
4871 {\r
4872   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)\r
4873      return -1;\r
4874   else\r
4875      return boards[currentMove][y][x];\r
4876 }\r
4877 \r
4878 int\r
4879 OKToStartUserMove(x, y)\r
4880      int x, y;\r
4881 {\r
4882     ChessSquare from_piece;\r
4883     int white_piece;\r
4884 \r
4885     if (matchMode) return FALSE;\r
4886     if (gameMode == EditPosition) return TRUE;\r
4887 \r
4888     if (x >= 0 && y >= 0)\r
4889       from_piece = boards[currentMove][y][x];\r
4890     else\r
4891       from_piece = EmptySquare;\r
4892 \r
4893     if (from_piece == EmptySquare) return FALSE;\r
4894 \r
4895     white_piece = (int)from_piece >= (int)WhitePawn &&\r
4896       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */\r
4897 \r
4898     switch (gameMode) {\r
4899       case PlayFromGameFile:\r
4900       case AnalyzeFile:\r
4901       case TwoMachinesPlay:\r
4902       case EndOfGame:\r
4903         return FALSE;\r
4904 \r
4905       case IcsObserving:\r
4906       case IcsIdle:\r
4907         return FALSE;\r
4908 \r
4909       case MachinePlaysWhite:\r
4910       case IcsPlayingBlack:\r
4911         if (appData.zippyPlay) return FALSE;\r
4912         if (white_piece) {\r
4913             DisplayMoveError(_("You are playing Black"));\r
4914             return FALSE;\r
4915         }\r
4916         break;\r
4917 \r
4918       case MachinePlaysBlack:\r
4919       case IcsPlayingWhite:\r
4920         if (appData.zippyPlay) return FALSE;\r
4921         if (!white_piece) {\r
4922             DisplayMoveError(_("You are playing White"));\r
4923             return FALSE;\r
4924         }\r
4925         break;\r
4926 \r
4927       case EditGame:\r
4928         if (!white_piece && WhiteOnMove(currentMove)) {\r
4929             DisplayMoveError(_("It is White's turn"));\r
4930             return FALSE;\r
4931         }           \r
4932         if (white_piece && !WhiteOnMove(currentMove)) {\r
4933             DisplayMoveError(_("It is Black's turn"));\r
4934             return FALSE;\r
4935         }           \r
4936         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {\r
4937             /* Editing correspondence game history */\r
4938             /* Could disallow this or prompt for confirmation */\r
4939             cmailOldMove = -1;\r
4940         }\r
4941         if (currentMove < forwardMostMove) {\r
4942             /* Discarding moves */\r
4943             /* Could prompt for confirmation here,\r
4944                but I don't think that's such a good idea */\r
4945             forwardMostMove = currentMove;\r
4946         }\r
4947         break;\r
4948 \r
4949       case BeginningOfGame:\r
4950         if (appData.icsActive) return FALSE;\r
4951         if (!appData.noChessProgram) {\r
4952             if (!white_piece) {\r
4953                 DisplayMoveError(_("You are playing White"));\r
4954                 return FALSE;\r
4955             }\r
4956         }\r
4957         break;\r
4958         \r
4959       case Training:\r
4960         if (!white_piece && WhiteOnMove(currentMove)) {\r
4961             DisplayMoveError(_("It is White's turn"));\r
4962             return FALSE;\r
4963         }           \r
4964         if (white_piece && !WhiteOnMove(currentMove)) {\r
4965             DisplayMoveError(_("It is Black's turn"));\r
4966             return FALSE;\r
4967         }           \r
4968         break;\r
4969 \r
4970       default:\r
4971       case IcsExamining:\r
4972         break;\r
4973     }\r
4974     if (currentMove != forwardMostMove && gameMode != AnalyzeMode\r
4975         && gameMode != AnalyzeFile && gameMode != Training) {\r
4976         DisplayMoveError(_("Displayed position is not current"));\r
4977         return FALSE;\r
4978     }\r
4979     return TRUE;\r
4980 }\r
4981 \r
4982 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;\r
4983 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;\r
4984 int lastLoadGameUseList = FALSE;\r
4985 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];\r
4986 ChessMove lastLoadGameStart = (ChessMove) 0;\r
4987 \r
4988 \r
4989 ChessMove\r
4990 UserMoveTest(fromX, fromY, toX, toY, promoChar)\r
4991      int fromX, fromY, toX, toY;\r
4992      int promoChar;\r
4993 {\r
4994     ChessMove moveType;\r
4995     ChessSquare pdown, pup;\r
4996 \r
4997     if (fromX < 0 || fromY < 0) return ImpossibleMove;\r
4998     if ((fromX == toX) && (fromY == toY)) {\r
4999         return ImpossibleMove;\r
5000     }\r
5001 \r
5002     /* [HGM] suppress all moves into holdings area and guard band */\r
5003     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )\r
5004             return ImpossibleMove;\r
5005 \r
5006     /* [HGM] <sameColor> moved to here from winboard.c */\r
5007     /* note: this code seems to exist for filtering out some obviously illegal premoves */\r
5008     pdown = boards[currentMove][fromY][fromX];\r
5009     pup = boards[currentMove][toY][toX];\r
5010     if (    gameMode != EditPosition &&\r
5011             (WhitePawn <= pdown && pdown < BlackPawn &&\r
5012              WhitePawn <= pup && pup < BlackPawn  ||\r
5013              BlackPawn <= pdown && pdown < EmptySquare &&\r
5014              BlackPawn <= pup && pup < EmptySquare \r
5015             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&\r
5016                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||\r
5017                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) \r
5018         )           )\r
5019          return ImpossibleMove;\r
5020 \r
5021     /* Check if the user is playing in turn.  This is complicated because we\r
5022        let the user "pick up" a piece before it is his turn.  So the piece he\r
5023        tried to pick up may have been captured by the time he puts it down!\r
5024        Therefore we use the color the user is supposed to be playing in this\r
5025        test, not the color of the piece that is currently on the starting\r
5026        square---except in EditGame mode, where the user is playing both\r
5027        sides; fortunately there the capture race can't happen.  (It can\r
5028        now happen in IcsExamining mode, but that's just too bad.  The user\r
5029        will get a somewhat confusing message in that case.)\r
5030        */\r
5031 \r
5032     switch (gameMode) {\r
5033       case PlayFromGameFile:\r
5034       case AnalyzeFile:\r
5035       case TwoMachinesPlay:\r
5036       case EndOfGame:\r
5037       case IcsObserving:\r
5038       case IcsIdle:\r
5039         /* We switched into a game mode where moves are not accepted,\r
5040            perhaps while the mouse button was down. */\r
5041         return ImpossibleMove;\r
5042 \r
5043       case MachinePlaysWhite:\r
5044         /* User is moving for Black */\r
5045         if (WhiteOnMove(currentMove)) {\r
5046             DisplayMoveError(_("It is White's turn"));\r
5047             return ImpossibleMove;\r
5048         }\r
5049         break;\r
5050 \r
5051       case MachinePlaysBlack:\r
5052         /* User is moving for White */\r
5053         if (!WhiteOnMove(currentMove)) {\r
5054             DisplayMoveError(_("It is Black's turn"));\r
5055             return ImpossibleMove;\r
5056         }\r
5057         break;\r
5058 \r
5059       case EditGame:\r
5060       case IcsExamining:\r
5061       case BeginningOfGame:\r
5062       case AnalyzeMode:\r
5063       case Training:\r
5064         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&\r
5065             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {\r
5066             /* User is moving for Black */\r
5067             if (WhiteOnMove(currentMove)) {\r
5068                 DisplayMoveError(_("It is White's turn"));\r
5069                 return ImpossibleMove;\r
5070             }\r
5071         } else {\r
5072             /* User is moving for White */\r
5073             if (!WhiteOnMove(currentMove)) {\r
5074                 DisplayMoveError(_("It is Black's turn"));\r
5075                 return ImpossibleMove;\r
5076             }\r
5077         }\r
5078         break;\r
5079 \r
5080       case IcsPlayingBlack:\r
5081         /* User is moving for Black */\r
5082         if (WhiteOnMove(currentMove)) {\r
5083             if (!appData.premove) {\r
5084                 DisplayMoveError(_("It is White's turn"));\r
5085             } else if (toX >= 0 && toY >= 0) {\r
5086                 premoveToX = toX;\r
5087                 premoveToY = toY;\r
5088                 premoveFromX = fromX;\r
5089                 premoveFromY = fromY;\r
5090                 premovePromoChar = promoChar;\r
5091                 gotPremove = 1;\r
5092                 if (appData.debugMode) \r
5093                     fprintf(debugFP, "Got premove: fromX %d,"\r
5094                             "fromY %d, toX %d, toY %d\n",\r
5095                             fromX, fromY, toX, toY);\r
5096             }\r
5097             return ImpossibleMove;\r
5098         }\r
5099         break;\r
5100 \r
5101       case IcsPlayingWhite:\r
5102         /* User is moving for White */\r
5103         if (!WhiteOnMove(currentMove)) {\r
5104             if (!appData.premove) {\r
5105                 DisplayMoveError(_("It is Black's turn"));\r
5106             } else if (toX >= 0 && toY >= 0) {\r
5107                 premoveToX = toX;\r
5108                 premoveToY = toY;\r
5109                 premoveFromX = fromX;\r
5110                 premoveFromY = fromY;\r
5111                 premovePromoChar = promoChar;\r
5112                 gotPremove = 1;\r
5113                 if (appData.debugMode) \r
5114                     fprintf(debugFP, "Got premove: fromX %d,"\r
5115                             "fromY %d, toX %d, toY %d\n",\r
5116                             fromX, fromY, toX, toY);\r
5117             }\r
5118             return ImpossibleMove;\r
5119         }\r
5120         break;\r
5121 \r
5122       default:\r
5123         break;\r
5124 \r
5125       case EditPosition:\r
5126         /* EditPosition, empty square, or different color piece;\r
5127            click-click move is possible */\r
5128         if (toX == -2 || toY == -2) {\r
5129             boards[0][fromY][fromX] = EmptySquare;\r
5130             return AmbiguousMove;\r
5131         } else if (toX >= 0 && toY >= 0) {\r
5132             boards[0][toY][toX] = boards[0][fromY][fromX];\r
5133             boards[0][fromY][fromX] = EmptySquare;\r
5134             return AmbiguousMove;\r
5135         }\r
5136         return ImpossibleMove;\r
5137     }\r
5138 \r
5139     /* [HGM] If move started in holdings, it means a drop */\r
5140     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { \r
5141          if( pup != EmptySquare ) return ImpossibleMove;\r
5142          if(appData.testLegality) {\r
5143              /* it would be more logical if LegalityTest() also figured out\r
5144               * which drops are legal. For now we forbid pawns on back rank.\r
5145               * Shogi is on its own here...\r
5146               */\r
5147              if( (pdown == WhitePawn || pdown == BlackPawn) &&\r
5148                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )\r
5149                  return(ImpossibleMove); /* no pawn drops on 1st/8th */\r
5150          }\r
5151          return WhiteDrop; /* Not needed to specify white or black yet */\r
5152     }\r
5153 \r
5154     userOfferedDraw = FALSE;\r
5155         \r
5156     /* [HGM] always test for legality, to get promotion info */\r
5157     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),\r
5158                           epStatus[currentMove], castlingRights[currentMove],\r
5159                                          fromY, fromX, toY, toX, promoChar);\r
5160 \r
5161     /* [HGM] but possibly ignore an IllegalMove result */\r
5162     if (appData.testLegality) {\r
5163         if (moveType == IllegalMove || moveType == ImpossibleMove) {\r
5164             DisplayMoveError(_("Illegal move"));\r
5165             return ImpossibleMove;\r
5166         }\r
5167     }\r
5168 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);\r
5169     return moveType;\r
5170     /* [HGM] <popupFix> in stead of calling FinishMove directly, this\r
5171        function is made into one that returns an OK move type if FinishMove\r
5172        should be called. This to give the calling driver routine the\r
5173        opportunity to finish the userMove input with a promotion popup,\r
5174        without bothering the user with this for invalid or illegal moves */\r
5175 \r
5176 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */\r
5177 }\r
5178 \r
5179 /* Common tail of UserMoveEvent and DropMenuEvent */\r
5180 int\r
5181 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)\r
5182      ChessMove moveType;\r
5183      int fromX, fromY, toX, toY;\r
5184      /*char*/int promoChar;\r
5185 {\r
5186     char *bookHit = 0;\r
5187 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);\r
5188     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { \r
5189         // [HGM] superchess: suppress promotions to non-available piece\r
5190         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));\r
5191         if(WhiteOnMove(currentMove)) {\r
5192             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;\r
5193         } else {\r
5194             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;\r
5195         }\r
5196     }\r
5197 \r
5198     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion\r
5199        move type in caller when we know the move is a legal promotion */\r
5200     if(moveType == NormalMove && promoChar)\r
5201         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);\r
5202 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);\r
5203     /* [HGM] convert drag-and-drop piece drops to standard form */\r
5204     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {\r
5205          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
5206          fromX = boards[currentMove][fromY][fromX];\r
5207          fromY = DROP_RANK;\r
5208     }\r
5209 \r
5210     /* [HGM] <popupFix> The following if has been moved here from\r
5211        UserMoveEvent(). Because it seemed to belon here (why not allow\r
5212        piece drops in training games?), and because it can only be\r
5213        performed after it is known to what we promote. */\r
5214     if (gameMode == Training) {\r
5215       /* compare the move played on the board to the next move in the\r
5216        * game. If they match, display the move and the opponent's response. \r
5217        * If they don't match, display an error message.\r
5218        */\r
5219       int saveAnimate;\r
5220       Board testBoard;\r
5221       CopyBoard(testBoard, boards[currentMove]);\r
5222       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);\r
5223 \r
5224       if (CompareBoards(testBoard, boards[currentMove+1])) {\r
5225         ForwardInner(currentMove+1);\r
5226 \r
5227         /* Autoplay the opponent's response.\r
5228          * if appData.animate was TRUE when Training mode was entered,\r
5229          * the response will be animated.\r
5230          */\r
5231         saveAnimate = appData.animate;\r
5232         appData.animate = animateTraining;\r
5233         ForwardInner(currentMove+1);\r
5234         appData.animate = saveAnimate;\r
5235 \r
5236         /* check for the end of the game */\r
5237         if (currentMove >= forwardMostMove) {\r
5238           gameMode = PlayFromGameFile;\r
5239           ModeHighlight();\r
5240           SetTrainingModeOff();\r
5241           DisplayInformation(_("End of game"));\r
5242         }\r
5243       } else {\r
5244         DisplayError(_("Incorrect move"), 0);\r
5245       }\r
5246       return 1;\r
5247     }\r
5248 \r
5249   /* Ok, now we know that the move is good, so we can kill\r
5250      the previous line in Analysis Mode */\r
5251   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {\r
5252     forwardMostMove = currentMove;\r
5253   }\r
5254 \r
5255   /* If we need the chess program but it's dead, restart it */\r
5256   ResurrectChessProgram();\r
5257 \r
5258   /* A user move restarts a paused game*/\r
5259   if (pausing)\r
5260     PauseEvent();\r
5261 \r
5262   thinkOutput[0] = NULLCHAR;\r
5263 \r
5264   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/\r
5265 \r
5266     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) \r
5267                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { \r
5268         // [HGM] superchess: take promotion piece out of holdings\r
5269         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));\r
5270         if(WhiteOnMove(forwardMostMove-1)) {\r
5271             if(!--boards[forwardMostMove][k][BOARD_WIDTH-2])\r
5272                 boards[forwardMostMove][k][BOARD_WIDTH-1] = EmptySquare;\r
5273         } else {\r
5274             if(!--boards[forwardMostMove][BOARD_HEIGHT-1-k][1])\r
5275                 boards[forwardMostMove][BOARD_HEIGHT-1-k][0] = EmptySquare;\r
5276         }\r
5277     }\r
5278 \r
5279   if (gameMode == BeginningOfGame) {\r
5280     if (appData.noChessProgram) {\r
5281       gameMode = EditGame;\r
5282       SetGameInfo();\r
5283     } else {\r
5284       char buf[MSG_SIZ];\r
5285       gameMode = MachinePlaysBlack;\r
5286       StartClocks();\r
5287       SetGameInfo();\r
5288       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
5289       DisplayTitle(buf);\r
5290       if (first.sendName) {\r
5291         sprintf(buf, "name %s\n", gameInfo.white);\r
5292         SendToProgram(buf, &first);\r
5293       }\r
5294       StartClocks();\r
5295     }\r
5296     ModeHighlight();\r
5297   }\r
5298 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);\r
5299   /* Relay move to ICS or chess engine */\r
5300   if (appData.icsActive) {\r
5301     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
5302         gameMode == IcsExamining) {\r
5303       SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
5304       ics_user_moved = 1;\r
5305     }\r
5306   } else {\r
5307     if (first.sendTime && (gameMode == BeginningOfGame ||\r
5308                            gameMode == MachinePlaysWhite ||\r
5309                            gameMode == MachinePlaysBlack)) {\r
5310       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);\r
5311     }\r
5312     if (gameMode != EditGame && gameMode != PlayFromGameFile) {\r
5313          // [HGM] book: if program might be playing, let it use book\r
5314         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);\r
5315         first.maybeThinking = TRUE;\r
5316     } else SendMoveToProgram(forwardMostMove-1, &first);\r
5317     if (currentMove == cmailOldMove + 1) {\r
5318       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
5319     }\r
5320   }\r
5321 \r
5322   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5323 \r
5324   switch (gameMode) {\r
5325   case EditGame:\r
5326     switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
5327                      EP_UNKNOWN, castlingRights[currentMove]) ) {\r
5328     case MT_NONE:\r
5329     case MT_CHECK:\r
5330       break;\r
5331     case MT_CHECKMATE:\r
5332     case MT_STAINMATE:\r
5333       if (WhiteOnMove(currentMove)) {\r
5334         GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
5335       } else {\r
5336         GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
5337       }\r
5338       break;\r
5339     case MT_STALEMATE:\r
5340       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
5341       break;\r
5342     }\r
5343     break;\r
5344     \r
5345   case MachinePlaysBlack:\r
5346   case MachinePlaysWhite:\r
5347     /* disable certain menu options while machine is thinking */\r
5348     SetMachineThinkingEnables();\r
5349     break;\r
5350 \r
5351   default:\r
5352     break;\r
5353   }\r
5354 \r
5355   if(bookHit) { // [HGM] book: simulate book reply\r
5356         static char bookMove[MSG_SIZ]; // a bit generous?\r
5357 \r
5358         programStats.nodes = programStats.depth = programStats.time = \r
5359         programStats.score = programStats.got_only_move = 0;\r
5360         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
5361 \r
5362         strcpy(bookMove, "move ");\r
5363         strcat(bookMove, bookHit);\r
5364         HandleMachineMove(bookMove, &first);\r
5365   }\r
5366   return 1;\r
5367 }\r
5368 \r
5369 void\r
5370 UserMoveEvent(fromX, fromY, toX, toY, promoChar)\r
5371      int fromX, fromY, toX, toY;\r
5372      int promoChar;\r
5373 {\r
5374     /* [HGM] This routine was added to allow calling of its two logical\r
5375        parts from other modules in the old way. Before, UserMoveEvent()\r
5376        automatically called FinishMove() if the move was OK, and returned\r
5377        otherwise. I separated the two, in order to make it possible to\r
5378        slip a promotion popup in between. But that it always needs two\r
5379        calls, to the first part, (now called UserMoveTest() ), and to\r
5380        FinishMove if the first part succeeded. Calls that do not need\r
5381        to do anything in between, can call this routine the old way. \r
5382     */\r
5383     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);\r
5384 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);\r
5385     if(moveType != ImpossibleMove)\r
5386         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);\r
5387 }\r
5388 \r
5389 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )\r
5390 {\r
5391 //    char * hint = lastHint;\r
5392     FrontEndProgramStats stats;\r
5393 \r
5394     stats.which = cps == &first ? 0 : 1;\r
5395     stats.depth = cpstats->depth;\r
5396     stats.nodes = cpstats->nodes;\r
5397     stats.score = cpstats->score;\r
5398     stats.time = cpstats->time;\r
5399     stats.pv = cpstats->movelist;\r
5400     stats.hint = lastHint;\r
5401     stats.an_move_index = 0;\r
5402     stats.an_move_count = 0;\r
5403 \r
5404     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {\r
5405         stats.hint = cpstats->move_name;\r
5406         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;\r
5407         stats.an_move_count = cpstats->nr_moves;\r
5408     }\r
5409 \r
5410     SetProgramStats( &stats );\r
5411 }\r
5412 \r
5413 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)\r
5414 {   // [HGM] book: this routine intercepts moves to simulate book replies\r
5415     char *bookHit = NULL;\r
5416 \r
5417     //first determine if the incoming move brings opponent into his book\r
5418     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))\r
5419         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move\r
5420     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");\r
5421     if(bookHit != NULL && !cps->bookSuspend) {\r
5422         // make sure opponent is not going to reply after receiving move to book position\r
5423         SendToProgram("force\n", cps);\r
5424         cps->bookSuspend = TRUE; // flag indicating it has to be restarted\r
5425     }\r
5426     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move\r
5427     // now arrange restart after book miss\r
5428     if(bookHit) {\r
5429         // after a book hit we never send 'go', and the code after the call to this routine\r
5430         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').\r
5431         char buf[MSG_SIZ];\r
5432         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(\r
5433         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it\r
5434         SendToProgram(buf, cps);\r
5435         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'\r
5436     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine\r
5437         SendToProgram("go\n", cps);\r
5438         cps->bookSuspend = FALSE; // after a 'go' we are never suspended\r
5439     } else { // 'go' might be sent based on 'firstMove' after this routine returns\r
5440         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return\r
5441             SendToProgram("go\n", cps); \r
5442         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss\r
5443     }\r
5444     return bookHit; // notify caller of hit, so it can take action to send move to opponent\r
5445 }\r
5446 \r
5447 char *savedMessage;\r
5448 ChessProgramState *savedState;\r
5449 void DeferredBookMove(void)\r
5450 {\r
5451         if(savedState->lastPing != savedState->lastPong)\r
5452                     ScheduleDelayedEvent(DeferredBookMove, 10);\r
5453         else\r
5454         HandleMachineMove(savedMessage, savedState);\r
5455 }\r
5456 \r
5457 void\r
5458 HandleMachineMove(message, cps)\r
5459      char *message;\r
5460      ChessProgramState *cps;\r
5461 {\r
5462     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];\r
5463     char realname[MSG_SIZ];\r
5464     int fromX, fromY, toX, toY;\r
5465     ChessMove moveType;\r
5466     char promoChar;\r
5467     char *p;\r
5468     int machineWhite;\r
5469     char *bookHit;\r
5470 \r
5471 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit\r
5472     /*\r
5473      * Kludge to ignore BEL characters\r
5474      */\r
5475     while (*message == '\007') message++;\r
5476 \r
5477     /*\r
5478      * [HGM] engine debug message: ignore lines starting with '#' character\r
5479      */\r
5480     if(cps->debug && *message == '#') return;\r
5481 \r
5482     /*\r
5483      * Look for book output\r
5484      */\r
5485     if (cps == &first && bookRequested) {\r
5486         if (message[0] == '\t' || message[0] == ' ') {\r
5487             /* Part of the book output is here; append it */\r
5488             strcat(bookOutput, message);\r
5489             strcat(bookOutput, "  \n");\r
5490             return;\r
5491         } else if (bookOutput[0] != NULLCHAR) {\r
5492             /* All of book output has arrived; display it */\r
5493             char *p = bookOutput;\r
5494             while (*p != NULLCHAR) {\r
5495                 if (*p == '\t') *p = ' ';\r
5496                 p++;\r
5497             }\r
5498             DisplayInformation(bookOutput);\r
5499             bookRequested = FALSE;\r
5500             /* Fall through to parse the current output */\r
5501         }\r
5502     }\r
5503 \r
5504     /*\r
5505      * Look for machine move.\r
5506      */\r
5507     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||\r
5508         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) \r
5509     {\r
5510         /* This method is only useful on engines that support ping */\r
5511         if (cps->lastPing != cps->lastPong) {\r
5512           if (gameMode == BeginningOfGame) {\r
5513             /* Extra move from before last new; ignore */\r
5514             if (appData.debugMode) {\r
5515                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
5516             }\r
5517           } else {\r
5518             if (appData.debugMode) {\r
5519                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
5520                         cps->which, gameMode);\r
5521             }\r
5522 \r
5523             SendToProgram("undo\n", cps);\r
5524           }\r
5525           return;\r
5526         }\r
5527 \r
5528         switch (gameMode) {\r
5529           case BeginningOfGame:\r
5530             /* Extra move from before last reset; ignore */\r
5531             if (appData.debugMode) {\r
5532                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
5533             }\r
5534             return;\r
5535 \r
5536           case EndOfGame:\r
5537           case IcsIdle:\r
5538           default:\r
5539             /* Extra move after we tried to stop.  The mode test is\r
5540                not a reliable way of detecting this problem, but it's\r
5541                the best we can do on engines that don't support ping.\r
5542             */\r
5543             if (appData.debugMode) {\r
5544                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
5545                         cps->which, gameMode);\r
5546             }\r
5547             SendToProgram("undo\n", cps);\r
5548             return;\r
5549 \r
5550           case MachinePlaysWhite:\r
5551           case IcsPlayingWhite:\r
5552             machineWhite = TRUE;\r
5553             break;\r
5554 \r
5555           case MachinePlaysBlack:\r
5556           case IcsPlayingBlack:\r
5557             machineWhite = FALSE;\r
5558             break;\r
5559 \r
5560           case TwoMachinesPlay:\r
5561             machineWhite = (cps->twoMachinesColor[0] == 'w');\r
5562             break;\r
5563         }\r
5564         if (WhiteOnMove(forwardMostMove) != machineWhite) {\r
5565             if (appData.debugMode) {\r
5566                 fprintf(debugFP,\r
5567                         "Ignoring move out of turn by %s, gameMode %d"\r
5568                         ", forwardMost %d\n",\r
5569                         cps->which, gameMode, forwardMostMove);\r
5570             }\r
5571             return;\r
5572         }\r
5573 \r
5574     if (appData.debugMode) { int f = forwardMostMove;\r
5575         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,\r
5576                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
5577     }\r
5578         if(cps->alphaRank) AlphaRank(machineMove, 4);\r
5579         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,\r
5580                               &fromX, &fromY, &toX, &toY, &promoChar)) {\r
5581             /* Machine move could not be parsed; ignore it. */\r
5582             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),\r
5583                     machineMove, cps->which);\r
5584             DisplayError(buf1, 0);\r
5585             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",\r
5586                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);\r
5587             if (gameMode == TwoMachinesPlay) {\r
5588               GameEnds(machineWhite ? BlackWins : WhiteWins,\r
5589                        buf1, GE_XBOARD);\r
5590             }\r
5591             return;\r
5592         }\r
5593 \r
5594         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */\r
5595         /* So we have to redo legality test with true e.p. status here,  */\r
5596         /* to make sure an illegal e.p. capture does not slip through,   */\r
5597         /* to cause a forfeit on a justified illegal-move complaint      */\r
5598         /* of the opponent.                                              */\r
5599         if( gameMode==TwoMachinesPlay && appData.testLegality\r
5600             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */\r
5601                                                               ) {\r
5602            ChessMove moveType;\r
5603            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
5604                         epStatus[forwardMostMove], castlingRights[forwardMostMove],\r
5605                              fromY, fromX, toY, toX, promoChar);\r
5606             if (appData.debugMode) {\r
5607                 int i;\r
5608                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",\r
5609                     castlingRights[forwardMostMove][i], castlingRank[i]);\r
5610                 fprintf(debugFP, "castling rights\n");\r
5611             }\r
5612             if(moveType == IllegalMove) {\r
5613                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",\r
5614                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);\r
5615                 GameEnds(machineWhite ? BlackWins : WhiteWins,\r
5616                            buf1, GE_XBOARD);\r
5617                 return;\r
5618            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)\r
5619            /* [HGM] Kludge to handle engines that send FRC-style castling\r
5620               when they shouldn't (like TSCP-Gothic) */\r
5621            switch(moveType) {\r
5622              case WhiteASideCastleFR:\r
5623              case BlackASideCastleFR:\r
5624                toX+=2;\r
5625                currentMoveString[2]++;\r
5626                break;\r
5627              case WhiteHSideCastleFR:\r
5628              case BlackHSideCastleFR:\r
5629                toX--;\r
5630                currentMoveString[2]--;\r
5631                break;\r
5632              default: ; // nothing to do, but suppresses warning of pedantic compilers\r
5633            }\r
5634         }\r
5635         hintRequested = FALSE;\r
5636         lastHint[0] = NULLCHAR;\r
5637         bookRequested = FALSE;\r
5638         /* Program may be pondering now */\r
5639         cps->maybeThinking = TRUE;\r
5640         if (cps->sendTime == 2) cps->sendTime = 1;\r
5641         if (cps->offeredDraw) cps->offeredDraw--;\r
5642 \r
5643 #if ZIPPY\r
5644         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&\r
5645             first.initDone) {\r
5646           SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
5647           ics_user_moved = 1;\r
5648           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */\r
5649                 char buf[3*MSG_SIZ];\r
5650 \r
5651                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %.0f nodes, %1.0f knps) PV=%s\n",\r
5652                         programStats.score / 100.,\r
5653                         programStats.depth,\r
5654                         programStats.time / 100.,\r
5655                         u64ToDouble(programStats.nodes),\r
5656                         u64ToDouble(programStats.nodes) / (10*abs(programStats.time) + 1.),\r
5657                         programStats.movelist);\r
5658                 SendToICS(buf);\r
5659           }\r
5660         }\r
5661 #endif\r
5662         /* currentMoveString is set as a side-effect of ParseOneMove */\r
5663         strcpy(machineMove, currentMoveString);\r
5664         strcat(machineMove, "\n");\r
5665         strcpy(moveList[forwardMostMove], machineMove);\r
5666 \r
5667         /* [AS] Save move info and clear stats for next move */\r
5668         pvInfoList[ forwardMostMove ].score = programStats.score;\r
5669         pvInfoList[ forwardMostMove ].depth = programStats.depth;\r
5670         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats\r
5671         ClearProgramStats();\r
5672         thinkOutput[0] = NULLCHAR;\r
5673         hiddenThinkOutputState = 0;\r
5674 \r
5675         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/\r
5676 \r
5677         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */\r
5678         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {\r
5679             int count = 0;\r
5680 \r
5681             while( count < adjudicateLossPlies ) {\r
5682                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;\r
5683 \r
5684                 if( count & 1 ) {\r
5685                     score = -score; /* Flip score for winning side */\r
5686                 }\r
5687 \r
5688                 if( score > adjudicateLossThreshold ) {\r
5689                     break;\r
5690                 }\r
5691 \r
5692                 count++;\r
5693             }\r
5694 \r
5695             if( count >= adjudicateLossPlies ) {\r
5696                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5697 \r
5698                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5699                     "Xboard adjudication", \r
5700                     GE_XBOARD );\r
5701 \r
5702                 return;\r
5703             }\r
5704         }\r
5705 \r
5706         if( gameMode == TwoMachinesPlay ) {\r
5707           // [HGM] some adjudications useful with buggy engines\r
5708             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;\r
5709           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {\r
5710 \r
5711 \r
5712             if( appData.testLegality )\r
5713             {   /* [HGM] Some more adjudications for obstinate engines */\r
5714                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,\r
5715                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,\r
5716                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;\r
5717                 static int moveCount = 6;\r
5718                 ChessMove result;\r
5719                 char *reason = NULL;\r
5720 \r
5721                 /* Count what is on board. */\r
5722                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
5723                 {   ChessSquare p = boards[forwardMostMove][i][j];\r
5724                     int m=i;\r
5725 \r
5726                     switch((int) p)\r
5727                     {   /* count B,N,R and other of each side */\r
5728                         case WhiteKing:\r
5729                         case BlackKing:\r
5730                              NrK++; break; // [HGM] atomic: count Kings\r
5731                         case WhiteKnight:\r
5732                              NrWN++; break;\r
5733                         case WhiteBishop:\r
5734                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj\r
5735                              bishopsColor |= 1 << ((i^j)&1);\r
5736                              NrWB++; break;\r
5737                         case BlackKnight:\r
5738                              NrBN++; break;\r
5739                         case BlackBishop:\r
5740                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj\r
5741                              bishopsColor |= 1 << ((i^j)&1);\r
5742                              NrBB++; break;\r
5743                         case WhiteRook:\r
5744                              NrWR++; break;\r
5745                         case BlackRook:\r
5746                              NrBR++; break;\r
5747                         case WhiteQueen:\r
5748                              NrWQ++; break;\r
5749                         case BlackQueen:\r
5750                              NrBQ++; break;\r
5751                         case EmptySquare: \r
5752                              break;\r
5753                         case BlackPawn:\r
5754                              m = 7-i;\r
5755                         case WhitePawn:\r
5756                              PawnAdvance += m; NrPawns++;\r
5757                     }\r
5758                     NrPieces += (p != EmptySquare);\r
5759                     NrW += ((int)p < (int)BlackPawn);\r
5760                     if(gameInfo.variant == VariantXiangqi && \r
5761                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {\r
5762                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces\r
5763                         NrW -= ((int)p < (int)BlackPawn);\r
5764                     }\r
5765                 }\r
5766 \r
5767                 /* Some material-based adjudications that have to be made before stalemate test */\r
5768                 if(gameInfo.variant == VariantAtomic && NrK < 2) {\r
5769                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal\r
5770                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated\r
5771                      if(appData.checkMates) {\r
5772                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move\r
5773                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5774                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, \r
5775                                                         "Xboard adjudication: King destroyed", GE_XBOARD );\r
5776                          return;\r
5777                      }\r
5778                 }\r
5779 \r
5780                 /* Bare King in Shatranj (loses) or Losers (wins) */\r
5781                 if( NrW == 1 || NrPieces - NrW == 1) {\r
5782                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)\r
5783                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable\r
5784                      if(appData.checkMates) {\r
5785                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move\r
5786                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5787                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5788                                                         "Xboard adjudication: Bare king", GE_XBOARD );\r
5789                          return;\r
5790                      }\r
5791                   } else\r
5792                   if( gameInfo.variant == VariantShatranj && --bare < 0)\r
5793                   {    /* bare King */\r
5794                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm\r
5795                         if(appData.checkMates) {\r
5796                             /* but only adjudicate if adjudication enabled */\r
5797                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move\r
5798                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5799                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, \r
5800                                                         "Xboard adjudication: Bare king", GE_XBOARD );\r
5801                             return;\r
5802                         }\r
5803                   }\r
5804                 } else bare = 1;\r
5805 \r
5806 \r
5807             // don't wait for engine to announce game end if we can judge ourselves\r
5808             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,\r
5809                                        castlingRights[forwardMostMove]) ) {\r
5810               case MT_NONE:\r
5811               case MT_CHECK:\r
5812               default:\r
5813                 break;\r
5814               case MT_STALEMATE:\r
5815               case MT_STAINMATE:\r
5816                 reason = "Xboard adjudication: Stalemate";\r
5817                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt\r
5818                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw\r
5819                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:\r
5820                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win\r
5821                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends\r
5822                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :\r
5823                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?\r
5824                                                                         EP_CHECKMATE : EP_WINS);\r
5825                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)\r
5826                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses\r
5827                 }\r
5828                 break;\r
5829               case MT_CHECKMATE:\r
5830                 reason = "Xboard adjudication: Checkmate";\r
5831                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);\r
5832                 break;\r
5833             }\r
5834 \r
5835                 switch(i = epStatus[forwardMostMove]) {\r
5836                     case EP_STALEMATE:\r
5837                         result = GameIsDrawn; break;\r
5838                     case EP_CHECKMATE:\r
5839                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;\r
5840                     case EP_WINS:\r
5841                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;\r
5842                     default:\r
5843                         result = (ChessMove) 0;\r
5844                 }\r
5845                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested\r
5846                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5847                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5848                     GameEnds( result, reason, GE_XBOARD );\r
5849                     return;\r
5850                 }\r
5851 \r
5852                 /* Next absolutely insufficient mating material. */\r
5853                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && \r
5854                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible\r
5855                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||\r
5856                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color\r
5857                 {    /* KBK, KNK, KK of KBKB with like Bishops */\r
5858 \r
5859                      /* always flag draws, for judging claims */\r
5860                      epStatus[forwardMostMove] = EP_INSUF_DRAW;\r
5861 \r
5862                      if(appData.materialDraws) {\r
5863                          /* but only adjudicate them if adjudication enabled */\r
5864                          SendToProgram("force\n", cps->other); // suppress reply\r
5865                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */\r
5866                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5867                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );\r
5868                          return;\r
5869                      }\r
5870                 }\r
5871 \r
5872                 /* Then some trivial draws (only adjudicate, cannot be claimed) */\r
5873                 if(NrPieces == 4 && \r
5874                    (   NrWR == 1 && NrBR == 1 /* KRKR */\r
5875                    || NrWQ==1 && NrBQ==1     /* KQKQ */\r
5876                    || NrWN==2 || NrBN==2     /* KNNK */\r
5877                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */\r
5878                   ) ) {\r
5879                      if(--moveCount < 0 && appData.trivialDraws)\r
5880                      {    /* if the first 3 moves do not show a tactical win, declare draw */\r
5881                           SendToProgram("force\n", cps->other); // suppress reply\r
5882                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5883                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5884                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );\r
5885                           return;\r
5886                      }\r
5887                 } else moveCount = 6;\r
5888             }\r
5889           }\r
5890 #if 1\r
5891     if (appData.debugMode) { int i;\r
5892       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",\r
5893               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],\r
5894               appData.drawRepeats);\r
5895       for( i=forwardMostMove; i>=backwardMostMove; i-- )\r
5896            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);\r
5897 \r
5898     }\r
5899 #endif\r
5900                 /* Check for rep-draws */\r
5901                 count = 0;\r
5902                 for(k = forwardMostMove-2;\r
5903                     k>=backwardMostMove && k>=forwardMostMove-100 &&\r
5904                         epStatus[k] < EP_UNKNOWN &&\r
5905                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;\r
5906                     k-=2)\r
5907                 {   int rights=0;\r
5908 #if 0\r
5909     if (appData.debugMode) {\r
5910       fprintf(debugFP, " loop\n");\r
5911     }\r
5912 #endif\r
5913                     if(CompareBoards(boards[k], boards[forwardMostMove])) {\r
5914 #if 0\r
5915     if (appData.debugMode) {\r
5916       fprintf(debugFP, "match\n");\r
5917     }\r
5918 #endif\r
5919                         /* compare castling rights */\r
5920                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&\r
5921                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )\r
5922                                 rights++; /* King lost rights, while rook still had them */\r
5923                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */\r
5924                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||\r
5925                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )\r
5926                                    rights++; /* but at least one rook lost them */\r
5927                         }\r
5928                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&\r
5929                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )\r
5930                                 rights++; \r
5931                         if( castlingRights[forwardMostMove][5] >= 0 ) {\r
5932                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||\r
5933                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )\r
5934                                    rights++;\r
5935                         }\r
5936 #if 0\r
5937     if (appData.debugMode) {\r
5938       for(i=0; i<nrCastlingRights; i++)\r
5939       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);\r
5940     }\r
5941 \r
5942     if (appData.debugMode) {\r
5943       fprintf(debugFP, " %d %d\n", rights, k);\r
5944     }\r
5945 #endif\r
5946                         if( rights == 0 && ++count > appData.drawRepeats-2\r
5947                             && appData.drawRepeats > 1) {\r
5948                              /* adjudicate after user-specified nr of repeats */\r
5949                              SendToProgram("force\n", cps->other); // suppress reply\r
5950                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5951                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5952                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { \r
5953                                 // [HGM] xiangqi: check for forbidden perpetuals\r
5954                                 int m, ourPerpetual = 1, hisPerpetual = 1;\r
5955                                 for(m=forwardMostMove; m>k; m-=2) {\r
5956                                     if(MateTest(boards[m], PosFlags(m), \r
5957                                                         EP_NONE, castlingRights[m]) != MT_CHECK)\r
5958                                         ourPerpetual = 0; // the current mover did not always check\r
5959                                     if(MateTest(boards[m-1], PosFlags(m-1), \r
5960                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)\r
5961                                         hisPerpetual = 0; // the opponent did not always check\r
5962                                 }\r
5963                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",\r
5964                                                                         ourPerpetual, hisPerpetual);\r
5965                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit\r
5966                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5967                                            "Xboard adjudication: perpetual checking", GE_XBOARD );\r
5968                                     return;\r
5969                                 }\r
5970                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet\r
5971                                     break; // (or we would have caught him before). Abort repetition-checking loop.\r
5972                                 // Now check for perpetual chases\r
5973                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase\r
5974                                     hisPerpetual = PerpetualChase(k, forwardMostMove);\r
5975                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);\r
5976                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit\r
5977                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5978                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );\r
5979                                         return;\r
5980                                     }\r
5981                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet\r
5982                                         break; // Abort repetition-checking loop.\r
5983                                 }\r
5984                                 // if neither of us is checking or chasing all the time, or both are, it is draw\r
5985                              }\r
5986                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );\r
5987                              return;\r
5988                         }\r
5989                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */\r
5990                              epStatus[forwardMostMove] = EP_REP_DRAW;\r
5991                     }\r
5992                 }\r
5993 \r
5994                 /* Now we test for 50-move draws. Determine ply count */\r
5995                 count = forwardMostMove;\r
5996                 /* look for last irreversble move */\r
5997                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )\r
5998                     count--;\r
5999                 /* if we hit starting position, add initial plies */\r
6000                 if( count == backwardMostMove )\r
6001                     count -= initialRulePlies;\r
6002                 count = forwardMostMove - count; \r
6003                 if( count >= 100)\r
6004                          epStatus[forwardMostMove] = EP_RULE_DRAW;\r
6005                          /* this is used to judge if draw claims are legal */\r
6006                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {\r
6007                          SendToProgram("force\n", cps->other); // suppress reply\r
6008                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6009                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6010                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );\r
6011                          return;\r
6012                 }\r
6013 \r
6014                 /* if draw offer is pending, treat it as a draw claim\r
6015                  * when draw condition present, to allow engines a way to\r
6016                  * claim draws before making their move to avoid a race\r
6017                  * condition occurring after their move\r
6018                  */\r
6019                 if( cps->other->offeredDraw || cps->offeredDraw ) {\r
6020                          char *p = NULL;\r
6021                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)\r
6022                              p = "Draw claim: 50-move rule";\r
6023                          if(epStatus[forwardMostMove] == EP_REP_DRAW)\r
6024                              p = "Draw claim: 3-fold repetition";\r
6025                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)\r
6026                              p = "Draw claim: insufficient mating material";\r
6027                          if( p != NULL ) {\r
6028                              SendToProgram("force\n", cps->other); // suppress reply\r
6029                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6030                              GameEnds( GameIsDrawn, p, GE_XBOARD );\r
6031                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6032                              return;\r
6033                          }\r
6034                 }\r
6035 \r
6036 \r
6037                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {\r
6038                     SendToProgram("force\n", cps->other); // suppress reply\r
6039                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6040                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6041 \r
6042                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );\r
6043 \r
6044                     return;\r
6045                 }\r
6046         }\r
6047 \r
6048         bookHit = NULL;\r
6049         if (gameMode == TwoMachinesPlay) {\r
6050             /* [HGM] relaying draw offers moved to after reception of move */\r
6051             /* and interpreting offer as claim if it brings draw condition */\r
6052             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {\r
6053                 SendToProgram("draw\n", cps->other);\r
6054             }\r
6055             if (cps->other->sendTime) {\r
6056                 SendTimeRemaining(cps->other,\r
6057                                   cps->other->twoMachinesColor[0] == 'w');\r
6058             }\r
6059             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);\r
6060             if (firstMove && !bookHit) {\r
6061                 firstMove = FALSE;\r
6062                 if (cps->other->useColors) {\r
6063                   SendToProgram(cps->other->twoMachinesColor, cps->other);\r
6064                 }\r
6065                 SendToProgram("go\n", cps->other);\r
6066             }\r
6067             cps->other->maybeThinking = TRUE;\r
6068         }\r
6069 \r
6070         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6071         \r
6072         if (!pausing && appData.ringBellAfterMoves) {\r
6073             RingBell();\r
6074         }\r
6075 \r
6076         /* \r
6077          * Reenable menu items that were disabled while\r
6078          * machine was thinking\r
6079          */\r
6080         if (gameMode != TwoMachinesPlay)\r
6081             SetUserThinkingEnables();\r
6082 \r
6083         // [HGM] book: after book hit opponent has received move and is now in force mode\r
6084         // force the book reply into it, and then fake that it outputted this move by jumping\r
6085         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move\r
6086         if(bookHit) {\r
6087                 static char bookMove[MSG_SIZ]; // a bit generous?\r
6088 \r
6089                 strcpy(bookMove, "move ");\r
6090                 strcat(bookMove, bookHit);\r
6091                 message = bookMove;\r
6092                 cps = cps->other;\r
6093                 programStats.nodes = programStats.depth = programStats.time = \r
6094                 programStats.score = programStats.got_only_move = 0;\r
6095                 sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
6096 \r
6097                 if(cps->lastPing != cps->lastPong) {\r
6098                     savedMessage = message; // args for deferred call\r
6099                     savedState = cps;\r
6100                     ScheduleDelayedEvent(DeferredBookMove, 10);\r
6101                     return;\r
6102                 }\r
6103                 goto FakeBookMove;\r
6104         }\r
6105 \r
6106         return;\r
6107     }\r
6108 \r
6109     /* Set special modes for chess engines.  Later something general\r
6110      *  could be added here; for now there is just one kludge feature,\r
6111      *  needed because Crafty 15.10 and earlier don't ignore SIGINT\r
6112      *  when "xboard" is given as an interactive command.\r
6113      */\r
6114     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {\r
6115         cps->useSigint = FALSE;\r
6116         cps->useSigterm = FALSE;\r
6117     }\r
6118 \r
6119     /* [HGM] Allow engine to set up a position. Don't ask me why one would\r
6120      * want this, I was asked to put it in, and obliged.\r
6121      */\r
6122     if (!strncmp(message, "setboard ", 9)) {\r
6123         Board initial_position; int i;\r
6124 \r
6125         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);\r
6126 \r
6127         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {\r
6128             DisplayError(_("Bad FEN received from engine"), 0);\r
6129             return ;\r
6130         } else {\r
6131            Reset(FALSE, FALSE);\r
6132            CopyBoard(boards[0], initial_position);\r
6133            initialRulePlies = FENrulePlies;\r
6134            epStatus[0] = FENepStatus;\r
6135            for( i=0; i<nrCastlingRights; i++ )\r
6136                 castlingRights[0][i] = FENcastlingRights[i];\r
6137            if(blackPlaysFirst) gameMode = MachinePlaysWhite;\r
6138            else gameMode = MachinePlaysBlack;                 \r
6139            DrawPosition(FALSE, boards[currentMove]);\r
6140         }\r
6141         return;\r
6142     }\r
6143 \r
6144     /*\r
6145      * Look for communication commands\r
6146      */\r
6147     if (!strncmp(message, "telluser ", 9)) {\r
6148         DisplayNote(message + 9);\r
6149         return;\r
6150     }\r
6151     if (!strncmp(message, "tellusererror ", 14)) {\r
6152         DisplayError(message + 14, 0);\r
6153         return;\r
6154     }\r
6155     if (!strncmp(message, "tellopponent ", 13)) {\r
6156       if (appData.icsActive) {\r
6157         if (loggedOn) {\r
6158           sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);\r
6159           SendToICS(buf1);\r
6160         }\r
6161       } else {\r
6162         DisplayNote(message + 13);\r
6163       }\r
6164       return;\r
6165     }\r
6166     if (!strncmp(message, "tellothers ", 11)) {\r
6167       if (appData.icsActive) {\r
6168         if (loggedOn) {\r
6169           sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);\r
6170           SendToICS(buf1);\r
6171         }\r
6172       }\r
6173       return;\r
6174     }\r
6175     if (!strncmp(message, "tellall ", 8)) {\r
6176       if (appData.icsActive) {\r
6177         if (loggedOn) {\r
6178           sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);\r
6179           SendToICS(buf1);\r
6180         }\r
6181       } else {\r
6182         DisplayNote(message + 8);\r
6183       }\r
6184       return;\r
6185     }\r
6186     if (strncmp(message, "warning", 7) == 0) {\r
6187         /* Undocumented feature, use tellusererror in new code */\r
6188         DisplayError(message, 0);\r
6189         return;\r
6190     }\r
6191     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {\r
6192         strcpy(realname, cps->tidy);\r
6193         strcat(realname, " query");\r
6194         AskQuestion(realname, buf2, buf1, cps->pr);\r
6195         return;\r
6196     }\r
6197     /* Commands from the engine directly to ICS.  We don't allow these to be \r
6198      *  sent until we are logged on. Crafty kibitzes have been known to \r
6199      *  interfere with the login process.\r
6200      */\r
6201     if (loggedOn) {\r
6202         if (!strncmp(message, "tellics ", 8)) {\r
6203             SendToICS(message + 8);\r
6204             SendToICS("\n");\r
6205             return;\r
6206         }\r
6207         if (!strncmp(message, "tellicsnoalias ", 15)) {\r
6208             SendToICS(ics_prefix);\r
6209             SendToICS(message + 15);\r
6210             SendToICS("\n");\r
6211             return;\r
6212         }\r
6213         /* The following are for backward compatibility only */\r
6214         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||\r
6215             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {\r
6216             SendToICS(ics_prefix);\r
6217             SendToICS(message);\r
6218             SendToICS("\n");\r
6219             return;\r
6220         }\r
6221     }\r
6222     if (strncmp(message, "feature ", 8) == 0) {\r
6223       ParseFeatures(message+8, cps);\r
6224     }\r
6225     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {\r
6226         return;\r
6227     }\r
6228     /*\r
6229      * If the move is illegal, cancel it and redraw the board.\r
6230      * Also deal with other error cases.  Matching is rather loose\r
6231      * here to accommodate engines written before the spec.\r
6232      */\r
6233     if (strncmp(message + 1, "llegal move", 11) == 0 ||\r
6234         strncmp(message, "Error", 5) == 0) {\r
6235         if (StrStr(message, "name") || \r
6236             StrStr(message, "rating") || StrStr(message, "?") ||\r
6237             StrStr(message, "result") || StrStr(message, "board") ||\r
6238             StrStr(message, "bk") || StrStr(message, "computer") ||\r
6239             StrStr(message, "variant") || StrStr(message, "hint") ||\r
6240             StrStr(message, "random") || StrStr(message, "depth") ||\r
6241             StrStr(message, "accepted")) {\r
6242             return;\r
6243         }\r
6244         if (StrStr(message, "protover")) {\r
6245           /* Program is responding to input, so it's apparently done\r
6246              initializing, and this error message indicates it is\r
6247              protocol version 1.  So we don't need to wait any longer\r
6248              for it to initialize and send feature commands. */\r
6249           FeatureDone(cps, 1);\r
6250           cps->protocolVersion = 1;\r
6251           return;\r
6252         }\r
6253         cps->maybeThinking = FALSE;\r
6254 \r
6255         if (StrStr(message, "draw")) {\r
6256             /* Program doesn't have "draw" command */\r
6257             cps->sendDrawOffers = 0;\r
6258             return;\r
6259         }\r
6260         if (cps->sendTime != 1 &&\r
6261             (StrStr(message, "time") || StrStr(message, "otim"))) {\r
6262           /* Program apparently doesn't have "time" or "otim" command */\r
6263           cps->sendTime = 0;\r
6264           return;\r
6265         }\r
6266         if (StrStr(message, "analyze")) {\r
6267             cps->analysisSupport = FALSE;\r
6268             cps->analyzing = FALSE;\r
6269             Reset(FALSE, TRUE);\r
6270             sprintf(buf2, _("%s does not support analysis"), cps->tidy);\r
6271             DisplayError(buf2, 0);\r
6272             return;\r
6273         }\r
6274         if (StrStr(message, "(no matching move)st")) {\r
6275           /* Special kludge for GNU Chess 4 only */\r
6276           cps->stKludge = TRUE;\r
6277           SendTimeControl(cps, movesPerSession, timeControl,\r
6278                           timeIncrement, appData.searchDepth,\r
6279                           searchTime);\r
6280           return;\r
6281         }\r
6282         if (StrStr(message, "(no matching move)sd")) {\r
6283           /* Special kludge for GNU Chess 4 only */\r
6284           cps->sdKludge = TRUE;\r
6285           SendTimeControl(cps, movesPerSession, timeControl,\r
6286                           timeIncrement, appData.searchDepth,\r
6287                           searchTime);\r
6288           return;\r
6289         }\r
6290         if (!StrStr(message, "llegal")) {\r
6291             return;\r
6292         }\r
6293         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
6294             gameMode == IcsIdle) return;\r
6295         if (forwardMostMove <= backwardMostMove) return;\r
6296 #if 0\r
6297         /* Following removed: it caused a bug where a real illegal move\r
6298            message in analyze mored would be ignored. */\r
6299         if (cps == &first && programStats.ok_to_send == 0) {\r
6300             /* Bogus message from Crafty responding to "."  This filtering\r
6301                can miss some of the bad messages, but fortunately the bug \r
6302                is fixed in current Crafty versions, so it doesn't matter. */\r
6303             return;\r
6304         }\r
6305 #endif\r
6306         if (pausing) PauseEvent();\r
6307         if (gameMode == PlayFromGameFile) {\r
6308             /* Stop reading this game file */\r
6309             gameMode = EditGame;\r
6310             ModeHighlight();\r
6311         }\r
6312         currentMove = --forwardMostMove;\r
6313         DisplayMove(currentMove-1); /* before DisplayMoveError */\r
6314         SwitchClocks();\r
6315         DisplayBothClocks();\r
6316         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),\r
6317                 parseList[currentMove], cps->which);\r
6318         DisplayMoveError(buf1);\r
6319         DrawPosition(FALSE, boards[currentMove]);\r
6320 \r
6321         /* [HGM] illegal-move claim should forfeit game when Xboard */\r
6322         /* only passes fully legal moves                            */\r
6323         if( appData.testLegality && gameMode == TwoMachinesPlay ) {\r
6324             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,\r
6325                                 "False illegal-move claim", GE_XBOARD );\r
6326         }\r
6327         return;\r
6328     }\r
6329     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {\r
6330         /* Program has a broken "time" command that\r
6331            outputs a string not ending in newline.\r
6332            Don't use it. */\r
6333         cps->sendTime = 0;\r
6334     }\r
6335     \r
6336     /*\r
6337      * If chess program startup fails, exit with an error message.\r
6338      * Attempts to recover here are futile.\r
6339      */\r
6340     if ((StrStr(message, "unknown host") != NULL)\r
6341         || (StrStr(message, "No remote directory") != NULL)\r
6342         || (StrStr(message, "not found") != NULL)\r
6343         || (StrStr(message, "No such file") != NULL)\r
6344         || (StrStr(message, "can't alloc") != NULL)\r
6345         || (StrStr(message, "Permission denied") != NULL)) {\r
6346 \r
6347         cps->maybeThinking = FALSE;\r
6348         sprintf(buf1, _("Failed to start %s chess program %s on %s: %s\n"),\r
6349                 cps->which, cps->program, cps->host, message);\r
6350         RemoveInputSource(cps->isr);\r
6351         DisplayFatalError(buf1, 0, 1);\r
6352         return;\r
6353     }\r
6354     \r
6355     /* \r
6356      * Look for hint output\r
6357      */\r
6358     if (sscanf(message, "Hint: %s", buf1) == 1) {\r
6359         if (cps == &first && hintRequested) {\r
6360             hintRequested = FALSE;\r
6361             if (ParseOneMove(buf1, forwardMostMove, &moveType,\r
6362                                  &fromX, &fromY, &toX, &toY, &promoChar)) {\r
6363                 (void) CoordsToAlgebraic(boards[forwardMostMove],\r
6364                                     PosFlags(forwardMostMove), EP_UNKNOWN,\r
6365                                     fromY, fromX, toY, toX, promoChar, buf1);\r
6366                 sprintf(buf2, _("Hint: %s"), buf1);\r
6367                 DisplayInformation(buf2);\r
6368             } else {\r
6369                 /* Hint move could not be parsed!? */\r
6370                 sprintf(buf2,\r
6371                         _("Illegal hint move \"%s\"\nfrom %s chess program"),\r
6372                         buf1, cps->which);\r
6373                 DisplayError(buf2, 0);\r
6374             }\r
6375         } else {\r
6376             strcpy(lastHint, buf1);\r
6377         }\r
6378         return;\r
6379     }\r
6380 \r
6381     /*\r
6382      * Ignore other messages if game is not in progress\r
6383      */\r
6384     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
6385         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;\r
6386 \r
6387     /*\r
6388      * look for win, lose, draw, or draw offer\r
6389      */\r
6390     if (strncmp(message, "1-0", 3) == 0) {\r
6391         char *p, *q, *r = "";\r
6392         p = strchr(message, '{');\r
6393         if (p) {\r
6394             q = strchr(p, '}');\r
6395             if (q) {\r
6396                 *q = NULLCHAR;\r
6397                 r = p + 1;\r
6398             }\r
6399         }\r
6400         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */\r
6401         return;\r
6402     } else if (strncmp(message, "0-1", 3) == 0) {\r
6403         char *p, *q, *r = "";\r
6404         p = strchr(message, '{');\r
6405         if (p) {\r
6406             q = strchr(p, '}');\r
6407             if (q) {\r
6408                 *q = NULLCHAR;\r
6409                 r = p + 1;\r
6410             }\r
6411         }\r
6412         /* Kludge for Arasan 4.1 bug */\r
6413         if (strcmp(r, "Black resigns") == 0) {\r
6414             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));\r
6415             return;\r
6416         }\r
6417         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));\r
6418         return;\r
6419     } else if (strncmp(message, "1/2", 3) == 0) {\r
6420         char *p, *q, *r = "";\r
6421         p = strchr(message, '{');\r
6422         if (p) {\r
6423             q = strchr(p, '}');\r
6424             if (q) {\r
6425                 *q = NULLCHAR;\r
6426                 r = p + 1;\r
6427             }\r
6428         }\r
6429             \r
6430         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));\r
6431         return;\r
6432 \r
6433     } else if (strncmp(message, "White resign", 12) == 0) {\r
6434         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
6435         return;\r
6436     } else if (strncmp(message, "Black resign", 12) == 0) {\r
6437         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
6438         return;\r
6439     } else if (strncmp(message, "White matches", 13) == 0 ||\r
6440                strncmp(message, "Black matches", 13) == 0   ) {\r
6441         /* [HGM] ignore GNUShogi noises */\r
6442         return;\r
6443     } else if (strncmp(message, "White", 5) == 0 &&\r
6444                message[5] != '(' &&\r
6445                StrStr(message, "Black") == NULL) {\r
6446         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6447         return;\r
6448     } else if (strncmp(message, "Black", 5) == 0 &&\r
6449                message[5] != '(') {\r
6450         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6451         return;\r
6452     } else if (strcmp(message, "resign") == 0 ||\r
6453                strcmp(message, "computer resigns") == 0) {\r
6454         switch (gameMode) {\r
6455           case MachinePlaysBlack:\r
6456           case IcsPlayingBlack:\r
6457             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);\r
6458             break;\r
6459           case MachinePlaysWhite:\r
6460           case IcsPlayingWhite:\r
6461             GameEnds(BlackWins, "White resigns", GE_ENGINE);\r
6462             break;\r
6463           case TwoMachinesPlay:\r
6464             if (cps->twoMachinesColor[0] == 'w')\r
6465               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
6466             else\r
6467               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
6468             break;\r
6469           default:\r
6470             /* can't happen */\r
6471             break;\r
6472         }\r
6473         return;\r
6474     } else if (strncmp(message, "opponent mates", 14) == 0) {\r
6475         switch (gameMode) {\r
6476           case MachinePlaysBlack:\r
6477           case IcsPlayingBlack:\r
6478             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
6479             break;\r
6480           case MachinePlaysWhite:\r
6481           case IcsPlayingWhite:\r
6482             GameEnds(BlackWins, "Black mates", GE_ENGINE);\r
6483             break;\r
6484           case TwoMachinesPlay:\r
6485             if (cps->twoMachinesColor[0] == 'w')\r
6486               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6487             else\r
6488               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6489             break;\r
6490           default:\r
6491             /* can't happen */\r
6492             break;\r
6493         }\r
6494         return;\r
6495     } else if (strncmp(message, "computer mates", 14) == 0) {\r
6496         switch (gameMode) {\r
6497           case MachinePlaysBlack:\r
6498           case IcsPlayingBlack:\r
6499             GameEnds(BlackWins, "Black mates", GE_ENGINE1);\r
6500             break;\r
6501           case MachinePlaysWhite:\r
6502           case IcsPlayingWhite:\r
6503             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
6504             break;\r
6505           case TwoMachinesPlay:\r
6506             if (cps->twoMachinesColor[0] == 'w')\r
6507               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6508             else\r
6509               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6510             break;\r
6511           default:\r
6512             /* can't happen */\r
6513             break;\r
6514         }\r
6515         return;\r
6516     } else if (strncmp(message, "checkmate", 9) == 0) {\r
6517         if (WhiteOnMove(forwardMostMove)) {\r
6518             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6519         } else {\r
6520             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6521         }\r
6522         return;\r
6523     } else if (strstr(message, "Draw") != NULL ||\r
6524                strstr(message, "game is a draw") != NULL) {\r
6525         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));\r
6526         return;\r
6527     } else if (strstr(message, "offer") != NULL &&\r
6528                strstr(message, "draw") != NULL) {\r
6529 #if ZIPPY\r
6530         if (appData.zippyPlay && first.initDone) {\r
6531             /* Relay offer to ICS */\r
6532             SendToICS(ics_prefix);\r
6533             SendToICS("draw\n");\r
6534         }\r
6535 #endif\r
6536         cps->offeredDraw = 2; /* valid until this engine moves twice */\r
6537         if (gameMode == TwoMachinesPlay) {\r
6538             if (cps->other->offeredDraw) {\r
6539                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
6540             /* [HGM] in two-machine mode we delay relaying draw offer      */\r
6541             /* until after we also have move, to see if it is really claim */\r
6542             }\r
6543 #if 0\r
6544               else {\r
6545                 if (cps->other->sendDrawOffers) {\r
6546                     SendToProgram("draw\n", cps->other);\r
6547                 }\r
6548             }\r
6549 #endif\r
6550         } else if (gameMode == MachinePlaysWhite ||\r
6551                    gameMode == MachinePlaysBlack) {\r
6552           if (userOfferedDraw) {\r
6553             DisplayInformation(_("Machine accepts your draw offer"));\r
6554             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
6555           } else {\r
6556             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));\r
6557           }\r
6558         }\r
6559     }\r
6560 \r
6561     \r
6562     /*\r
6563      * Look for thinking output\r
6564      */\r
6565     if ( appData.showThinking // [HGM] thinking: test all options that cause this output\r
6566           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()\r
6567                                 ) {\r
6568         int plylev, mvleft, mvtot, curscore, time;\r
6569         char mvname[MOVE_LEN];\r
6570         u64 nodes; // [DM]\r
6571         char plyext;\r
6572         int ignore = FALSE;\r
6573         int prefixHint = FALSE;\r
6574         mvname[0] = NULLCHAR;\r
6575 \r
6576         switch (gameMode) {\r
6577           case MachinePlaysBlack:\r
6578           case IcsPlayingBlack:\r
6579             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
6580             break;\r
6581           case MachinePlaysWhite:\r
6582           case IcsPlayingWhite:\r
6583             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
6584             break;\r
6585           case AnalyzeMode:\r
6586           case AnalyzeFile:\r
6587             break;\r
6588           case IcsObserving: /* [DM] icsEngineAnalyze */\r
6589             if (!appData.icsEngineAnalyze) ignore = TRUE;\r
6590             break;\r
6591           case TwoMachinesPlay:\r
6592             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {\r
6593                 ignore = TRUE;\r
6594             }\r
6595             break;\r
6596           default:\r
6597             ignore = TRUE;\r
6598             break;\r
6599         }\r
6600 \r
6601         if (!ignore) {\r
6602             buf1[0] = NULLCHAR;\r
6603             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",\r
6604                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {\r
6605 \r
6606                 if (plyext != ' ' && plyext != '\t') {\r
6607                     time *= 100;\r
6608                 }\r
6609 \r
6610                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
6611                 if( cps->scoreIsAbsolute && \r
6612                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )\r
6613                 {\r
6614                     curscore = -curscore;\r
6615                 }\r
6616 \r
6617 \r
6618                 programStats.depth = plylev;\r
6619                 programStats.nodes = nodes;\r
6620                 programStats.time = time;\r
6621                 programStats.score = curscore;\r
6622                 programStats.got_only_move = 0;\r
6623 \r
6624                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */\r
6625                         int ticklen;\r
6626 \r
6627                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time\r
6628                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time\r
6629                         if(WhiteOnMove(forwardMostMove)) \r
6630                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;\r
6631                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;\r
6632                 }\r
6633 \r
6634                 /* Buffer overflow protection */\r
6635                 if (buf1[0] != NULLCHAR) {\r
6636                     if (strlen(buf1) >= sizeof(programStats.movelist)\r
6637                         && appData.debugMode) {\r
6638                         fprintf(debugFP,\r
6639                                 "PV is too long; using the first %d bytes.\n",\r
6640                                 sizeof(programStats.movelist) - 1);\r
6641                     }\r
6642 \r
6643                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );\r
6644                 } else {\r
6645                     sprintf(programStats.movelist, " no PV\n");\r
6646                 }\r
6647 \r
6648                 if (programStats.seen_stat) {\r
6649                     programStats.ok_to_send = 1;\r
6650                 }\r
6651 \r
6652                 if (strchr(programStats.movelist, '(') != NULL) {\r
6653                     programStats.line_is_book = 1;\r
6654                     programStats.nr_moves = 0;\r
6655                     programStats.moves_left = 0;\r
6656                 } else {\r
6657                     programStats.line_is_book = 0;\r
6658                 }\r
6659 \r
6660                 SendProgramStatsToFrontend( cps, &programStats );\r
6661 \r
6662                 /* \r
6663                     [AS] Protect the thinkOutput buffer from overflow... this\r
6664                     is only useful if buf1 hasn't overflowed first!\r
6665                 */\r
6666                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",\r
6667                         plylev, \r
6668                         (gameMode == TwoMachinesPlay ?\r
6669                          ToUpper(cps->twoMachinesColor[0]) : ' '),\r
6670                         ((double) curscore) / 100.0,\r
6671                         prefixHint ? lastHint : "",\r
6672                         prefixHint ? " " : "" );\r
6673 \r
6674                 if( buf1[0] != NULLCHAR ) {\r
6675                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;\r
6676 \r
6677                     if( strlen(buf1) > max_len ) {\r
6678                         if( appData.debugMode) {\r
6679                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");\r
6680                         }\r
6681                         buf1[max_len+1] = '\0';\r
6682                     }\r
6683 \r
6684                     strcat( thinkOutput, buf1 );\r
6685                 }\r
6686 \r
6687                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode\r
6688                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6689                     DisplayMove(currentMove - 1);\r
6690                     DisplayAnalysis();\r
6691                 }\r
6692                 return;\r
6693 \r
6694             } else if ((p=StrStr(message, "(only move)")) != NULL) {\r
6695                 /* crafty (9.25+) says "(only move) <move>"\r
6696                  * if there is only 1 legal move\r
6697                  */\r
6698                 sscanf(p, "(only move) %s", buf1);\r
6699                 sprintf(thinkOutput, "%s (only move)", buf1);\r
6700                 sprintf(programStats.movelist, "%s (only move)", buf1);\r
6701                 programStats.depth = 1;\r
6702                 programStats.nr_moves = 1;\r
6703                 programStats.moves_left = 1;\r
6704                 programStats.nodes = 1;\r
6705                 programStats.time = 1;\r
6706                 programStats.got_only_move = 1;\r
6707 \r
6708                 /* Not really, but we also use this member to\r
6709                    mean "line isn't going to change" (Crafty\r
6710                    isn't searching, so stats won't change) */\r
6711                 programStats.line_is_book = 1;\r
6712 \r
6713                 SendProgramStatsToFrontend( cps, &programStats );\r
6714                 \r
6715                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || \r
6716                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6717                     DisplayMove(currentMove - 1);\r
6718                     DisplayAnalysis();\r
6719                 }\r
6720                 return;\r
6721             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",\r
6722                               &time, &nodes, &plylev, &mvleft,\r
6723                               &mvtot, mvname) >= 5) {\r
6724                 /* The stat01: line is from Crafty (9.29+) in response\r
6725                    to the "." command */\r
6726                 programStats.seen_stat = 1;\r
6727                 cps->maybeThinking = TRUE;\r
6728 \r
6729                 if (programStats.got_only_move || !appData.periodicUpdates)\r
6730                   return;\r
6731 \r
6732                 programStats.depth = plylev;\r
6733                 programStats.time = time;\r
6734                 programStats.nodes = nodes;\r
6735                 programStats.moves_left = mvleft;\r
6736                 programStats.nr_moves = mvtot;\r
6737                 strcpy(programStats.move_name, mvname);\r
6738                 programStats.ok_to_send = 1;\r
6739                 programStats.movelist[0] = '\0';\r
6740 \r
6741                 SendProgramStatsToFrontend( cps, &programStats );\r
6742 \r
6743                 DisplayAnalysis();\r
6744                 return;\r
6745 \r
6746             } else if (strncmp(message,"++",2) == 0) {\r
6747                 /* Crafty 9.29+ outputs this */\r
6748                 programStats.got_fail = 2;\r
6749                 return;\r
6750 \r
6751             } else if (strncmp(message,"--",2) == 0) {\r
6752                 /* Crafty 9.29+ outputs this */\r
6753                 programStats.got_fail = 1;\r
6754                 return;\r
6755 \r
6756             } else if (thinkOutput[0] != NULLCHAR &&\r
6757                        strncmp(message, "    ", 4) == 0) {\r
6758                 unsigned message_len;\r
6759 \r
6760                 p = message;\r
6761                 while (*p && *p == ' ') p++;\r
6762 \r
6763                 message_len = strlen( p );\r
6764 \r
6765                 /* [AS] Avoid buffer overflow */\r
6766                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {\r
6767                     strcat(thinkOutput, " ");\r
6768                     strcat(thinkOutput, p);\r
6769                 }\r
6770 \r
6771                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {\r
6772                     strcat(programStats.movelist, " ");\r
6773                     strcat(programStats.movelist, p);\r
6774                 }\r
6775 \r
6776                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||\r
6777                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6778                     DisplayMove(currentMove - 1);\r
6779                     DisplayAnalysis();\r
6780                 }\r
6781                 return;\r
6782             }\r
6783         }\r
6784         else {\r
6785             buf1[0] = NULLCHAR;\r
6786 \r
6787             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",\r
6788                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) \r
6789             {\r
6790                 ChessProgramStats cpstats;\r
6791 \r
6792                 if (plyext != ' ' && plyext != '\t') {\r
6793                     time *= 100;\r
6794                 }\r
6795 \r
6796                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
6797                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {\r
6798                     curscore = -curscore;\r
6799                 }\r
6800 \r
6801                 cpstats.depth = plylev;\r
6802                 cpstats.nodes = nodes;\r
6803                 cpstats.time = time;\r
6804                 cpstats.score = curscore;\r
6805                 cpstats.got_only_move = 0;\r
6806                 cpstats.movelist[0] = '\0';\r
6807 \r
6808                 if (buf1[0] != NULLCHAR) {\r
6809                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );\r
6810                 }\r
6811 \r
6812                 cpstats.ok_to_send = 0;\r
6813                 cpstats.line_is_book = 0;\r
6814                 cpstats.nr_moves = 0;\r
6815                 cpstats.moves_left = 0;\r
6816 \r
6817                 SendProgramStatsToFrontend( cps, &cpstats );\r
6818             }\r
6819         }\r
6820     }\r
6821 }\r
6822 \r
6823 \r
6824 /* Parse a game score from the character string "game", and\r
6825    record it as the history of the current game.  The game\r
6826    score is NOT assumed to start from the standard position. \r
6827    The display is not updated in any way.\r
6828    */\r
6829 void\r
6830 ParseGameHistory(game)\r
6831      char *game;\r
6832 {\r
6833     ChessMove moveType;\r
6834     int fromX, fromY, toX, toY, boardIndex;\r
6835     char promoChar;\r
6836     char *p, *q;\r
6837     char buf[MSG_SIZ];\r
6838 \r
6839     if (appData.debugMode)\r
6840       fprintf(debugFP, "Parsing game history: %s\n", game);\r
6841 \r
6842     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");\r
6843     gameInfo.site = StrSave(appData.icsHost);\r
6844     gameInfo.date = PGNDate();\r
6845     gameInfo.round = StrSave("-");\r
6846 \r
6847     /* Parse out names of players */\r
6848     while (*game == ' ') game++;\r
6849     p = buf;\r
6850     while (*game != ' ') *p++ = *game++;\r
6851     *p = NULLCHAR;\r
6852     gameInfo.white = StrSave(buf);\r
6853     while (*game == ' ') game++;\r
6854     p = buf;\r
6855     while (*game != ' ' && *game != '\n') *p++ = *game++;\r
6856     *p = NULLCHAR;\r
6857     gameInfo.black = StrSave(buf);\r
6858 \r
6859     /* Parse moves */\r
6860     boardIndex = blackPlaysFirst ? 1 : 0;\r
6861     yynewstr(game);\r
6862     for (;;) {\r
6863         yyboardindex = boardIndex;\r
6864         moveType = (ChessMove) yylex();\r
6865         switch (moveType) {\r
6866           case IllegalMove:             /* maybe suicide chess, etc. */\r
6867   if (appData.debugMode) {\r
6868     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);\r
6869     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6870     setbuf(debugFP, NULL);\r
6871   }\r
6872           case WhitePromotionChancellor:\r
6873           case BlackPromotionChancellor:\r
6874           case WhitePromotionArchbishop:\r
6875           case BlackPromotionArchbishop:\r
6876           case WhitePromotionQueen:\r
6877           case BlackPromotionQueen:\r
6878           case WhitePromotionRook:\r
6879           case BlackPromotionRook:\r
6880           case WhitePromotionBishop:\r
6881           case BlackPromotionBishop:\r
6882           case WhitePromotionKnight:\r
6883           case BlackPromotionKnight:\r
6884           case WhitePromotionKing:\r
6885           case BlackPromotionKing:\r
6886           case NormalMove:\r
6887           case WhiteCapturesEnPassant:\r
6888           case BlackCapturesEnPassant:\r
6889           case WhiteKingSideCastle:\r
6890           case WhiteQueenSideCastle:\r
6891           case BlackKingSideCastle:\r
6892           case BlackQueenSideCastle:\r
6893           case WhiteKingSideCastleWild:\r
6894           case WhiteQueenSideCastleWild:\r
6895           case BlackKingSideCastleWild:\r
6896           case BlackQueenSideCastleWild:\r
6897           /* PUSH Fabien */\r
6898           case WhiteHSideCastleFR:\r
6899           case WhiteASideCastleFR:\r
6900           case BlackHSideCastleFR:\r
6901           case BlackASideCastleFR:\r
6902           /* POP Fabien */\r
6903             fromX = currentMoveString[0] - AAA;\r
6904             fromY = currentMoveString[1] - ONE;\r
6905             toX = currentMoveString[2] - AAA;\r
6906             toY = currentMoveString[3] - ONE;\r
6907             promoChar = currentMoveString[4];\r
6908             break;\r
6909           case WhiteDrop:\r
6910           case BlackDrop:\r
6911             fromX = moveType == WhiteDrop ?\r
6912               (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
6913             (int) CharToPiece(ToLower(currentMoveString[0]));\r
6914             fromY = DROP_RANK;\r
6915             toX = currentMoveString[2] - AAA;\r
6916             toY = currentMoveString[3] - ONE;\r
6917             promoChar = NULLCHAR;\r
6918             break;\r
6919           case AmbiguousMove:\r
6920             /* bug? */\r
6921             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);\r
6922   if (appData.debugMode) {\r
6923     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);\r
6924     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6925     setbuf(debugFP, NULL);\r
6926   }\r
6927             DisplayError(buf, 0);\r
6928             return;\r
6929           case ImpossibleMove:\r
6930             /* bug? */\r
6931             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);\r
6932   if (appData.debugMode) {\r
6933     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);\r
6934     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6935     setbuf(debugFP, NULL);\r
6936   }\r
6937             DisplayError(buf, 0);\r
6938             return;\r
6939           case (ChessMove) 0:   /* end of file */\r
6940             if (boardIndex < backwardMostMove) {\r
6941                 /* Oops, gap.  How did that happen? */\r
6942                 DisplayError(_("Gap in move list"), 0);\r
6943                 return;\r
6944             }\r
6945             backwardMostMove =  blackPlaysFirst ? 1 : 0;\r
6946             if (boardIndex > forwardMostMove) {\r
6947                 forwardMostMove = boardIndex;\r
6948             }\r
6949             return;\r
6950           case ElapsedTime:\r
6951             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {\r
6952                 strcat(parseList[boardIndex-1], " ");\r
6953                 strcat(parseList[boardIndex-1], yy_text);\r
6954             }\r
6955             continue;\r
6956           case Comment:\r
6957           case PGNTag:\r
6958           case NAG:\r
6959           default:\r
6960             /* ignore */\r
6961             continue;\r
6962           case WhiteWins:\r
6963           case BlackWins:\r
6964           case GameIsDrawn:\r
6965           case GameUnfinished:\r
6966             if (gameMode == IcsExamining) {\r
6967                 if (boardIndex < backwardMostMove) {\r
6968                     /* Oops, gap.  How did that happen? */\r
6969                     return;\r
6970                 }\r
6971                 backwardMostMove = blackPlaysFirst ? 1 : 0;\r
6972                 return;\r
6973             }\r
6974             gameInfo.result = moveType;\r
6975             p = strchr(yy_text, '{');\r
6976             if (p == NULL) p = strchr(yy_text, '(');\r
6977             if (p == NULL) {\r
6978                 p = yy_text;\r
6979                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
6980             } else {\r
6981                 q = strchr(p, *p == '{' ? '}' : ')');\r
6982                 if (q != NULL) *q = NULLCHAR;\r
6983                 p++;\r
6984             }\r
6985             gameInfo.resultDetails = StrSave(p);\r
6986             continue;\r
6987         }\r
6988         if (boardIndex >= forwardMostMove &&\r
6989             !(gameMode == IcsObserving && ics_gamenum == -1)) {\r
6990             backwardMostMove = blackPlaysFirst ? 1 : 0;\r
6991             return;\r
6992         }\r
6993         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),\r
6994                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,\r
6995                                  parseList[boardIndex]);\r
6996         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);\r
6997         /* currentMoveString is set as a side-effect of yylex */\r
6998         strcpy(moveList[boardIndex], currentMoveString);\r
6999         strcat(moveList[boardIndex], "\n");\r
7000         boardIndex++;\r
7001         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);\r
7002         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),\r
7003                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {\r
7004           case MT_NONE:\r
7005           case MT_STALEMATE:\r
7006           default:\r
7007             break;\r
7008           case MT_CHECK:\r
7009             if(gameInfo.variant != VariantShogi)\r
7010                 strcat(parseList[boardIndex - 1], "+");\r
7011             break;\r
7012           case MT_CHECKMATE:\r
7013           case MT_STAINMATE:\r
7014             strcat(parseList[boardIndex - 1], "#");\r
7015             break;\r
7016         }\r
7017     }\r
7018 }\r
7019 \r
7020 \r
7021 /* Apply a move to the given board  */\r
7022 void\r
7023 ApplyMove(fromX, fromY, toX, toY, promoChar, board)\r
7024      int fromX, fromY, toX, toY;\r
7025      int promoChar;\r
7026      Board board;\r
7027 {\r
7028   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;\r
7029 \r
7030     /* [HGM] compute & store e.p. status and castling rights for new position */\r
7031     /* if we are updating a board for which those exist (i.e. in boards[])    */\r
7032     if((p = ((int)board - (int)boards[0])/((int)boards[1]-(int)boards[0])) < MAX_MOVES && p > 0)\r
7033     { int i;\r
7034 \r
7035       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;\r
7036       oldEP = epStatus[p-1];\r
7037       epStatus[p] = EP_NONE;\r
7038 \r
7039       if( board[toY][toX] != EmptySquare ) \r
7040            epStatus[p] = EP_CAPTURE;  \r
7041 \r
7042       if( board[fromY][fromX] == WhitePawn ) {\r
7043            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers\r
7044                epStatus[p] = EP_PAWN_MOVE;\r
7045            if( toY-fromY==2) {\r
7046                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&\r
7047                         gameInfo.variant != VariantBerolina || toX < fromX)\r
7048                       epStatus[p] = toX | berolina;\r
7049                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&\r
7050                         gameInfo.variant != VariantBerolina || toX > fromX) \r
7051                       epStatus[p] = toX;\r
7052            }\r
7053       } else \r
7054       if( board[fromY][fromX] == BlackPawn ) {\r
7055            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers\r
7056                epStatus[p] = EP_PAWN_MOVE; \r
7057            if( toY-fromY== -2) {\r
7058                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&\r
7059                         gameInfo.variant != VariantBerolina || toX < fromX)\r
7060                       epStatus[p] = toX | berolina;\r
7061                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&\r
7062                         gameInfo.variant != VariantBerolina || toX > fromX) \r
7063                       epStatus[p] = toX;\r
7064            }\r
7065        }\r
7066 \r
7067        for(i=0; i<nrCastlingRights; i++) {\r
7068            castlingRights[p][i] = castlingRights[p-1][i];\r
7069            if(castlingRights[p][i] == fromX && castlingRank[i] == fromY ||\r
7070               castlingRights[p][i] == toX   && castlingRank[i] == toY   \r
7071              ) castlingRights[p][i] = -1; // revoke for moved or captured piece\r
7072        }\r
7073 \r
7074     }\r
7075 \r
7076   /* [HGM] In Shatranj and Courier all promotions are to Ferz */\r
7077   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)\r
7078        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);\r
7079          \r
7080   if (fromX == toX && fromY == toY) return;\r
7081 \r
7082   if (fromY == DROP_RANK) {\r
7083         /* must be first */\r
7084         piece = board[toY][toX] = (ChessSquare) fromX;\r
7085   } else {\r
7086      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */\r
7087      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */\r
7088      if(gameInfo.variant == VariantKnightmate)\r
7089          king += (int) WhiteUnicorn - (int) WhiteKing;\r
7090 \r
7091     /* Code added by Tord: */\r
7092     /* FRC castling assumed when king captures friendly rook. */\r
7093     if (board[fromY][fromX] == WhiteKing &&\r
7094              board[toY][toX] == WhiteRook) {\r
7095       board[fromY][fromX] = EmptySquare;\r
7096       board[toY][toX] = EmptySquare;\r
7097       if(toX > fromX) {\r
7098         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;\r
7099       } else {\r
7100         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;\r
7101       }\r
7102     } else if (board[fromY][fromX] == BlackKing &&\r
7103                board[toY][toX] == BlackRook) {\r
7104       board[fromY][fromX] = EmptySquare;\r
7105       board[toY][toX] = EmptySquare;\r
7106       if(toX > fromX) {\r
7107         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;\r
7108       } else {\r
7109         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;\r
7110       }\r
7111     /* End of code added by Tord */\r
7112 \r
7113     } else if (board[fromY][fromX] == king\r
7114         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7115         && toY == fromY && toX > fromX+1) {\r
7116         board[fromY][fromX] = EmptySquare;\r
7117         board[toY][toX] = king;\r
7118         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
7119         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
7120     } else if (board[fromY][fromX] == king\r
7121         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7122                && toY == fromY && toX < fromX-1) {\r
7123         board[fromY][fromX] = EmptySquare;\r
7124         board[toY][toX] = king;\r
7125         board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
7126         board[fromY][BOARD_LEFT] = EmptySquare;\r
7127     } else if (board[fromY][fromX] == WhitePawn\r
7128                && toY == BOARD_HEIGHT-1\r
7129                && gameInfo.variant != VariantXiangqi\r
7130                ) {\r
7131         /* white pawn promotion */\r
7132         board[toY][toX] = CharToPiece(ToUpper(promoChar));\r
7133         if (board[toY][toX] == EmptySquare) {\r
7134             board[toY][toX] = WhiteQueen;\r
7135         }\r
7136         if(gameInfo.variant==VariantBughouse ||\r
7137            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
7138             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
7139         board[fromY][fromX] = EmptySquare;\r
7140     } else if ((fromY == BOARD_HEIGHT-4)\r
7141                && (toX != fromX)\r
7142                && gameInfo.variant != VariantXiangqi\r
7143                && gameInfo.variant != VariantBerolina\r
7144                && (board[fromY][fromX] == WhitePawn)\r
7145                && (board[toY][toX] == EmptySquare)) {\r
7146         board[fromY][fromX] = EmptySquare;\r
7147         board[toY][toX] = WhitePawn;\r
7148         captured = board[toY - 1][toX];\r
7149         board[toY - 1][toX] = EmptySquare;\r
7150     } else if ((fromY == BOARD_HEIGHT-4)\r
7151                && (toX == fromX)\r
7152                && gameInfo.variant == VariantBerolina\r
7153                && (board[fromY][fromX] == WhitePawn)\r
7154                && (board[toY][toX] == EmptySquare)) {\r
7155         board[fromY][fromX] = EmptySquare;\r
7156         board[toY][toX] = WhitePawn;\r
7157         if(oldEP & EP_BEROLIN_A) {\r
7158                 captured = board[fromY][fromX-1];\r
7159                 board[fromY][fromX-1] = EmptySquare;\r
7160         }else{  captured = board[fromY][fromX+1];\r
7161                 board[fromY][fromX+1] = EmptySquare;\r
7162         }\r
7163     } else if (board[fromY][fromX] == king\r
7164         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7165                && toY == fromY && toX > fromX+1) {\r
7166         board[fromY][fromX] = EmptySquare;\r
7167         board[toY][toX] = king;\r
7168         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
7169         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
7170     } else if (board[fromY][fromX] == king\r
7171         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7172                && toY == fromY && toX < fromX-1) {\r
7173         board[fromY][fromX] = EmptySquare;\r
7174         board[toY][toX] = king;\r
7175         board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
7176         board[fromY][BOARD_LEFT] = EmptySquare;\r
7177     } else if (fromY == 7 && fromX == 3\r
7178                && board[fromY][fromX] == BlackKing\r
7179                && toY == 7 && toX == 5) {\r
7180         board[fromY][fromX] = EmptySquare;\r
7181         board[toY][toX] = BlackKing;\r
7182         board[fromY][7] = EmptySquare;\r
7183         board[toY][4] = BlackRook;\r
7184     } else if (fromY == 7 && fromX == 3\r
7185                && board[fromY][fromX] == BlackKing\r
7186                && toY == 7 && toX == 1) {\r
7187         board[fromY][fromX] = EmptySquare;\r
7188         board[toY][toX] = BlackKing;\r
7189         board[fromY][0] = EmptySquare;\r
7190         board[toY][2] = BlackRook;\r
7191     } else if (board[fromY][fromX] == BlackPawn\r
7192                && toY == 0\r
7193                && gameInfo.variant != VariantXiangqi\r
7194                ) {\r
7195         /* black pawn promotion */\r
7196         board[0][toX] = CharToPiece(ToLower(promoChar));\r
7197         if (board[0][toX] == EmptySquare) {\r
7198             board[0][toX] = BlackQueen;\r
7199         }\r
7200         if(gameInfo.variant==VariantBughouse ||\r
7201            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
7202             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
7203         board[fromY][fromX] = EmptySquare;\r
7204     } else if ((fromY == 3)\r
7205                && (toX != fromX)\r
7206                && gameInfo.variant != VariantXiangqi\r
7207                && gameInfo.variant != VariantBerolina\r
7208                && (board[fromY][fromX] == BlackPawn)\r
7209                && (board[toY][toX] == EmptySquare)) {\r
7210         board[fromY][fromX] = EmptySquare;\r
7211         board[toY][toX] = BlackPawn;\r
7212         captured = board[toY + 1][toX];\r
7213         board[toY + 1][toX] = EmptySquare;\r
7214     } else if ((fromY == 3)\r
7215                && (toX == fromX)\r
7216                && gameInfo.variant == VariantBerolina\r
7217                && (board[fromY][fromX] == BlackPawn)\r
7218                && (board[toY][toX] == EmptySquare)) {\r
7219         board[fromY][fromX] = EmptySquare;\r
7220         board[toY][toX] = BlackPawn;\r
7221         if(oldEP & EP_BEROLIN_A) {\r
7222                 captured = board[fromY][fromX-1];\r
7223                 board[fromY][fromX-1] = EmptySquare;\r
7224         }else{  captured = board[fromY][fromX+1];\r
7225                 board[fromY][fromX+1] = EmptySquare;\r
7226         }\r
7227     } else {\r
7228         board[toY][toX] = board[fromY][fromX];\r
7229         board[fromY][fromX] = EmptySquare;\r
7230     }\r
7231 \r
7232     /* [HGM] now we promote for Shogi, if needed */\r
7233     if(gameInfo.variant == VariantShogi && promoChar == 'q')\r
7234         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
7235   }\r
7236 \r
7237     if (gameInfo.holdingsWidth != 0) {\r
7238 \r
7239       /* !!A lot more code needs to be written to support holdings  */\r
7240       /* [HGM] OK, so I have written it. Holdings are stored in the */\r
7241       /* penultimate board files, so they are automaticlly stored   */\r
7242       /* in the game history.                                       */\r
7243       if (fromY == DROP_RANK) {\r
7244         /* Delete from holdings, by decreasing count */\r
7245         /* and erasing image if necessary            */\r
7246         p = (int) fromX;\r
7247         if(p < (int) BlackPawn) { /* white drop */\r
7248              p -= (int)WhitePawn;\r
7249              if(p >= gameInfo.holdingsSize) p = 0;\r
7250              if(--board[p][BOARD_WIDTH-2] == 0)\r
7251                   board[p][BOARD_WIDTH-1] = EmptySquare;\r
7252         } else {                  /* black drop */\r
7253              p -= (int)BlackPawn;\r
7254              if(p >= gameInfo.holdingsSize) p = 0;\r
7255              if(--board[BOARD_HEIGHT-1-p][1] == 0)\r
7256                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;\r
7257         }\r
7258       }\r
7259       if (captured != EmptySquare && gameInfo.holdingsSize > 0\r
7260           && gameInfo.variant != VariantBughouse        ) {\r
7261         /* [HGM] holdings: Add to holdings, if holdings exist */\r
7262         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { \r
7263                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip\r
7264                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;\r
7265         }\r
7266         p = (int) captured;\r
7267         if (p >= (int) BlackPawn) {\r
7268           p -= (int)BlackPawn;\r
7269           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
7270                   /* in Shogi restore piece to its original  first */\r
7271                   captured = (ChessSquare) (DEMOTED captured);\r
7272                   p = DEMOTED p;\r
7273           }\r
7274           p = PieceToNumber((ChessSquare)p);\r
7275           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }\r
7276           board[p][BOARD_WIDTH-2]++;\r
7277           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;\r
7278         } else {\r
7279           p -= (int)WhitePawn;\r
7280           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
7281                   captured = (ChessSquare) (DEMOTED captured);\r
7282                   p = DEMOTED p;\r
7283           }\r
7284           p = PieceToNumber((ChessSquare)p);\r
7285           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }\r
7286           board[BOARD_HEIGHT-1-p][1]++;\r
7287           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;\r
7288         }\r
7289       }\r
7290 \r
7291     } else if (gameInfo.variant == VariantAtomic) {\r
7292       if (captured != EmptySquare) {\r
7293         int y, x;\r
7294         for (y = toY-1; y <= toY+1; y++) {\r
7295           for (x = toX-1; x <= toX+1; x++) {\r
7296             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&\r
7297                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {\r
7298               board[y][x] = EmptySquare;\r
7299             }\r
7300           }\r
7301         }\r
7302         board[toY][toX] = EmptySquare;\r
7303       }\r
7304     }\r
7305     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {\r
7306         /* [HGM] Shogi promotions */\r
7307         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
7308     }\r
7309 \r
7310 }\r
7311 \r
7312 /* Updates forwardMostMove */\r
7313 void\r
7314 MakeMove(fromX, fromY, toX, toY, promoChar)\r
7315      int fromX, fromY, toX, toY;\r
7316      int promoChar;\r
7317 {\r
7318 //    forwardMostMove++; // [HGM] bare: moved downstream\r
7319 \r
7320     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */\r
7321         int timeLeft; static int lastLoadFlag=0; int king, piece;\r
7322         piece = boards[forwardMostMove][fromY][fromX];\r
7323         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;\r
7324         if(gameInfo.variant == VariantKnightmate)\r
7325             king += (int) WhiteUnicorn - (int) WhiteKing;\r
7326         if(forwardMostMove == 0) {\r
7327             if(blackPlaysFirst) \r
7328                 fprintf(serverMoves, "%s;", second.tidy);\r
7329             fprintf(serverMoves, "%s;", first.tidy);\r
7330             if(!blackPlaysFirst) \r
7331                 fprintf(serverMoves, "%s;", second.tidy);\r
7332         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");\r
7333         lastLoadFlag = loadFlag;\r
7334         // print base move\r
7335         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);\r
7336         // print castling suffix\r
7337         if( toY == fromY && piece == king ) {\r
7338             if(toX-fromX > 1)\r
7339                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);\r
7340             if(fromX-toX >1)\r
7341                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);\r
7342         }\r
7343         // e.p. suffix\r
7344         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||\r
7345              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&\r
7346              boards[forwardMostMove][toY][toX] == EmptySquare\r
7347              && fromX != toX )\r
7348                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);\r
7349         // promotion suffix\r
7350         if(promoChar != NULLCHAR)\r
7351                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);\r
7352         if(!loadFlag) {\r
7353             fprintf(serverMoves, "/%d/%d",\r
7354                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);\r
7355             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;\r
7356             else                      timeLeft = blackTimeRemaining/1000;\r
7357             fprintf(serverMoves, "/%d", timeLeft);\r
7358         }\r
7359         fflush(serverMoves);\r
7360     }\r
7361 \r
7362     if (forwardMostMove+1 >= MAX_MOVES) {\r
7363       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),\r
7364                         0, 1);\r
7365       return;\r
7366     }\r
7367     SwitchClocks();\r
7368     timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;\r
7369     timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;\r
7370     if (commentList[forwardMostMove+1] != NULL) {\r
7371         free(commentList[forwardMostMove+1]);\r
7372         commentList[forwardMostMove+1] = NULL;\r
7373     }\r
7374     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);\r
7375     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);\r
7376     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board\r
7377     gameInfo.result = GameUnfinished;\r
7378     if (gameInfo.resultDetails != NULL) {\r
7379         free(gameInfo.resultDetails);\r
7380         gameInfo.resultDetails = NULL;\r
7381     }\r
7382     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,\r
7383                               moveList[forwardMostMove - 1]);\r
7384     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],\r
7385                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,\r
7386                              fromY, fromX, toY, toX, promoChar,\r
7387                              parseList[forwardMostMove - 1]);\r
7388     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
7389                        epStatus[forwardMostMove], /* [HGM] use true e.p. */\r
7390                             castlingRights[forwardMostMove]) ) {\r
7391       case MT_NONE:\r
7392       case MT_STALEMATE:\r
7393       default:\r
7394         break;\r
7395       case MT_CHECK:\r
7396         if(gameInfo.variant != VariantShogi)\r
7397             strcat(parseList[forwardMostMove - 1], "+");\r
7398         break;\r
7399       case MT_CHECKMATE:\r
7400       case MT_STAINMATE:\r
7401         strcat(parseList[forwardMostMove - 1], "#");\r
7402         break;\r
7403     }\r
7404     if (appData.debugMode) {\r
7405         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);\r
7406     }\r
7407 \r
7408 }\r
7409 \r
7410 /* Updates currentMove if not pausing */\r
7411 void\r
7412 ShowMove(fromX, fromY, toX, toY)\r
7413 {\r
7414     int instant = (gameMode == PlayFromGameFile) ?\r
7415         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;\r
7416     if(appData.noGUI) return;\r
7417     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
7418         if (!instant) {\r
7419             if (forwardMostMove == currentMove + 1) {\r
7420                 AnimateMove(boards[forwardMostMove - 1],\r
7421                             fromX, fromY, toX, toY);\r
7422             }\r
7423             if (appData.highlightLastMove) {\r
7424                 SetHighlights(fromX, fromY, toX, toY);\r
7425             }\r
7426         }\r
7427         currentMove = forwardMostMove;\r
7428     }\r
7429 \r
7430     if (instant) return;\r
7431 \r
7432     DisplayMove(currentMove - 1);\r
7433     DrawPosition(FALSE, boards[currentMove]);\r
7434     DisplayBothClocks();\r
7435     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
7436 }\r
7437 \r
7438 void SendEgtPath(ChessProgramState *cps)\r
7439 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */\r
7440         char buf[MSG_SIZ], name[MSG_SIZ], *p;\r
7441 \r
7442         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;\r
7443 \r
7444         while(*p) {\r
7445             char c, *q = name+1, *r, *s;\r
7446 \r
7447             name[0] = ','; // extract next format name from feature and copy with prefixed ','\r
7448             while(*p && *p != ',') *q++ = *p++;\r
7449             *q++ = ':'; *q = 0;\r
7450             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && \r
7451                 strcmp(name, ",nalimov:") == 0 ) {\r
7452                 // take nalimov path from the menu-changeable option first, if it is defined\r
7453                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);\r
7454                 SendToProgram(buf,cps);     // send egtbpath command for nalimov\r
7455             } else\r
7456             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||\r
7457                 (s = StrStr(appData.egtFormats, name)) != NULL) {\r
7458                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma\r
7459                 s = r = StrStr(s, ":") + 1; // beginning of path info\r
7460                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string\r
7461                 c = *r; *r = 0;             // temporarily null-terminate path info\r
7462                     *--q = 0;               // strip of trailig ':' from name\r
7463                     sprintf(buf, "egtbpath %s %s\n", name+1, s);\r
7464                 *r = c;\r
7465                 SendToProgram(buf,cps);     // send egtbpath command for this format\r
7466             }\r
7467             if(*p == ',') p++; // read away comma to position for next format name\r
7468         }\r
7469 }\r
7470 \r
7471 void\r
7472 InitChessProgram(cps, setup)\r
7473      ChessProgramState *cps;\r
7474      int setup; /* [HGM] needed to setup FRC opening position */\r
7475 {\r
7476     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;\r
7477     if (appData.noChessProgram) return;\r
7478     hintRequested = FALSE;\r
7479     bookRequested = FALSE;\r
7480 \r
7481     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */\r
7482     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */\r
7483     if(cps->memSize) { /* [HGM] memory */\r
7484         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);\r
7485         SendToProgram(buf, cps);\r
7486     }\r
7487     SendEgtPath(cps); /* [HGM] EGT */\r
7488     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */\r
7489         sprintf(buf, "cores %d\n", appData.smpCores);\r
7490         SendToProgram(buf, cps);\r
7491     }\r
7492 \r
7493     SendToProgram(cps->initString, cps);\r
7494     if (gameInfo.variant != VariantNormal &&\r
7495         gameInfo.variant != VariantLoadable\r
7496         /* [HGM] also send variant if board size non-standard */\r
7497         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0\r
7498                                             ) {\r
7499       char *v = VariantName(gameInfo.variant);\r
7500       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {\r
7501         /* [HGM] in protocol 1 we have to assume all variants valid */\r
7502         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);\r
7503         DisplayFatalError(buf, 0, 1);\r
7504         return;\r
7505       }\r
7506 \r
7507       /* [HGM] make prefix for non-standard board size. Awkward testing... */\r
7508       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7509       if( gameInfo.variant == VariantXiangqi )\r
7510            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;\r
7511       if( gameInfo.variant == VariantShogi )\r
7512            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;\r
7513       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )\r
7514            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;\r
7515       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || \r
7516                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )\r
7517            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7518       if( gameInfo.variant == VariantCourier )\r
7519            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7520       if( gameInfo.variant == VariantSuper )\r
7521            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;\r
7522       if( gameInfo.variant == VariantGreat )\r
7523            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;\r
7524 \r
7525       if(overruled) {\r
7526            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, \r
7527                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name\r
7528            /* [HGM] varsize: try first if this defiant size variant is specifically known */\r
7529            if(StrStr(cps->variants, b) == NULL) { \r
7530                // specific sized variant not known, check if general sizing allowed\r
7531                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best\r
7532                    if(StrStr(cps->variants, "boardsize") == NULL) {\r
7533                        sprintf(buf, "Board size %dx%d+%d not supported by %s",\r
7534                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);\r
7535                        DisplayFatalError(buf, 0, 1);\r
7536                        return;\r
7537                    }\r
7538                    /* [HGM] here we really should compare with the maximum supported board size */\r
7539                }\r
7540            }\r
7541       } else sprintf(b, "%s", VariantName(gameInfo.variant));\r
7542       sprintf(buf, "variant %s\n", b);\r
7543       SendToProgram(buf, cps);\r
7544     }\r
7545     currentlyInitializedVariant = gameInfo.variant;\r
7546 \r
7547     /* [HGM] send opening position in FRC to first engine */\r
7548     if(setup) {\r
7549           SendToProgram("force\n", cps);\r
7550           SendBoard(cps, 0);\r
7551           /* engine is now in force mode! Set flag to wake it up after first move. */\r
7552           setboardSpoiledMachineBlack = 1;\r
7553     }\r
7554 \r
7555     if (cps->sendICS) {\r
7556       sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");\r
7557       SendToProgram(buf, cps);\r
7558     }\r
7559     cps->maybeThinking = FALSE;\r
7560     cps->offeredDraw = 0;\r
7561     if (!appData.icsActive) {\r
7562         SendTimeControl(cps, movesPerSession, timeControl,\r
7563                         timeIncrement, appData.searchDepth,\r
7564                         searchTime);\r
7565     }\r
7566     if (appData.showThinking \r
7567         // [HGM] thinking: four options require thinking output to be sent\r
7568         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()\r
7569                                 ) {\r
7570         SendToProgram("post\n", cps);\r
7571     }\r
7572     SendToProgram("hard\n", cps);\r
7573     if (!appData.ponderNextMove) {\r
7574         /* Warning: "easy" is a toggle in GNU Chess, so don't send\r
7575            it without being sure what state we are in first.  "hard"\r
7576            is not a toggle, so that one is OK.\r
7577          */\r
7578         SendToProgram("easy\n", cps);\r
7579     }\r
7580     if (cps->usePing) {\r
7581       sprintf(buf, "ping %d\n", ++cps->lastPing);\r
7582       SendToProgram(buf, cps);\r
7583     }\r
7584     cps->initDone = TRUE;\r
7585 }   \r
7586 \r
7587 \r
7588 void\r
7589 StartChessProgram(cps)\r
7590      ChessProgramState *cps;\r
7591 {\r
7592     char buf[MSG_SIZ];\r
7593     int err;\r
7594 \r
7595     if (appData.noChessProgram) return;\r
7596     cps->initDone = FALSE;\r
7597 \r
7598     if (strcmp(cps->host, "localhost") == 0) {\r
7599         err = StartChildProcess(cps->program, cps->dir, &cps->pr);\r
7600     } else if (*appData.remoteShell == NULLCHAR) {\r
7601         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);\r
7602     } else {\r
7603         if (*appData.remoteUser == NULLCHAR) {\r
7604             sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,\r
7605                     cps->program);\r
7606         } else {\r
7607             sprintf(buf, "%s %s -l %s %s", appData.remoteShell,\r
7608                     cps->host, appData.remoteUser, cps->program);\r
7609         }\r
7610         err = StartChildProcess(buf, "", &cps->pr);\r
7611     }\r
7612     \r
7613     if (err != 0) {\r
7614         sprintf(buf, _("Startup failure on '%s'"), cps->program);\r
7615         DisplayFatalError(buf, err, 1);\r
7616         cps->pr = NoProc;\r
7617         cps->isr = NULL;\r
7618         return;\r
7619     }\r
7620     \r
7621     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);\r
7622     if (cps->protocolVersion > 1) {\r
7623       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);\r
7624       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options\r
7625       cps->comboCnt = 0;  //                and values of combo boxes\r
7626       SendToProgram(buf, cps);\r
7627     } else {\r
7628       SendToProgram("xboard\n", cps);\r
7629     }\r
7630 }\r
7631 \r
7632 \r
7633 void\r
7634 TwoMachinesEventIfReady P((void))\r
7635 {\r
7636   if (first.lastPing != first.lastPong) {\r
7637     DisplayMessage("", _("Waiting for first chess program"));\r
7638     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000\r
7639     return;\r
7640   }\r
7641   if (second.lastPing != second.lastPong) {\r
7642     DisplayMessage("", _("Waiting for second chess program"));\r
7643     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000\r
7644     return;\r
7645   }\r
7646   ThawUI();\r
7647   TwoMachinesEvent();\r
7648 }\r
7649 \r
7650 void\r
7651 NextMatchGame P((void))\r
7652 {\r
7653     int index; /* [HGM] autoinc: step lod index during match */\r
7654     Reset(FALSE, TRUE);\r
7655     if (*appData.loadGameFile != NULLCHAR) {\r
7656         index = appData.loadGameIndex;\r
7657         if(index < 0) { // [HGM] autoinc\r
7658             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;\r
7659             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;\r
7660         } \r
7661         LoadGameFromFile(appData.loadGameFile,\r
7662                          index,\r
7663                          appData.loadGameFile, FALSE);\r
7664     } else if (*appData.loadPositionFile != NULLCHAR) {\r
7665         index = appData.loadPositionIndex;\r
7666         if(index < 0) { // [HGM] autoinc\r
7667             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;\r
7668             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;\r
7669         } \r
7670         LoadPositionFromFile(appData.loadPositionFile,\r
7671                              index,\r
7672                              appData.loadPositionFile);\r
7673     }\r
7674     TwoMachinesEventIfReady();\r
7675 }\r
7676 \r
7677 void UserAdjudicationEvent( int result )\r
7678 {\r
7679     ChessMove gameResult = GameIsDrawn;\r
7680 \r
7681     if( result > 0 ) {\r
7682         gameResult = WhiteWins;\r
7683     }\r
7684     else if( result < 0 ) {\r
7685         gameResult = BlackWins;\r
7686     }\r
7687 \r
7688     if( gameMode == TwoMachinesPlay ) {\r
7689         GameEnds( gameResult, "User adjudication", GE_XBOARD );\r
7690     }\r
7691 }\r
7692 \r
7693 \r
7694 void\r
7695 GameEnds(result, resultDetails, whosays)\r
7696      ChessMove result;\r
7697      char *resultDetails;\r
7698      int whosays;\r
7699 {\r
7700     GameMode nextGameMode;\r
7701     int isIcsGame;\r
7702     char buf[MSG_SIZ];\r
7703 \r
7704     if(endingGame) return; /* [HGM] crash: forbid recursion */\r
7705     endingGame = 1;\r
7706 \r
7707     if (appData.debugMode) {\r
7708       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",\r
7709               result, resultDetails ? resultDetails : "(null)", whosays);\r
7710     }\r
7711 \r
7712     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {\r
7713         /* If we are playing on ICS, the server decides when the\r
7714            game is over, but the engine can offer to draw, claim \r
7715            a draw, or resign. \r
7716          */\r
7717 #if ZIPPY\r
7718         if (appData.zippyPlay && first.initDone) {\r
7719             if (result == GameIsDrawn) {\r
7720                 /* In case draw still needs to be claimed */\r
7721                 SendToICS(ics_prefix);\r
7722                 SendToICS("draw\n");\r
7723             } else if (StrCaseStr(resultDetails, "resign")) {\r
7724                 SendToICS(ics_prefix);\r
7725                 SendToICS("resign\n");\r
7726             }\r
7727         }\r
7728 #endif\r
7729         endingGame = 0; /* [HGM] crash */\r
7730         return;\r
7731     }\r
7732 \r
7733     /* If we're loading the game from a file, stop */\r
7734     if (whosays == GE_FILE) {\r
7735       (void) StopLoadGameTimer();\r
7736       gameFileFP = NULL;\r
7737     }\r
7738 \r
7739     /* Cancel draw offers */\r
7740     first.offeredDraw = second.offeredDraw = 0;\r
7741 \r
7742     /* If this is an ICS game, only ICS can really say it's done;\r
7743        if not, anyone can. */\r
7744     isIcsGame = (gameMode == IcsPlayingWhite || \r
7745                  gameMode == IcsPlayingBlack || \r
7746                  gameMode == IcsObserving    || \r
7747                  gameMode == IcsExamining);\r
7748 \r
7749     if (!isIcsGame || whosays == GE_ICS) {\r
7750         /* OK -- not an ICS game, or ICS said it was done */\r
7751         StopClocks();\r
7752         if (!isIcsGame && !appData.noChessProgram) \r
7753           SetUserThinkingEnables();\r
7754     \r
7755         /* [HGM] if a machine claims the game end we verify this claim */\r
7756         if(gameMode == TwoMachinesPlay && appData.testClaims) {\r
7757             if(appData.testLegality && whosays >= GE_ENGINE1 ) {\r
7758                 char claimer;\r
7759                 ChessMove trueResult = (ChessMove) -1;\r
7760 \r
7761                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */\r
7762                                             first.twoMachinesColor[0] :\r
7763                                             second.twoMachinesColor[0] ;\r
7764 \r
7765                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first\r
7766                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {\r
7767                     /* [HGM] verify: engine mate claims accepted if they were flagged */\r
7768                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;\r
7769                 } else\r
7770                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win\r
7771                     /* [HGM] verify: engine mate claims accepted if they were flagged */\r
7772                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;\r
7773                 } else\r
7774                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now\r
7775                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE\r
7776                 }\r
7777 \r
7778                 // now verify win claims, but not in drop games, as we don't understand those yet\r
7779                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper\r
7780                                                  || gameInfo.variant == VariantGreat) &&\r
7781                     (result == WhiteWins && claimer == 'w' ||\r
7782                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win\r
7783                       if (appData.debugMode) {\r
7784                         fprintf(debugFP, "result=%d sp=%d move=%d\n",\r
7785                                 result, epStatus[forwardMostMove], forwardMostMove);\r
7786                       }\r
7787                       if(result != trueResult) {\r
7788                               sprintf(buf, "False win claim: '%s'", resultDetails);\r
7789                               result = claimer == 'w' ? BlackWins : WhiteWins;\r
7790                               resultDetails = buf;\r
7791                       }\r
7792                 } else\r
7793                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS\r
7794                     && (forwardMostMove <= backwardMostMove ||\r
7795                         epStatus[forwardMostMove-1] > EP_DRAWS ||\r
7796                         (claimer=='b')==(forwardMostMove&1))\r
7797                                                                                   ) {\r
7798                       /* [HGM] verify: draws that were not flagged are false claims */\r
7799                       sprintf(buf, "False draw claim: '%s'", resultDetails);\r
7800                       result = claimer == 'w' ? BlackWins : WhiteWins;\r
7801                       resultDetails = buf;\r
7802                 }\r
7803                 /* (Claiming a loss is accepted no questions asked!) */\r
7804             }\r
7805             /* [HGM] bare: don't allow bare King to win */\r
7806             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)\r
7807                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway \r
7808                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...\r
7809                && result != GameIsDrawn)\r
7810             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);\r
7811                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {\r
7812                         int p = (int)boards[forwardMostMove][i][j] - color;\r
7813                         if(p >= 0 && p <= (int)WhiteKing) k++;\r
7814                 }\r
7815                 if (appData.debugMode) {\r
7816                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",\r
7817                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);\r
7818                 }\r
7819                 if(k <= 1) {\r
7820                         result = GameIsDrawn;\r
7821                         sprintf(buf, "%s but bare king", resultDetails);\r
7822                         resultDetails = buf;\r
7823                 }\r
7824             }\r
7825         }\r
7826 \r
7827 \r
7828         if(serverMoves != NULL && !loadFlag) { char c = '=';\r
7829             if(result==WhiteWins) c = '+';\r
7830             if(result==BlackWins) c = '-';\r
7831             if(resultDetails != NULL)\r
7832                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);\r
7833         }\r
7834         if (resultDetails != NULL) {\r
7835             gameInfo.result = result;\r
7836             gameInfo.resultDetails = StrSave(resultDetails);\r
7837 \r
7838             /* display last move only if game was not loaded from file */\r
7839             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))\r
7840                 DisplayMove(currentMove - 1);\r
7841     \r
7842             if (forwardMostMove != 0) {\r
7843                 if (gameMode != PlayFromGameFile && gameMode != EditGame) {\r
7844                     if (*appData.saveGameFile != NULLCHAR) {\r
7845                         SaveGameToFile(appData.saveGameFile, TRUE);\r
7846                     } else if (appData.autoSaveGames) {\r
7847                         AutoSaveGame();\r
7848                     }\r
7849                     if (*appData.savePositionFile != NULLCHAR) {\r
7850                         SavePositionToFile(appData.savePositionFile);\r
7851                     }\r
7852                 }\r
7853             }\r
7854 \r
7855             /* Tell program how game ended in case it is learning */\r
7856             /* [HGM] Moved this to after saving the PGN, just in case */\r
7857             /* engine died and we got here through time loss. In that */\r
7858             /* case we will get a fatal error writing the pipe, which */\r
7859             /* would otherwise lose us the PGN.                       */\r
7860             /* [HGM] crash: not needed anymore, but doesn't hurt;     */\r
7861             /* output during GameEnds should never be fatal anymore   */\r
7862             if (gameMode == MachinePlaysWhite ||\r
7863                 gameMode == MachinePlaysBlack ||\r
7864                 gameMode == TwoMachinesPlay ||\r
7865                 gameMode == IcsPlayingWhite ||\r
7866                 gameMode == IcsPlayingBlack ||\r
7867                 gameMode == BeginningOfGame) {\r
7868                 char buf[MSG_SIZ];\r
7869                 sprintf(buf, "result %s {%s}\n", PGNResult(result),\r
7870                         resultDetails);\r
7871                 if (first.pr != NoProc) {\r
7872                     SendToProgram(buf, &first);\r
7873                 }\r
7874                 if (second.pr != NoProc &&\r
7875                     gameMode == TwoMachinesPlay) {\r
7876                     SendToProgram(buf, &second);\r
7877                 }\r
7878             }\r
7879         }\r
7880 \r
7881         if (appData.icsActive) {\r
7882             if (appData.quietPlay &&\r
7883                 (gameMode == IcsPlayingWhite ||\r
7884                  gameMode == IcsPlayingBlack)) {\r
7885                 SendToICS(ics_prefix);\r
7886                 SendToICS("set shout 1\n");\r
7887             }\r
7888             nextGameMode = IcsIdle;\r
7889             ics_user_moved = FALSE;\r
7890             /* clean up premove.  It's ugly when the game has ended and the\r
7891              * premove highlights are still on the board.\r
7892              */\r
7893             if (gotPremove) {\r
7894               gotPremove = FALSE;\r
7895               ClearPremoveHighlights();\r
7896               DrawPosition(FALSE, boards[currentMove]);\r
7897             }\r
7898             if (whosays == GE_ICS) {\r
7899                 switch (result) {\r
7900                 case WhiteWins:\r
7901                     if (gameMode == IcsPlayingWhite)\r
7902                         PlayIcsWinSound();\r
7903                     else if(gameMode == IcsPlayingBlack)\r
7904                         PlayIcsLossSound();\r
7905                     break;\r
7906                 case BlackWins:\r
7907                     if (gameMode == IcsPlayingBlack)\r
7908                         PlayIcsWinSound();\r
7909                     else if(gameMode == IcsPlayingWhite)\r
7910                         PlayIcsLossSound();\r
7911                     break;\r
7912                 case GameIsDrawn:\r
7913                     PlayIcsDrawSound();\r
7914                     break;\r
7915                 default:\r
7916                     PlayIcsUnfinishedSound();\r
7917                 }\r
7918             }\r
7919         } else if (gameMode == EditGame ||\r
7920                    gameMode == PlayFromGameFile || \r
7921                    gameMode == AnalyzeMode || \r
7922                    gameMode == AnalyzeFile) {\r
7923             nextGameMode = gameMode;\r
7924         } else {\r
7925             nextGameMode = EndOfGame;\r
7926         }\r
7927         pausing = FALSE;\r
7928         ModeHighlight();\r
7929     } else {\r
7930         nextGameMode = gameMode;\r
7931     }\r
7932 \r
7933     if (appData.noChessProgram) {\r
7934         gameMode = nextGameMode;\r
7935         ModeHighlight();\r
7936         endingGame = 0; /* [HGM] crash */\r
7937         return;\r
7938     }\r
7939 \r
7940     if (first.reuse) {\r
7941         /* Put first chess program into idle state */\r
7942         if (first.pr != NoProc &&\r
7943             (gameMode == MachinePlaysWhite ||\r
7944              gameMode == MachinePlaysBlack ||\r
7945              gameMode == TwoMachinesPlay ||\r
7946              gameMode == IcsPlayingWhite ||\r
7947              gameMode == IcsPlayingBlack ||\r
7948              gameMode == BeginningOfGame)) {\r
7949             SendToProgram("force\n", &first);\r
7950             if (first.usePing) {\r
7951               char buf[MSG_SIZ];\r
7952               sprintf(buf, "ping %d\n", ++first.lastPing);\r
7953               SendToProgram(buf, &first);\r
7954             }\r
7955         }\r
7956     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
7957         /* Kill off first chess program */\r
7958         if (first.isr != NULL)\r
7959           RemoveInputSource(first.isr);\r
7960         first.isr = NULL;\r
7961     \r
7962         if (first.pr != NoProc) {\r
7963             ExitAnalyzeMode();\r
7964             DoSleep( appData.delayBeforeQuit );\r
7965             SendToProgram("quit\n", &first);\r
7966             DoSleep( appData.delayAfterQuit );\r
7967             DestroyChildProcess(first.pr, first.useSigterm);\r
7968         }\r
7969         first.pr = NoProc;\r
7970     }\r
7971     if (second.reuse) {\r
7972         /* Put second chess program into idle state */\r
7973         if (second.pr != NoProc &&\r
7974             gameMode == TwoMachinesPlay) {\r
7975             SendToProgram("force\n", &second);\r
7976             if (second.usePing) {\r
7977               char buf[MSG_SIZ];\r
7978               sprintf(buf, "ping %d\n", ++second.lastPing);\r
7979               SendToProgram(buf, &second);\r
7980             }\r
7981         }\r
7982     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
7983         /* Kill off second chess program */\r
7984         if (second.isr != NULL)\r
7985           RemoveInputSource(second.isr);\r
7986         second.isr = NULL;\r
7987     \r
7988         if (second.pr != NoProc) {\r
7989             DoSleep( appData.delayBeforeQuit );\r
7990             SendToProgram("quit\n", &second);\r
7991             DoSleep( appData.delayAfterQuit );\r
7992             DestroyChildProcess(second.pr, second.useSigterm);\r
7993         }\r
7994         second.pr = NoProc;\r
7995     }\r
7996 \r
7997     if (matchMode && gameMode == TwoMachinesPlay) {\r
7998         switch (result) {\r
7999         case WhiteWins:\r
8000           if (first.twoMachinesColor[0] == 'w') {\r
8001             first.matchWins++;\r
8002           } else {\r
8003             second.matchWins++;\r
8004           }\r
8005           break;\r
8006         case BlackWins:\r
8007           if (first.twoMachinesColor[0] == 'b') {\r
8008             first.matchWins++;\r
8009           } else {\r
8010             second.matchWins++;\r
8011           }\r
8012           break;\r
8013         default:\r
8014           break;\r
8015         }\r
8016         if (matchGame < appData.matchGames) {\r
8017             char *tmp;\r
8018             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */\r
8019                 tmp = first.twoMachinesColor;\r
8020                 first.twoMachinesColor = second.twoMachinesColor;\r
8021                 second.twoMachinesColor = tmp;\r
8022             }\r
8023             gameMode = nextGameMode;\r
8024             matchGame++;\r
8025             if(appData.matchPause>10000 || appData.matchPause<10)\r
8026                 appData.matchPause = 10000; /* [HGM] make pause adjustable */\r
8027             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);\r
8028             endingGame = 0; /* [HGM] crash */\r
8029             return;\r
8030         } else {\r
8031             char buf[MSG_SIZ];\r
8032             gameMode = nextGameMode;\r
8033             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),\r
8034                     first.tidy, second.tidy,\r
8035                     first.matchWins, second.matchWins,\r
8036                     appData.matchGames - (first.matchWins + second.matchWins));\r
8037             DisplayFatalError(buf, 0, 0);\r
8038         }\r
8039     }\r
8040     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&\r
8041         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))\r
8042       ExitAnalyzeMode();\r
8043     gameMode = nextGameMode;\r
8044     ModeHighlight();\r
8045     endingGame = 0;  /* [HGM] crash */\r
8046 }\r
8047 \r
8048 /* Assumes program was just initialized (initString sent).\r
8049    Leaves program in force mode. */\r
8050 void\r
8051 FeedMovesToProgram(cps, upto) \r
8052      ChessProgramState *cps;\r
8053      int upto;\r
8054 {\r
8055     int i;\r
8056     \r
8057     if (appData.debugMode)\r
8058       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",\r
8059               startedFromSetupPosition ? "position and " : "",\r
8060               backwardMostMove, upto, cps->which);\r
8061     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];\r
8062         // [HGM] variantswitch: make engine aware of new variant\r
8063         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)\r
8064                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!\r
8065         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));\r
8066         SendToProgram(buf, cps);\r
8067         currentlyInitializedVariant = gameInfo.variant;\r
8068     }\r
8069     SendToProgram("force\n", cps);\r
8070     if (startedFromSetupPosition) {\r
8071         SendBoard(cps, backwardMostMove);\r
8072     if (appData.debugMode) {\r
8073         fprintf(debugFP, "feedMoves\n");\r
8074     }\r
8075     }\r
8076     for (i = backwardMostMove; i < upto; i++) {\r
8077         SendMoveToProgram(i, cps);\r
8078     }\r
8079 }\r
8080 \r
8081 \r
8082 void\r
8083 ResurrectChessProgram()\r
8084 {\r
8085      /* The chess program may have exited.\r
8086         If so, restart it and feed it all the moves made so far. */\r
8087 \r
8088     if (appData.noChessProgram || first.pr != NoProc) return;\r
8089     \r
8090     StartChessProgram(&first);\r
8091     InitChessProgram(&first, FALSE);\r
8092     FeedMovesToProgram(&first, currentMove);\r
8093 \r
8094     if (!first.sendTime) {\r
8095         /* can't tell gnuchess what its clock should read,\r
8096            so we bow to its notion. */\r
8097         ResetClocks();\r
8098         timeRemaining[0][currentMove] = whiteTimeRemaining;\r
8099         timeRemaining[1][currentMove] = blackTimeRemaining;\r
8100     }\r
8101 \r
8102     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||\r
8103                 appData.icsEngineAnalyze) && first.analysisSupport) {\r
8104       SendToProgram("analyze\n", &first);\r
8105       first.analyzing = TRUE;\r
8106     }\r
8107 }\r
8108 \r
8109 /*\r
8110  * Button procedures\r
8111  */\r
8112 void\r
8113 Reset(redraw, init)\r
8114      int redraw, init;\r
8115 {\r
8116     int i;\r
8117 \r
8118     if (appData.debugMode) {\r
8119         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",\r
8120                 redraw, init, gameMode);\r
8121     }\r
8122     pausing = pauseExamInvalid = FALSE;\r
8123     startedFromSetupPosition = blackPlaysFirst = FALSE;\r
8124     firstMove = TRUE;\r
8125     whiteFlag = blackFlag = FALSE;\r
8126     userOfferedDraw = FALSE;\r
8127     hintRequested = bookRequested = FALSE;\r
8128     first.maybeThinking = FALSE;\r
8129     second.maybeThinking = FALSE;\r
8130     first.bookSuspend = FALSE; // [HGM] book\r
8131     second.bookSuspend = FALSE;\r
8132     thinkOutput[0] = NULLCHAR;\r
8133     lastHint[0] = NULLCHAR;\r
8134     ClearGameInfo(&gameInfo);\r
8135     gameInfo.variant = StringToVariant(appData.variant);\r
8136     ics_user_moved = ics_clock_paused = FALSE;\r
8137     ics_getting_history = H_FALSE;\r
8138     ics_gamenum = -1;\r
8139     white_holding[0] = black_holding[0] = NULLCHAR;\r
8140     ClearProgramStats();\r
8141     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode\r
8142     \r
8143     ResetFrontEnd();\r
8144     ClearHighlights();\r
8145     flipView = appData.flipView;\r
8146     ClearPremoveHighlights();\r
8147     gotPremove = FALSE;\r
8148     alarmSounded = FALSE;\r
8149 \r
8150     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
8151     if(appData.serverMovesName != NULL) {\r
8152         /* [HGM] prepare to make moves file for broadcasting */\r
8153         clock_t t = clock();\r
8154         if(serverMoves != NULL) fclose(serverMoves);\r
8155         serverMoves = fopen(appData.serverMovesName, "r");\r
8156         if(serverMoves != NULL) {\r
8157             fclose(serverMoves);\r
8158             /* delay 15 sec before overwriting, so all clients can see end */\r
8159             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);\r
8160         }\r
8161         serverMoves = fopen(appData.serverMovesName, "w");\r
8162     }\r
8163 \r
8164     ExitAnalyzeMode();\r
8165     gameMode = BeginningOfGame;\r
8166     ModeHighlight();\r
8167     if(appData.icsActive) gameInfo.variant = VariantNormal;\r
8168     InitPosition(redraw);\r
8169     for (i = 0; i < MAX_MOVES; i++) {\r
8170         if (commentList[i] != NULL) {\r
8171             free(commentList[i]);\r
8172             commentList[i] = NULL;\r
8173         }\r
8174     }\r
8175     ResetClocks();\r
8176     timeRemaining[0][0] = whiteTimeRemaining;\r
8177     timeRemaining[1][0] = blackTimeRemaining;\r
8178     if (first.pr == NULL) {\r
8179         StartChessProgram(&first);\r
8180     }\r
8181     if (init) {\r
8182             InitChessProgram(&first, startedFromSetupPosition);\r
8183     }\r
8184     DisplayTitle("");\r
8185     DisplayMessage("", "");\r
8186     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
8187 }\r
8188 \r
8189 void\r
8190 AutoPlayGameLoop()\r
8191 {\r
8192     for (;;) {\r
8193         if (!AutoPlayOneMove())\r
8194           return;\r
8195         if (matchMode || appData.timeDelay == 0)\r
8196           continue;\r
8197         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)\r
8198           return;\r
8199         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));\r
8200         break;\r
8201     }\r
8202 }\r
8203 \r
8204 \r
8205 int\r
8206 AutoPlayOneMove()\r
8207 {\r
8208     int fromX, fromY, toX, toY;\r
8209 \r
8210     if (appData.debugMode) {\r
8211       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);\r
8212     }\r
8213 \r
8214     if (gameMode != PlayFromGameFile)\r
8215       return FALSE;\r
8216 \r
8217     if (currentMove >= forwardMostMove) {\r
8218       gameMode = EditGame;\r
8219       ModeHighlight();\r
8220 \r
8221       /* [AS] Clear current move marker at the end of a game */\r
8222       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */\r
8223 \r
8224       return FALSE;\r
8225     }\r
8226     \r
8227     toX = moveList[currentMove][2] - AAA;\r
8228     toY = moveList[currentMove][3] - ONE;\r
8229 \r
8230     if (moveList[currentMove][1] == '@') {\r
8231         if (appData.highlightLastMove) {\r
8232             SetHighlights(-1, -1, toX, toY);\r
8233         }\r
8234     } else {\r
8235         fromX = moveList[currentMove][0] - AAA;\r
8236         fromY = moveList[currentMove][1] - ONE;\r
8237 \r
8238         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */\r
8239 \r
8240         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
8241 \r
8242         if (appData.highlightLastMove) {\r
8243             SetHighlights(fromX, fromY, toX, toY);\r
8244         }\r
8245     }\r
8246     DisplayMove(currentMove);\r
8247     SendMoveToProgram(currentMove++, &first);\r
8248     DisplayBothClocks();\r
8249     DrawPosition(FALSE, boards[currentMove]);\r
8250     // [HGM] PV info: always display, routine tests if empty\r
8251     DisplayComment(currentMove - 1, commentList[currentMove]);\r
8252     return TRUE;\r
8253 }\r
8254 \r
8255 \r
8256 int\r
8257 LoadGameOneMove(readAhead)\r
8258      ChessMove readAhead;\r
8259 {\r
8260     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;\r
8261     char promoChar = NULLCHAR;\r
8262     ChessMove moveType;\r
8263     char move[MSG_SIZ];\r
8264     char *p, *q;\r
8265     \r
8266     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && \r
8267         gameMode != AnalyzeMode && gameMode != Training) {\r
8268         gameFileFP = NULL;\r
8269         return FALSE;\r
8270     }\r
8271     \r
8272     yyboardindex = forwardMostMove;\r
8273     if (readAhead != (ChessMove)0) {\r
8274       moveType = readAhead;\r
8275     } else {\r
8276       if (gameFileFP == NULL)\r
8277           return FALSE;\r
8278       moveType = (ChessMove) yylex();\r
8279     }\r
8280     \r
8281     done = FALSE;\r
8282     switch (moveType) {\r
8283       case Comment:\r
8284         if (appData.debugMode) \r
8285           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
8286         p = yy_text;\r
8287         if (*p == '{' || *p == '[' || *p == '(') {\r
8288             p[strlen(p) - 1] = NULLCHAR;\r
8289             p++;\r
8290         }\r
8291 \r
8292         /* append the comment but don't display it */\r
8293         while (*p == '\n') p++;\r
8294         AppendComment(currentMove, p);\r
8295         return TRUE;\r
8296 \r
8297       case WhiteCapturesEnPassant:\r
8298       case BlackCapturesEnPassant:\r
8299       case WhitePromotionChancellor:\r
8300       case BlackPromotionChancellor:\r
8301       case WhitePromotionArchbishop:\r
8302       case BlackPromotionArchbishop:\r
8303       case WhitePromotionCentaur:\r
8304       case BlackPromotionCentaur:\r
8305       case WhitePromotionQueen:\r
8306       case BlackPromotionQueen:\r
8307       case WhitePromotionRook:\r
8308       case BlackPromotionRook:\r
8309       case WhitePromotionBishop:\r
8310       case BlackPromotionBishop:\r
8311       case WhitePromotionKnight:\r
8312       case BlackPromotionKnight:\r
8313       case WhitePromotionKing:\r
8314       case BlackPromotionKing:\r
8315       case NormalMove:\r
8316       case WhiteKingSideCastle:\r
8317       case WhiteQueenSideCastle:\r
8318       case BlackKingSideCastle:\r
8319       case BlackQueenSideCastle:\r
8320       case WhiteKingSideCastleWild:\r
8321       case WhiteQueenSideCastleWild:\r
8322       case BlackKingSideCastleWild:\r
8323       case BlackQueenSideCastleWild:\r
8324       /* PUSH Fabien */\r
8325       case WhiteHSideCastleFR:\r
8326       case WhiteASideCastleFR:\r
8327       case BlackHSideCastleFR:\r
8328       case BlackASideCastleFR:\r
8329       /* POP Fabien */\r
8330         if (appData.debugMode)\r
8331           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
8332         fromX = currentMoveString[0] - AAA;\r
8333         fromY = currentMoveString[1] - ONE;\r
8334         toX = currentMoveString[2] - AAA;\r
8335         toY = currentMoveString[3] - ONE;\r
8336         promoChar = currentMoveString[4];\r
8337         break;\r
8338 \r
8339       case WhiteDrop:\r
8340       case BlackDrop:\r
8341         if (appData.debugMode)\r
8342           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
8343         fromX = moveType == WhiteDrop ?\r
8344           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
8345         (int) CharToPiece(ToLower(currentMoveString[0]));\r
8346         fromY = DROP_RANK;\r
8347         toX = currentMoveString[2] - AAA;\r
8348         toY = currentMoveString[3] - ONE;\r
8349         break;\r
8350 \r
8351       case WhiteWins:\r
8352       case BlackWins:\r
8353       case GameIsDrawn:\r
8354       case GameUnfinished:\r
8355         if (appData.debugMode)\r
8356           fprintf(debugFP, "Parsed game end: %s\n", yy_text);\r
8357         p = strchr(yy_text, '{');\r
8358         if (p == NULL) p = strchr(yy_text, '(');\r
8359         if (p == NULL) {\r
8360             p = yy_text;\r
8361             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
8362         } else {\r
8363             q = strchr(p, *p == '{' ? '}' : ')');\r
8364             if (q != NULL) *q = NULLCHAR;\r
8365             p++;\r
8366         }\r
8367         GameEnds(moveType, p, GE_FILE);\r
8368         done = TRUE;\r
8369         if (cmailMsgLoaded) {\r
8370             ClearHighlights();\r
8371             flipView = WhiteOnMove(currentMove);\r
8372             if (moveType == GameUnfinished) flipView = !flipView;\r
8373             if (appData.debugMode)\r
8374               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;\r
8375         }\r
8376         break;\r
8377 \r
8378       case (ChessMove) 0:       /* end of file */\r
8379         if (appData.debugMode)\r
8380           fprintf(debugFP, "Parser hit end of file\n");\r
8381         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8382                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8383           case MT_NONE:\r
8384           case MT_CHECK:\r
8385             break;\r
8386           case MT_CHECKMATE:\r
8387           case MT_STAINMATE:\r
8388             if (WhiteOnMove(currentMove)) {\r
8389                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
8390             } else {\r
8391                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
8392             }\r
8393             break;\r
8394           case MT_STALEMATE:\r
8395             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
8396             break;\r
8397         }\r
8398         done = TRUE;\r
8399         break;\r
8400 \r
8401       case MoveNumberOne:\r
8402         if (lastLoadGameStart == GNUChessGame) {\r
8403             /* GNUChessGames have numbers, but they aren't move numbers */\r
8404             if (appData.debugMode)\r
8405               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
8406                       yy_text, (int) moveType);\r
8407             return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
8408         }\r
8409         /* else fall thru */\r
8410 \r
8411       case XBoardGame:\r
8412       case GNUChessGame:\r
8413       case PGNTag:\r
8414         /* Reached start of next game in file */\r
8415         if (appData.debugMode)\r
8416           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);\r
8417         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8418                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8419           case MT_NONE:\r
8420           case MT_CHECK:\r
8421             break;\r
8422           case MT_CHECKMATE:\r
8423           case MT_STAINMATE:\r
8424             if (WhiteOnMove(currentMove)) {\r
8425                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
8426             } else {\r
8427                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
8428             }\r
8429             break;\r
8430           case MT_STALEMATE:\r
8431             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
8432             break;\r
8433         }\r
8434         done = TRUE;\r
8435         break;\r
8436 \r
8437       case PositionDiagram:     /* should not happen; ignore */\r
8438       case ElapsedTime:         /* ignore */\r
8439       case NAG:                 /* ignore */\r
8440         if (appData.debugMode)\r
8441           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
8442                   yy_text, (int) moveType);\r
8443         return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
8444 \r
8445       case IllegalMove:\r
8446         if (appData.testLegality) {\r
8447             if (appData.debugMode)\r
8448               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);\r
8449             sprintf(move, _("Illegal move: %d.%s%s"),\r
8450                     (forwardMostMove / 2) + 1,\r
8451                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8452             DisplayError(move, 0);\r
8453             done = TRUE;\r
8454         } else {\r
8455             if (appData.debugMode)\r
8456               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",\r
8457                       yy_text, currentMoveString);\r
8458             fromX = currentMoveString[0] - AAA;\r
8459             fromY = currentMoveString[1] - ONE;\r
8460             toX = currentMoveString[2] - AAA;\r
8461             toY = currentMoveString[3] - ONE;\r
8462             promoChar = currentMoveString[4];\r
8463         }\r
8464         break;\r
8465 \r
8466       case AmbiguousMove:\r
8467         if (appData.debugMode)\r
8468           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);\r
8469         sprintf(move, _("Ambiguous move: %d.%s%s"),\r
8470                 (forwardMostMove / 2) + 1,\r
8471                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8472         DisplayError(move, 0);\r
8473         done = TRUE;\r
8474         break;\r
8475 \r
8476       default:\r
8477       case ImpossibleMove:\r
8478         if (appData.debugMode)\r
8479           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);\r
8480         sprintf(move, _("Illegal move: %d.%s%s"),\r
8481                 (forwardMostMove / 2) + 1,\r
8482                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8483         DisplayError(move, 0);\r
8484         done = TRUE;\r
8485         break;\r
8486     }\r
8487 \r
8488     if (done) {\r
8489         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {\r
8490             DrawPosition(FALSE, boards[currentMove]);\r
8491             DisplayBothClocks();\r
8492             if (!appData.matchMode) // [HGM] PV info: routine tests if empty\r
8493               DisplayComment(currentMove - 1, commentList[currentMove]);\r
8494         }\r
8495         (void) StopLoadGameTimer();\r
8496         gameFileFP = NULL;\r
8497         cmailOldMove = forwardMostMove;\r
8498         return FALSE;\r
8499     } else {\r
8500         /* currentMoveString is set as a side-effect of yylex */\r
8501         strcat(currentMoveString, "\n");\r
8502         strcpy(moveList[forwardMostMove], currentMoveString);\r
8503         \r
8504         thinkOutput[0] = NULLCHAR;\r
8505         MakeMove(fromX, fromY, toX, toY, promoChar);\r
8506         currentMove = forwardMostMove;\r
8507         return TRUE;\r
8508     }\r
8509 }\r
8510 \r
8511 /* Load the nth game from the given file */\r
8512 int\r
8513 LoadGameFromFile(filename, n, title, useList)\r
8514      char *filename;\r
8515      int n;\r
8516      char *title;\r
8517      /*Boolean*/ int useList;\r
8518 {\r
8519     FILE *f;\r
8520     char buf[MSG_SIZ];\r
8521 \r
8522     if (strcmp(filename, "-") == 0) {\r
8523         f = stdin;\r
8524         title = "stdin";\r
8525     } else {\r
8526         f = fopen(filename, "rb");\r
8527         if (f == NULL) {\r
8528             sprintf(buf, _("Can't open \"%s\""), filename);\r
8529             DisplayError(buf, errno);\r
8530             return FALSE;\r
8531         }\r
8532     }\r
8533     if (fseek(f, 0, 0) == -1) {\r
8534         /* f is not seekable; probably a pipe */\r
8535         useList = FALSE;\r
8536     }\r
8537     if (useList && n == 0) {\r
8538         int error = GameListBuild(f);\r
8539         if (error) {\r
8540             DisplayError(_("Cannot build game list"), error);\r
8541         } else if (!ListEmpty(&gameList) &&\r
8542                    ((ListGame *) gameList.tailPred)->number > 1) {\r
8543             GameListPopUp(f, title);\r
8544             return TRUE;\r
8545         }\r
8546         GameListDestroy();\r
8547         n = 1;\r
8548     }\r
8549     if (n == 0) n = 1;\r
8550     return LoadGame(f, n, title, FALSE);\r
8551 }\r
8552 \r
8553 \r
8554 void\r
8555 MakeRegisteredMove()\r
8556 {\r
8557     int fromX, fromY, toX, toY;\r
8558     char promoChar;\r
8559     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8560         switch (cmailMoveType[lastLoadGameNumber - 1]) {\r
8561           case CMAIL_MOVE:\r
8562           case CMAIL_DRAW:\r
8563             if (appData.debugMode)\r
8564               fprintf(debugFP, "Restoring %s for game %d\n",\r
8565                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
8566     \r
8567             thinkOutput[0] = NULLCHAR;\r
8568             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);\r
8569             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;\r
8570             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;\r
8571             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;\r
8572             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;\r
8573             promoChar = cmailMove[lastLoadGameNumber - 1][4];\r
8574             MakeMove(fromX, fromY, toX, toY, promoChar);\r
8575             ShowMove(fromX, fromY, toX, toY);\r
8576               \r
8577             switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8578                              EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8579               case MT_NONE:\r
8580               case MT_CHECK:\r
8581                 break;\r
8582                 \r
8583               case MT_CHECKMATE:\r
8584               case MT_STAINMATE:\r
8585                 if (WhiteOnMove(currentMove)) {\r
8586                     GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
8587                 } else {\r
8588                     GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
8589                 }\r
8590                 break;\r
8591                 \r
8592               case MT_STALEMATE:\r
8593                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
8594                 break;\r
8595             }\r
8596 \r
8597             break;\r
8598             \r
8599           case CMAIL_RESIGN:\r
8600             if (WhiteOnMove(currentMove)) {\r
8601                 GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
8602             } else {\r
8603                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
8604             }\r
8605             break;\r
8606             \r
8607           case CMAIL_ACCEPT:\r
8608             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
8609             break;\r
8610               \r
8611           default:\r
8612             break;\r
8613         }\r
8614     }\r
8615 \r
8616     return;\r
8617 }\r
8618 \r
8619 /* Wrapper around LoadGame for use when a Cmail message is loaded */\r
8620 int\r
8621 CmailLoadGame(f, gameNumber, title, useList)\r
8622      FILE *f;\r
8623      int gameNumber;\r
8624      char *title;\r
8625      int useList;\r
8626 {\r
8627     int retVal;\r
8628 \r
8629     if (gameNumber > nCmailGames) {\r
8630         DisplayError(_("No more games in this message"), 0);\r
8631         return FALSE;\r
8632     }\r
8633     if (f == lastLoadGameFP) {\r
8634         int offset = gameNumber - lastLoadGameNumber;\r
8635         if (offset == 0) {\r
8636             cmailMsg[0] = NULLCHAR;\r
8637             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8638                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
8639                 nCmailMovesRegistered--;\r
8640             }\r
8641             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
8642             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {\r
8643                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;\r
8644             }\r
8645         } else {\r
8646             if (! RegisterMove()) return FALSE;\r
8647         }\r
8648     }\r
8649 \r
8650     retVal = LoadGame(f, gameNumber, title, useList);\r
8651 \r
8652     /* Make move registered during previous look at this game, if any */\r
8653     MakeRegisteredMove();\r
8654 \r
8655     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {\r
8656         commentList[currentMove]\r
8657           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);\r
8658         DisplayComment(currentMove - 1, commentList[currentMove]);\r
8659     }\r
8660 \r
8661     return retVal;\r
8662 }\r
8663 \r
8664 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */\r
8665 int\r
8666 ReloadGame(offset)\r
8667      int offset;\r
8668 {\r
8669     int gameNumber = lastLoadGameNumber + offset;\r
8670     if (lastLoadGameFP == NULL) {\r
8671         DisplayError(_("No game has been loaded yet"), 0);\r
8672         return FALSE;\r
8673     }\r
8674     if (gameNumber <= 0) {\r
8675         DisplayError(_("Can't back up any further"), 0);\r
8676         return FALSE;\r
8677     }\r
8678     if (cmailMsgLoaded) {\r
8679         return CmailLoadGame(lastLoadGameFP, gameNumber,\r
8680                              lastLoadGameTitle, lastLoadGameUseList);\r
8681     } else {\r
8682         return LoadGame(lastLoadGameFP, gameNumber,\r
8683                         lastLoadGameTitle, lastLoadGameUseList);\r
8684     }\r
8685 }\r
8686 \r
8687 \r
8688 \r
8689 /* Load the nth game from open file f */\r
8690 int\r
8691 LoadGame(f, gameNumber, title, useList)\r
8692      FILE *f;\r
8693      int gameNumber;\r
8694      char *title;\r
8695      int useList;\r
8696 {\r
8697     ChessMove cm;\r
8698     char buf[MSG_SIZ];\r
8699     int gn = gameNumber;\r
8700     ListGame *lg = NULL;\r
8701     int numPGNTags = 0;\r
8702     int err;\r
8703     GameMode oldGameMode;\r
8704     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */\r
8705 \r
8706     if (appData.debugMode) \r
8707         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);\r
8708 \r
8709     if (gameMode == Training )\r
8710         SetTrainingModeOff();\r
8711 \r
8712     oldGameMode = gameMode;\r
8713     if (gameMode != BeginningOfGame) {\r
8714       Reset(FALSE, TRUE);\r
8715     }\r
8716 \r
8717     gameFileFP = f;\r
8718     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {\r
8719         fclose(lastLoadGameFP);\r
8720     }\r
8721 \r
8722     if (useList) {\r
8723         lg = (ListGame *) ListElem(&gameList, gameNumber-1);\r
8724         \r
8725         if (lg) {\r
8726             fseek(f, lg->offset, 0);\r
8727             GameListHighlight(gameNumber);\r
8728             gn = 1;\r
8729         }\r
8730         else {\r
8731             DisplayError(_("Game number out of range"), 0);\r
8732             return FALSE;\r
8733         }\r
8734     } else {\r
8735         GameListDestroy();\r
8736         if (fseek(f, 0, 0) == -1) {\r
8737             if (f == lastLoadGameFP ?\r
8738                 gameNumber == lastLoadGameNumber + 1 :\r
8739                 gameNumber == 1) {\r
8740                 gn = 1;\r
8741             } else {\r
8742                 DisplayError(_("Can't seek on game file"), 0);\r
8743                 return FALSE;\r
8744             }\r
8745         }\r
8746     }\r
8747     lastLoadGameFP = f;\r
8748     lastLoadGameNumber = gameNumber;\r
8749     strcpy(lastLoadGameTitle, title);\r
8750     lastLoadGameUseList = useList;\r
8751 \r
8752     yynewfile(f);\r
8753 \r
8754     if (lg && lg->gameInfo.white && lg->gameInfo.black) {\r
8755         sprintf(buf, "%s vs. %s", lg->gameInfo.white,\r
8756                 lg->gameInfo.black);\r
8757             DisplayTitle(buf);\r
8758     } else if (*title != NULLCHAR) {\r
8759         if (gameNumber > 1) {\r
8760             sprintf(buf, "%s %d", title, gameNumber);\r
8761             DisplayTitle(buf);\r
8762         } else {\r
8763             DisplayTitle(title);\r
8764         }\r
8765     }\r
8766 \r
8767     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {\r
8768         gameMode = PlayFromGameFile;\r
8769         ModeHighlight();\r
8770     }\r
8771 \r
8772     currentMove = forwardMostMove = backwardMostMove = 0;\r
8773     CopyBoard(boards[0], initialPosition);\r
8774     StopClocks();\r
8775 \r
8776     /*\r
8777      * Skip the first gn-1 games in the file.\r
8778      * Also skip over anything that precedes an identifiable \r
8779      * start of game marker, to avoid being confused by \r
8780      * garbage at the start of the file.  Currently \r
8781      * recognized start of game markers are the move number "1",\r
8782      * the pattern "gnuchess .* game", the pattern\r
8783      * "^[#;%] [^ ]* game file", and a PGN tag block.  \r
8784      * A game that starts with one of the latter two patterns\r
8785      * will also have a move number 1, possibly\r
8786      * following a position diagram.\r
8787      * 5-4-02: Let's try being more lenient and allowing a game to\r
8788      * start with an unnumbered move.  Does that break anything?\r
8789      */\r
8790     cm = lastLoadGameStart = (ChessMove) 0;\r
8791     while (gn > 0) {\r
8792         yyboardindex = forwardMostMove;\r
8793         cm = (ChessMove) yylex();\r
8794         switch (cm) {\r
8795           case (ChessMove) 0:\r
8796             if (cmailMsgLoaded) {\r
8797                 nCmailGames = CMAIL_MAX_GAMES - gn;\r
8798             } else {\r
8799                 Reset(TRUE, TRUE);\r
8800                 DisplayError(_("Game not found in file"), 0);\r
8801             }\r
8802             return FALSE;\r
8803 \r
8804           case GNUChessGame:\r
8805           case XBoardGame:\r
8806             gn--;\r
8807             lastLoadGameStart = cm;\r
8808             break;\r
8809             \r
8810           case MoveNumberOne:\r
8811             switch (lastLoadGameStart) {\r
8812               case GNUChessGame:\r
8813               case XBoardGame:\r
8814               case PGNTag:\r
8815                 break;\r
8816               case MoveNumberOne:\r
8817               case (ChessMove) 0:\r
8818                 gn--;           /* count this game */\r
8819                 lastLoadGameStart = cm;\r
8820                 break;\r
8821               default:\r
8822                 /* impossible */\r
8823                 break;\r
8824             }\r
8825             break;\r
8826 \r
8827           case PGNTag:\r
8828             switch (lastLoadGameStart) {\r
8829               case GNUChessGame:\r
8830               case PGNTag:\r
8831               case MoveNumberOne:\r
8832               case (ChessMove) 0:\r
8833                 gn--;           /* count this game */\r
8834                 lastLoadGameStart = cm;\r
8835                 break;\r
8836               case XBoardGame:\r
8837                 lastLoadGameStart = cm; /* game counted already */\r
8838                 break;\r
8839               default:\r
8840                 /* impossible */\r
8841                 break;\r
8842             }\r
8843             if (gn > 0) {\r
8844                 do {\r
8845                     yyboardindex = forwardMostMove;\r
8846                     cm = (ChessMove) yylex();\r
8847                 } while (cm == PGNTag || cm == Comment);\r
8848             }\r
8849             break;\r
8850 \r
8851           case WhiteWins:\r
8852           case BlackWins:\r
8853           case GameIsDrawn:\r
8854             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {\r
8855                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]\r
8856                     != CMAIL_OLD_RESULT) {\r
8857                     nCmailResults ++ ;\r
8858                     cmailResult[  CMAIL_MAX_GAMES\r
8859                                 - gn - 1] = CMAIL_OLD_RESULT;\r
8860                 }\r
8861             }\r
8862             break;\r
8863 \r
8864           case NormalMove:\r
8865             /* Only a NormalMove can be at the start of a game\r
8866              * without a position diagram. */\r
8867             if (lastLoadGameStart == (ChessMove) 0) {\r
8868               gn--;\r
8869               lastLoadGameStart = MoveNumberOne;\r
8870             }\r
8871             break;\r
8872 \r
8873           default:\r
8874             break;\r
8875         }\r
8876     }\r
8877     \r
8878     if (appData.debugMode)\r
8879       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);\r
8880 \r
8881     if (cm == XBoardGame) {\r
8882         /* Skip any header junk before position diagram and/or move 1 */\r
8883         for (;;) {\r
8884             yyboardindex = forwardMostMove;\r
8885             cm = (ChessMove) yylex();\r
8886 \r
8887             if (cm == (ChessMove) 0 ||\r
8888                 cm == GNUChessGame || cm == XBoardGame) {\r
8889                 /* Empty game; pretend end-of-file and handle later */\r
8890                 cm = (ChessMove) 0;\r
8891                 break;\r
8892             }\r
8893 \r
8894             if (cm == MoveNumberOne || cm == PositionDiagram ||\r
8895                 cm == PGNTag || cm == Comment)\r
8896               break;\r
8897         }\r
8898     } else if (cm == GNUChessGame) {\r
8899         if (gameInfo.event != NULL) {\r
8900             free(gameInfo.event);\r
8901         }\r
8902         gameInfo.event = StrSave(yy_text);\r
8903     }   \r
8904 \r
8905     startedFromSetupPosition = FALSE;\r
8906     while (cm == PGNTag) {\r
8907         if (appData.debugMode) \r
8908           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);\r
8909         err = ParsePGNTag(yy_text, &gameInfo);\r
8910         if (!err) numPGNTags++;\r
8911 \r
8912         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */\r
8913         if(gameInfo.variant != oldVariant) {\r
8914             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */\r
8915             InitPosition(TRUE);\r
8916             oldVariant = gameInfo.variant;\r
8917             if (appData.debugMode) \r
8918               fprintf(debugFP, "New variant %d\n", (int) oldVariant);\r
8919         }\r
8920 \r
8921 \r
8922         if (gameInfo.fen != NULL) {\r
8923           Board initial_position;\r
8924           startedFromSetupPosition = TRUE;\r
8925           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {\r
8926             Reset(TRUE, TRUE);\r
8927             DisplayError(_("Bad FEN position in file"), 0);\r
8928             return FALSE;\r
8929           }\r
8930           CopyBoard(boards[0], initial_position);\r
8931           if (blackPlaysFirst) {\r
8932             currentMove = forwardMostMove = backwardMostMove = 1;\r
8933             CopyBoard(boards[1], initial_position);\r
8934             strcpy(moveList[0], "");\r
8935             strcpy(parseList[0], "");\r
8936             timeRemaining[0][1] = whiteTimeRemaining;\r
8937             timeRemaining[1][1] = blackTimeRemaining;\r
8938             if (commentList[0] != NULL) {\r
8939               commentList[1] = commentList[0];\r
8940               commentList[0] = NULL;\r
8941             }\r
8942           } else {\r
8943             currentMove = forwardMostMove = backwardMostMove = 0;\r
8944           }\r
8945           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */\r
8946           {   int i;\r
8947               initialRulePlies = FENrulePlies;\r
8948               epStatus[forwardMostMove] = FENepStatus;\r
8949               for( i=0; i< nrCastlingRights; i++ )\r
8950                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];\r
8951           }\r
8952           yyboardindex = forwardMostMove;\r
8953           free(gameInfo.fen);\r
8954           gameInfo.fen = NULL;\r
8955         }\r
8956 \r
8957         yyboardindex = forwardMostMove;\r
8958         cm = (ChessMove) yylex();\r
8959 \r
8960         /* Handle comments interspersed among the tags */\r
8961         while (cm == Comment) {\r
8962             char *p;\r
8963             if (appData.debugMode) \r
8964               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
8965             p = yy_text;\r
8966             if (*p == '{' || *p == '[' || *p == '(') {\r
8967                 p[strlen(p) - 1] = NULLCHAR;\r
8968                 p++;\r
8969             }\r
8970             while (*p == '\n') p++;\r
8971             AppendComment(currentMove, p);\r
8972             yyboardindex = forwardMostMove;\r
8973             cm = (ChessMove) yylex();\r
8974         }\r
8975     }\r
8976 \r
8977     /* don't rely on existence of Event tag since if game was\r
8978      * pasted from clipboard the Event tag may not exist\r
8979      */\r
8980     if (numPGNTags > 0){\r
8981         char *tags;\r
8982         if (gameInfo.variant == VariantNormal) {\r
8983           gameInfo.variant = StringToVariant(gameInfo.event);\r
8984         }\r
8985         if (!matchMode) {\r
8986           if( appData.autoDisplayTags ) {\r
8987             tags = PGNTags(&gameInfo);\r
8988             TagsPopUp(tags, CmailMsg());\r
8989             free(tags);\r
8990           }\r
8991         }\r
8992     } else {\r
8993         /* Make something up, but don't display it now */\r
8994         SetGameInfo();\r
8995         TagsPopDown();\r
8996     }\r
8997 \r
8998     if (cm == PositionDiagram) {\r
8999         int i, j;\r
9000         char *p;\r
9001         Board initial_position;\r
9002 \r
9003         if (appData.debugMode)\r
9004           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);\r
9005 \r
9006         if (!startedFromSetupPosition) {\r
9007             p = yy_text;\r
9008             for (i = BOARD_HEIGHT - 1; i >= 0; i--)\r
9009               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)\r
9010                 switch (*p) {\r
9011                   case '[':\r
9012                   case '-':\r
9013                   case ' ':\r
9014                   case '\t':\r
9015                   case '\n':\r
9016                   case '\r':\r
9017                     break;\r
9018                   default:\r
9019                     initial_position[i][j++] = CharToPiece(*p);\r
9020                     break;\r
9021                 }\r
9022             while (*p == ' ' || *p == '\t' ||\r
9023                    *p == '\n' || *p == '\r') p++;\r
9024         \r
9025             if (strncmp(p, "black", strlen("black"))==0)\r
9026               blackPlaysFirst = TRUE;\r
9027             else\r
9028               blackPlaysFirst = FALSE;\r
9029             startedFromSetupPosition = TRUE;\r
9030         \r
9031             CopyBoard(boards[0], initial_position);\r
9032             if (blackPlaysFirst) {\r
9033                 currentMove = forwardMostMove = backwardMostMove = 1;\r
9034                 CopyBoard(boards[1], initial_position);\r
9035                 strcpy(moveList[0], "");\r
9036                 strcpy(parseList[0], "");\r
9037                 timeRemaining[0][1] = whiteTimeRemaining;\r
9038                 timeRemaining[1][1] = blackTimeRemaining;\r
9039                 if (commentList[0] != NULL) {\r
9040                     commentList[1] = commentList[0];\r
9041                     commentList[0] = NULL;\r
9042                 }\r
9043             } else {\r
9044                 currentMove = forwardMostMove = backwardMostMove = 0;\r
9045             }\r
9046         }\r
9047         yyboardindex = forwardMostMove;\r
9048         cm = (ChessMove) yylex();\r
9049     }\r
9050 \r
9051     if (first.pr == NoProc) {\r
9052         StartChessProgram(&first);\r
9053     }\r
9054     InitChessProgram(&first, FALSE);\r
9055     SendToProgram("force\n", &first);\r
9056     if (startedFromSetupPosition) {\r
9057         SendBoard(&first, forwardMostMove);\r
9058     if (appData.debugMode) {\r
9059         fprintf(debugFP, "Load Game\n");\r
9060     }\r
9061         DisplayBothClocks();\r
9062     }      \r
9063 \r
9064     /* [HGM] server: flag to write setup moves in broadcast file as one */\r
9065     loadFlag = appData.suppressLoadMoves;\r
9066 \r
9067     while (cm == Comment) {\r
9068         char *p;\r
9069         if (appData.debugMode) \r
9070           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
9071         p = yy_text;\r
9072         if (*p == '{' || *p == '[' || *p == '(') {\r
9073             p[strlen(p) - 1] = NULLCHAR;\r
9074             p++;\r
9075         }\r
9076         while (*p == '\n') p++;\r
9077         AppendComment(currentMove, p);\r
9078         yyboardindex = forwardMostMove;\r
9079         cm = (ChessMove) yylex();\r
9080     }\r
9081 \r
9082     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||\r
9083         cm == WhiteWins || cm == BlackWins ||\r
9084         cm == GameIsDrawn || cm == GameUnfinished) {\r
9085         DisplayMessage("", _("No moves in game"));\r
9086         if (cmailMsgLoaded) {\r
9087             if (appData.debugMode)\r
9088               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);\r
9089             ClearHighlights();\r
9090             flipView = FALSE;\r
9091         }\r
9092         DrawPosition(FALSE, boards[currentMove]);\r
9093         DisplayBothClocks();\r
9094         gameMode = EditGame;\r
9095         ModeHighlight();\r
9096         gameFileFP = NULL;\r
9097         cmailOldMove = 0;\r
9098         return TRUE;\r
9099     }\r
9100 \r
9101     // [HGM] PV info: routine tests if comment empty\r
9102     if (!matchMode && (pausing || appData.timeDelay != 0)) {\r
9103         DisplayComment(currentMove - 1, commentList[currentMove]);\r
9104     }\r
9105     if (!matchMode && appData.timeDelay != 0) \r
9106       DrawPosition(FALSE, boards[currentMove]);\r
9107 \r
9108     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {\r
9109       programStats.ok_to_send = 1;\r
9110     }\r
9111 \r
9112     /* if the first token after the PGN tags is a move\r
9113      * and not move number 1, retrieve it from the parser \r
9114      */\r
9115     if (cm != MoveNumberOne)\r
9116         LoadGameOneMove(cm);\r
9117 \r
9118     /* load the remaining moves from the file */\r
9119     while (LoadGameOneMove((ChessMove)0)) {\r
9120       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
9121       timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
9122     }\r
9123 \r
9124     /* rewind to the start of the game */\r
9125     currentMove = backwardMostMove;\r
9126 \r
9127     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
9128 \r
9129     if (oldGameMode == AnalyzeFile ||\r
9130         oldGameMode == AnalyzeMode) {\r
9131       AnalyzeFileEvent();\r
9132     }\r
9133 \r
9134     if (matchMode || appData.timeDelay == 0) {\r
9135       ToEndEvent();\r
9136       gameMode = EditGame;\r
9137       ModeHighlight();\r
9138     } else if (appData.timeDelay > 0) {\r
9139       AutoPlayGameLoop();\r
9140     }\r
9141 \r
9142     if (appData.debugMode) \r
9143         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);\r
9144 \r
9145     loadFlag = 0; /* [HGM] true game starts */\r
9146     return TRUE;\r
9147 }\r
9148 \r
9149 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */\r
9150 int\r
9151 ReloadPosition(offset)\r
9152      int offset;\r
9153 {\r
9154     int positionNumber = lastLoadPositionNumber + offset;\r
9155     if (lastLoadPositionFP == NULL) {\r
9156         DisplayError(_("No position has been loaded yet"), 0);\r
9157         return FALSE;\r
9158     }\r
9159     if (positionNumber <= 0) {\r
9160         DisplayError(_("Can't back up any further"), 0);\r
9161         return FALSE;\r
9162     }\r
9163     return LoadPosition(lastLoadPositionFP, positionNumber,\r
9164                         lastLoadPositionTitle);\r
9165 }\r
9166 \r
9167 /* Load the nth position from the given file */\r
9168 int\r
9169 LoadPositionFromFile(filename, n, title)\r
9170      char *filename;\r
9171      int n;\r
9172      char *title;\r
9173 {\r
9174     FILE *f;\r
9175     char buf[MSG_SIZ];\r
9176 \r
9177     if (strcmp(filename, "-") == 0) {\r
9178         return LoadPosition(stdin, n, "stdin");\r
9179     } else {\r
9180         f = fopen(filename, "rb");\r
9181         if (f == NULL) {\r
9182             sprintf(buf, _("Can't open \"%s\""), filename);\r
9183             DisplayError(buf, errno);\r
9184             return FALSE;\r
9185         } else {\r
9186             return LoadPosition(f, n, title);\r
9187         }\r
9188     }\r
9189 }\r
9190 \r
9191 /* Load the nth position from the given open file, and close it */\r
9192 int\r
9193 LoadPosition(f, positionNumber, title)\r
9194      FILE *f;\r
9195      int positionNumber;\r
9196      char *title;\r
9197 {\r
9198     char *p, line[MSG_SIZ];\r
9199     Board initial_position;\r
9200     int i, j, fenMode, pn;\r
9201     \r
9202     if (gameMode == Training )\r
9203         SetTrainingModeOff();\r
9204 \r
9205     if (gameMode != BeginningOfGame) {\r
9206         Reset(FALSE, TRUE);\r
9207     }\r
9208     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {\r
9209         fclose(lastLoadPositionFP);\r
9210     }\r
9211     if (positionNumber == 0) positionNumber = 1;\r
9212     lastLoadPositionFP = f;\r
9213     lastLoadPositionNumber = positionNumber;\r
9214     strcpy(lastLoadPositionTitle, title);\r
9215     if (first.pr == NoProc) {\r
9216       StartChessProgram(&first);\r
9217       InitChessProgram(&first, FALSE);\r
9218     }    \r
9219     pn = positionNumber;\r
9220     if (positionNumber < 0) {\r
9221         /* Negative position number means to seek to that byte offset */\r
9222         if (fseek(f, -positionNumber, 0) == -1) {\r
9223             DisplayError(_("Can't seek on position file"), 0);\r
9224             return FALSE;\r
9225         };\r
9226         pn = 1;\r
9227     } else {\r
9228         if (fseek(f, 0, 0) == -1) {\r
9229             if (f == lastLoadPositionFP ?\r
9230                 positionNumber == lastLoadPositionNumber + 1 :\r
9231                 positionNumber == 1) {\r
9232                 pn = 1;\r
9233             } else {\r
9234                 DisplayError(_("Can't seek on position file"), 0);\r
9235                 return FALSE;\r
9236             }\r
9237         }\r
9238     }\r
9239     /* See if this file is FEN or old-style xboard */\r
9240     if (fgets(line, MSG_SIZ, f) == NULL) {\r
9241         DisplayError(_("Position not found in file"), 0);\r
9242         return FALSE;\r
9243     }\r
9244 #if 0\r
9245     switch (line[0]) {\r
9246       case '#':  case 'x':\r
9247       default:\r
9248         fenMode = FALSE;\r
9249         break;\r
9250       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':\r
9251       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':\r
9252       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':\r
9253       case '7':  case '8':  case '9':\r
9254       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':\r
9255       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':\r
9256       case 'C':  case 'W':             case 'c':  case 'w': \r
9257         fenMode = TRUE;\r
9258         break;\r
9259     }\r
9260 #else\r
9261     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces\r
9262     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;\r
9263 #endif\r
9264 \r
9265     if (pn >= 2) {\r
9266         if (fenMode || line[0] == '#') pn--;\r
9267         while (pn > 0) {\r
9268             /* skip positions before number pn */\r
9269             if (fgets(line, MSG_SIZ, f) == NULL) {\r
9270                 Reset(TRUE, TRUE);\r
9271                 DisplayError(_("Position not found in file"), 0);\r
9272                 return FALSE;\r
9273             }\r
9274             if (fenMode || line[0] == '#') pn--;\r
9275         }\r
9276     }\r
9277 \r
9278     if (fenMode) {\r
9279         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {\r
9280             DisplayError(_("Bad FEN position in file"), 0);\r
9281             return FALSE;\r
9282         }\r
9283     } else {\r
9284         (void) fgets(line, MSG_SIZ, f);\r
9285         (void) fgets(line, MSG_SIZ, f);\r
9286     \r
9287         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
9288             (void) fgets(line, MSG_SIZ, f);\r
9289             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {\r
9290                 if (*p == ' ')\r
9291                   continue;\r
9292                 initial_position[i][j++] = CharToPiece(*p);\r
9293             }\r
9294         }\r
9295     \r
9296         blackPlaysFirst = FALSE;\r
9297         if (!feof(f)) {\r
9298             (void) fgets(line, MSG_SIZ, f);\r
9299             if (strncmp(line, "black", strlen("black"))==0)\r
9300               blackPlaysFirst = TRUE;\r
9301         }\r
9302     }\r
9303     startedFromSetupPosition = TRUE;\r
9304     \r
9305     SendToProgram("force\n", &first);\r
9306     CopyBoard(boards[0], initial_position);\r
9307     if (blackPlaysFirst) {\r
9308         currentMove = forwardMostMove = backwardMostMove = 1;\r
9309         strcpy(moveList[0], "");\r
9310         strcpy(parseList[0], "");\r
9311         CopyBoard(boards[1], initial_position);\r
9312         DisplayMessage("", _("Black to play"));\r
9313     } else {\r
9314         currentMove = forwardMostMove = backwardMostMove = 0;\r
9315         DisplayMessage("", _("White to play"));\r
9316     }\r
9317           /* [HGM] copy FEN attributes as well */\r
9318           {   int i;\r
9319               initialRulePlies = FENrulePlies;\r
9320               epStatus[forwardMostMove] = FENepStatus;\r
9321               for( i=0; i< nrCastlingRights; i++ )\r
9322                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];\r
9323           }\r
9324     SendBoard(&first, forwardMostMove);\r
9325     if (appData.debugMode) {\r
9326 int i, j;\r
9327   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}\r
9328   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");\r
9329         fprintf(debugFP, "Load Position\n");\r
9330     }\r
9331 \r
9332     if (positionNumber > 1) {\r
9333         sprintf(line, "%s %d", title, positionNumber);\r
9334         DisplayTitle(line);\r
9335     } else {\r
9336         DisplayTitle(title);\r
9337     }\r
9338     gameMode = EditGame;\r
9339     ModeHighlight();\r
9340     ResetClocks();\r
9341     timeRemaining[0][1] = whiteTimeRemaining;\r
9342     timeRemaining[1][1] = blackTimeRemaining;\r
9343     DrawPosition(FALSE, boards[currentMove]);\r
9344    \r
9345     return TRUE;\r
9346 }\r
9347 \r
9348 \r
9349 void\r
9350 CopyPlayerNameIntoFileName(dest, src)\r
9351      char **dest, *src;\r
9352 {\r
9353     while (*src != NULLCHAR && *src != ',') {\r
9354         if (*src == ' ') {\r
9355             *(*dest)++ = '_';\r
9356             src++;\r
9357         } else {\r
9358             *(*dest)++ = *src++;\r
9359         }\r
9360     }\r
9361 }\r
9362 \r
9363 char *DefaultFileName(ext)\r
9364      char *ext;\r
9365 {\r
9366     static char def[MSG_SIZ];\r
9367     char *p;\r
9368 \r
9369     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {\r
9370         p = def;\r
9371         CopyPlayerNameIntoFileName(&p, gameInfo.white);\r
9372         *p++ = '-';\r
9373         CopyPlayerNameIntoFileName(&p, gameInfo.black);\r
9374         *p++ = '.';\r
9375         strcpy(p, ext);\r
9376     } else {\r
9377         def[0] = NULLCHAR;\r
9378     }\r
9379     return def;\r
9380 }\r
9381 \r
9382 /* Save the current game to the given file */\r
9383 int\r
9384 SaveGameToFile(filename, append)\r
9385      char *filename;\r
9386      int append;\r
9387 {\r
9388     FILE *f;\r
9389     char buf[MSG_SIZ];\r
9390 \r
9391     if (strcmp(filename, "-") == 0) {\r
9392         return SaveGame(stdout, 0, NULL);\r
9393     } else {\r
9394         f = fopen(filename, append ? "a" : "w");\r
9395         if (f == NULL) {\r
9396             sprintf(buf, _("Can't open \"%s\""), filename);\r
9397             DisplayError(buf, errno);\r
9398             return FALSE;\r
9399         } else {\r
9400             return SaveGame(f, 0, NULL);\r
9401         }\r
9402     }\r
9403 }\r
9404 \r
9405 char *\r
9406 SavePart(str)\r
9407      char *str;\r
9408 {\r
9409     static char buf[MSG_SIZ];\r
9410     char *p;\r
9411     \r
9412     p = strchr(str, ' ');\r
9413     if (p == NULL) return str;\r
9414     strncpy(buf, str, p - str);\r
9415     buf[p - str] = NULLCHAR;\r
9416     return buf;\r
9417 }\r
9418 \r
9419 #define PGN_MAX_LINE 75\r
9420 \r
9421 #define PGN_SIDE_WHITE  0\r
9422 #define PGN_SIDE_BLACK  1\r
9423 \r
9424 /* [AS] */\r
9425 static int FindFirstMoveOutOfBook( int side )\r
9426 {\r
9427     int result = -1;\r
9428 \r
9429     if( backwardMostMove == 0 && ! startedFromSetupPosition) {\r
9430         int index = backwardMostMove;\r
9431         int has_book_hit = 0;\r
9432 \r
9433         if( (index % 2) != side ) {\r
9434             index++;\r
9435         }\r
9436 \r
9437         while( index < forwardMostMove ) {\r
9438             /* Check to see if engine is in book */\r
9439             int depth = pvInfoList[index].depth;\r
9440             int score = pvInfoList[index].score;\r
9441             int in_book = 0;\r
9442 \r
9443             if( depth <= 2 ) {\r
9444                 in_book = 1;\r
9445             }\r
9446             else if( score == 0 && depth == 63 ) {\r
9447                 in_book = 1; /* Zappa */\r
9448             }\r
9449             else if( score == 2 && depth == 99 ) {\r
9450                 in_book = 1; /* Abrok */\r
9451             }\r
9452 \r
9453             has_book_hit += in_book;\r
9454 \r
9455             if( ! in_book ) {\r
9456                 result = index;\r
9457 \r
9458                 break;\r
9459             }\r
9460 \r
9461             index += 2;\r
9462         }\r
9463     }\r
9464 \r
9465     return result;\r
9466 }\r
9467 \r
9468 /* [AS] */\r
9469 void GetOutOfBookInfo( char * buf )\r
9470 {\r
9471     int oob[2];\r
9472     int i;\r
9473     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9474 \r
9475     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );\r
9476     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );\r
9477 \r
9478     *buf = '\0';\r
9479 \r
9480     if( oob[0] >= 0 || oob[1] >= 0 ) {\r
9481         for( i=0; i<2; i++ ) {\r
9482             int idx = oob[i];\r
9483 \r
9484             if( idx >= 0 ) {\r
9485                 if( i > 0 && oob[0] >= 0 ) {\r
9486                     strcat( buf, "   " );\r
9487                 }\r
9488 \r
9489                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );\r
9490                 sprintf( buf+strlen(buf), "%s%.2f", \r
9491                     pvInfoList[idx].score >= 0 ? "+" : "",\r
9492                     pvInfoList[idx].score / 100.0 );\r
9493             }\r
9494         }\r
9495     }\r
9496 }\r
9497 \r
9498 /* Save game in PGN style and close the file */\r
9499 int\r
9500 SaveGamePGN(f)\r
9501      FILE *f;\r
9502 {\r
9503     int i, offset, linelen, newblock;\r
9504     time_t tm;\r
9505 //    char *movetext;\r
9506     char numtext[32];\r
9507     int movelen, numlen, blank;\r
9508     char move_buffer[100]; /* [AS] Buffer for move+PV info */\r
9509 \r
9510     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9511     \r
9512     tm = time((time_t *) NULL);\r
9513     \r
9514     PrintPGNTags(f, &gameInfo);\r
9515     \r
9516     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
9517         char *fen = PositionToFEN(backwardMostMove, NULL);\r
9518         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);\r
9519         fprintf(f, "\n{--------------\n");\r
9520         PrintPosition(f, backwardMostMove);\r
9521         fprintf(f, "--------------}\n");\r
9522         free(fen);\r
9523     }\r
9524     else {\r
9525         /* [AS] Out of book annotation */\r
9526         if( appData.saveOutOfBookInfo ) {\r
9527             char buf[64];\r
9528 \r
9529             GetOutOfBookInfo( buf );\r
9530 \r
9531             if( buf[0] != '\0' ) {\r
9532                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); \r
9533             }\r
9534         }\r
9535 \r
9536         fprintf(f, "\n");\r
9537     }\r
9538 \r
9539     i = backwardMostMove;\r
9540     linelen = 0;\r
9541     newblock = TRUE;\r
9542 \r
9543     while (i < forwardMostMove) {\r
9544         /* Print comments preceding this move */\r
9545         if (commentList[i] != NULL) {\r
9546             if (linelen > 0) fprintf(f, "\n");\r
9547             fprintf(f, "{\n%s}\n", commentList[i]);\r
9548             linelen = 0;\r
9549             newblock = TRUE;\r
9550         }\r
9551 \r
9552         /* Format move number */\r
9553         if ((i % 2) == 0) {\r
9554             sprintf(numtext, "%d.", (i - offset)/2 + 1);\r
9555         } else {\r
9556             if (newblock) {\r
9557                 sprintf(numtext, "%d...", (i - offset)/2 + 1);\r
9558             } else {\r
9559                 numtext[0] = NULLCHAR;\r
9560             }\r
9561         }\r
9562         numlen = strlen(numtext);\r
9563         newblock = FALSE;\r
9564 \r
9565         /* Print move number */\r
9566         blank = linelen > 0 && numlen > 0;\r
9567         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {\r
9568             fprintf(f, "\n");\r
9569             linelen = 0;\r
9570             blank = 0;\r
9571         }\r
9572         if (blank) {\r
9573             fprintf(f, " ");\r
9574             linelen++;\r
9575         }\r
9576         fprintf(f, numtext);\r
9577         linelen += numlen;\r
9578 \r
9579         /* Get move */\r
9580         strcpy(move_buffer, parseList[i]); // [HGM] pgn: print move via buffer, so it can be edited\r
9581         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */\r
9582         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {\r
9583                 int p = movelen - 1;\r
9584                 if(move_buffer[p] == ' ') p--;\r
9585                 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info\r
9586                     while(p && move_buffer[--p] != '(');\r
9587                     if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;\r
9588                 }\r
9589         }\r
9590 \r
9591         /* Print move */\r
9592         blank = linelen > 0 && movelen > 0;\r
9593         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
9594             fprintf(f, "\n");\r
9595             linelen = 0;\r
9596             blank = 0;\r
9597         }\r
9598         if (blank) {\r
9599             fprintf(f, " ");\r
9600             linelen++;\r
9601         }\r
9602         fprintf(f, move_buffer);\r
9603         linelen += movelen;\r
9604 \r
9605         /* [AS] Add PV info if present */\r
9606         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {\r
9607             /* [HGM] add time */\r
9608             char buf[MSG_SIZ]; int seconds = 0;\r
9609 \r
9610 #if 1\r
9611             if(i >= backwardMostMove) {\r
9612                 if(WhiteOnMove(i))\r
9613                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]\r
9614                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);\r
9615                 else\r
9616                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]\r
9617                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);\r
9618             }\r
9619             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest\r
9620 #else\r
9621             seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time\r
9622 #endif\r
9623 \r
9624             if( seconds <= 0) buf[0] = 0; else\r
9625             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {\r
9626                 seconds = (seconds + 4)/10; // round to full seconds\r
9627                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else\r
9628                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);\r
9629             }\r
9630 \r
9631             sprintf( move_buffer, "{%s%.2f/%d%s}", \r
9632                 pvInfoList[i].score >= 0 ? "+" : "",\r
9633                 pvInfoList[i].score / 100.0,\r
9634                 pvInfoList[i].depth,\r
9635                 buf );\r
9636 \r
9637             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */\r
9638 \r
9639             /* Print score/depth */\r
9640             blank = linelen > 0 && movelen > 0;\r
9641             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
9642                 fprintf(f, "\n");\r
9643                 linelen = 0;\r
9644                 blank = 0;\r
9645             }\r
9646             if (blank) {\r
9647                 fprintf(f, " ");\r
9648                 linelen++;\r
9649             }\r
9650             fprintf(f, move_buffer);\r
9651             linelen += movelen;\r
9652         }\r
9653 \r
9654         i++;\r
9655     }\r
9656     \r
9657     /* Start a new line */\r
9658     if (linelen > 0) fprintf(f, "\n");\r
9659 \r
9660     /* Print comments after last move */\r
9661     if (commentList[i] != NULL) {\r
9662         fprintf(f, "{\n%s}\n", commentList[i]);\r
9663     }\r
9664 \r
9665     /* Print result */\r
9666     if (gameInfo.resultDetails != NULL &&\r
9667         gameInfo.resultDetails[0] != NULLCHAR) {\r
9668         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,\r
9669                 PGNResult(gameInfo.result));\r
9670     } else {\r
9671         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
9672     }\r
9673 \r
9674     fclose(f);\r
9675     return TRUE;\r
9676 }\r
9677 \r
9678 /* Save game in old style and close the file */\r
9679 int\r
9680 SaveGameOldStyle(f)\r
9681      FILE *f;\r
9682 {\r
9683     int i, offset;\r
9684     time_t tm;\r
9685     \r
9686     tm = time((time_t *) NULL);\r
9687     \r
9688     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));\r
9689     PrintOpponents(f);\r
9690     \r
9691     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
9692         fprintf(f, "\n[--------------\n");\r
9693         PrintPosition(f, backwardMostMove);\r
9694         fprintf(f, "--------------]\n");\r
9695     } else {\r
9696         fprintf(f, "\n");\r
9697     }\r
9698 \r
9699     i = backwardMostMove;\r
9700     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9701 \r
9702     while (i < forwardMostMove) {\r
9703         if (commentList[i] != NULL) {\r
9704             fprintf(f, "[%s]\n", commentList[i]);\r
9705         }\r
9706 \r
9707         if ((i % 2) == 1) {\r
9708             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);\r
9709             i++;\r
9710         } else {\r
9711             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);\r
9712             i++;\r
9713             if (commentList[i] != NULL) {\r
9714                 fprintf(f, "\n");\r
9715                 continue;\r
9716             }\r
9717             if (i >= forwardMostMove) {\r
9718                 fprintf(f, "\n");\r
9719                 break;\r
9720             }\r
9721             fprintf(f, "%s\n", parseList[i]);\r
9722             i++;\r
9723         }\r
9724     }\r
9725     \r
9726     if (commentList[i] != NULL) {\r
9727         fprintf(f, "[%s]\n", commentList[i]);\r
9728     }\r
9729 \r
9730     /* This isn't really the old style, but it's close enough */\r
9731     if (gameInfo.resultDetails != NULL &&\r
9732         gameInfo.resultDetails[0] != NULLCHAR) {\r
9733         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),\r
9734                 gameInfo.resultDetails);\r
9735     } else {\r
9736         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
9737     }\r
9738 \r
9739     fclose(f);\r
9740     return TRUE;\r
9741 }\r
9742 \r
9743 /* Save the current game to open file f and close the file */\r
9744 int\r
9745 SaveGame(f, dummy, dummy2)\r
9746      FILE *f;\r
9747      int dummy;\r
9748      char *dummy2;\r
9749 {\r
9750     if (gameMode == EditPosition) EditPositionDone();\r
9751     if (appData.oldSaveStyle)\r
9752       return SaveGameOldStyle(f);\r
9753     else\r
9754       return SaveGamePGN(f);\r
9755 }\r
9756 \r
9757 /* Save the current position to the given file */\r
9758 int\r
9759 SavePositionToFile(filename)\r
9760      char *filename;\r
9761 {\r
9762     FILE *f;\r
9763     char buf[MSG_SIZ];\r
9764 \r
9765     if (strcmp(filename, "-") == 0) {\r
9766         return SavePosition(stdout, 0, NULL);\r
9767     } else {\r
9768         f = fopen(filename, "a");\r
9769         if (f == NULL) {\r
9770             sprintf(buf, _("Can't open \"%s\""), filename);\r
9771             DisplayError(buf, errno);\r
9772             return FALSE;\r
9773         } else {\r
9774             SavePosition(f, 0, NULL);\r
9775             return TRUE;\r
9776         }\r
9777     }\r
9778 }\r
9779 \r
9780 /* Save the current position to the given open file and close the file */\r
9781 int\r
9782 SavePosition(f, dummy, dummy2)\r
9783      FILE *f;\r
9784      int dummy;\r
9785      char *dummy2;\r
9786 {\r
9787     time_t tm;\r
9788     char *fen;\r
9789     \r
9790     if (appData.oldSaveStyle) {\r
9791         tm = time((time_t *) NULL);\r
9792     \r
9793         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));\r
9794         PrintOpponents(f);\r
9795         fprintf(f, "[--------------\n");\r
9796         PrintPosition(f, currentMove);\r
9797         fprintf(f, "--------------]\n");\r
9798     } else {\r
9799         fen = PositionToFEN(currentMove, NULL);\r
9800         fprintf(f, "%s\n", fen);\r
9801         free(fen);\r
9802     }\r
9803     fclose(f);\r
9804     return TRUE;\r
9805 }\r
9806 \r
9807 void\r
9808 ReloadCmailMsgEvent(unregister)\r
9809      int unregister;\r
9810 {\r
9811 #if !WIN32\r
9812     static char *inFilename = NULL;\r
9813     static char *outFilename;\r
9814     int i;\r
9815     struct stat inbuf, outbuf;\r
9816     int status;\r
9817     \r
9818     /* Any registered moves are unregistered if unregister is set, */\r
9819     /* i.e. invoked by the signal handler */\r
9820     if (unregister) {\r
9821         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
9822             cmailMoveRegistered[i] = FALSE;\r
9823             if (cmailCommentList[i] != NULL) {\r
9824                 free(cmailCommentList[i]);\r
9825                 cmailCommentList[i] = NULL;\r
9826             }\r
9827         }\r
9828         nCmailMovesRegistered = 0;\r
9829     }\r
9830 \r
9831     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
9832         cmailResult[i] = CMAIL_NOT_RESULT;\r
9833     }\r
9834     nCmailResults = 0;\r
9835 \r
9836     if (inFilename == NULL) {\r
9837         /* Because the filenames are static they only get malloced once  */\r
9838         /* and they never get freed                                      */\r
9839         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);\r
9840         sprintf(inFilename, "%s.game.in", appData.cmailGameName);\r
9841 \r
9842         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);\r
9843         sprintf(outFilename, "%s.out", appData.cmailGameName);\r
9844     }\r
9845     \r
9846     status = stat(outFilename, &outbuf);\r
9847     if (status < 0) {\r
9848         cmailMailedMove = FALSE;\r
9849     } else {\r
9850         status = stat(inFilename, &inbuf);\r
9851         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);\r
9852     }\r
9853     \r
9854     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE\r
9855        counts the games, notes how each one terminated, etc.\r
9856        \r
9857        It would be nice to remove this kludge and instead gather all\r
9858        the information while building the game list.  (And to keep it\r
9859        in the game list nodes instead of having a bunch of fixed-size\r
9860        parallel arrays.)  Note this will require getting each game's\r
9861        termination from the PGN tags, as the game list builder does\r
9862        not process the game moves.  --mann\r
9863        */\r
9864     cmailMsgLoaded = TRUE;\r
9865     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);\r
9866     \r
9867     /* Load first game in the file or popup game menu */\r
9868     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);\r
9869 \r
9870 #endif /* !WIN32 */\r
9871     return;\r
9872 }\r
9873 \r
9874 int\r
9875 RegisterMove()\r
9876 {\r
9877     FILE *f;\r
9878     char string[MSG_SIZ];\r
9879 \r
9880     if (   cmailMailedMove\r
9881         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {\r
9882         return TRUE;            /* Allow free viewing  */\r
9883     }\r
9884 \r
9885     /* Unregister move to ensure that we don't leave RegisterMove        */\r
9886     /* with the move registered when the conditions for registering no   */\r
9887     /* longer hold                                                       */\r
9888     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
9889         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
9890         nCmailMovesRegistered --;\r
9891 \r
9892         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) \r
9893           {\r
9894               free(cmailCommentList[lastLoadGameNumber - 1]);\r
9895               cmailCommentList[lastLoadGameNumber - 1] = NULL;\r
9896           }\r
9897     }\r
9898 \r
9899     if (cmailOldMove == -1) {\r
9900         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);\r
9901         return FALSE;\r
9902     }\r
9903 \r
9904     if (currentMove > cmailOldMove + 1) {\r
9905         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);\r
9906         return FALSE;\r
9907     }\r
9908 \r
9909     if (currentMove < cmailOldMove) {\r
9910         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);\r
9911         return FALSE;\r
9912     }\r
9913 \r
9914     if (forwardMostMove > currentMove) {\r
9915         /* Silently truncate extra moves */\r
9916         TruncateGame();\r
9917     }\r
9918 \r
9919     if (   (currentMove == cmailOldMove + 1)\r
9920         || (   (currentMove == cmailOldMove)\r
9921             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)\r
9922                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {\r
9923         if (gameInfo.result != GameUnfinished) {\r
9924             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;\r
9925         }\r
9926 \r
9927         if (commentList[currentMove] != NULL) {\r
9928             cmailCommentList[lastLoadGameNumber - 1]\r
9929               = StrSave(commentList[currentMove]);\r
9930         }\r
9931         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);\r
9932 \r
9933         if (appData.debugMode)\r
9934           fprintf(debugFP, "Saving %s for game %d\n",\r
9935                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
9936 \r
9937         sprintf(string,\r
9938                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);\r
9939         \r
9940         f = fopen(string, "w");\r
9941         if (appData.oldSaveStyle) {\r
9942             SaveGameOldStyle(f); /* also closes the file */\r
9943             \r
9944             sprintf(string, "%s.pos.out", appData.cmailGameName);\r
9945             f = fopen(string, "w");\r
9946             SavePosition(f, 0, NULL); /* also closes the file */\r
9947         } else {\r
9948             fprintf(f, "{--------------\n");\r
9949             PrintPosition(f, currentMove);\r
9950             fprintf(f, "--------------}\n\n");\r
9951             \r
9952             SaveGame(f, 0, NULL); /* also closes the file*/\r
9953         }\r
9954         \r
9955         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;\r
9956         nCmailMovesRegistered ++;\r
9957     } else if (nCmailGames == 1) {\r
9958         DisplayError(_("You have not made a move yet"), 0);\r
9959         return FALSE;\r
9960     }\r
9961 \r
9962     return TRUE;\r
9963 }\r
9964 \r
9965 void\r
9966 MailMoveEvent()\r
9967 {\r
9968 #if !WIN32\r
9969     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";\r
9970     FILE *commandOutput;\r
9971     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];\r
9972     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */\r
9973     int nBuffers;\r
9974     int i;\r
9975     int archived;\r
9976     char *arcDir;\r
9977 \r
9978     if (! cmailMsgLoaded) {\r
9979         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);\r
9980         return;\r
9981     }\r
9982 \r
9983     if (nCmailGames == nCmailResults) {\r
9984         DisplayError(_("No unfinished games"), 0);\r
9985         return;\r
9986     }\r
9987 \r
9988 #if CMAIL_PROHIBIT_REMAIL\r
9989     if (cmailMailedMove) {\r
9990         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
9991         DisplayError(msg, 0);\r
9992         return;\r
9993     }\r
9994 #endif\r
9995 \r
9996     if (! (cmailMailedMove || RegisterMove())) return;\r
9997     \r
9998     if (   cmailMailedMove\r
9999         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {\r
10000         sprintf(string, partCommandString,\r
10001                 appData.debugMode ? " -v" : "", appData.cmailGameName);\r
10002         commandOutput = popen(string, "r");\r
10003 \r
10004         if (commandOutput == NULL) {\r
10005             DisplayError(_("Failed to invoke cmail"), 0);\r
10006         } else {\r
10007             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {\r
10008                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);\r
10009             }\r
10010             if (nBuffers > 1) {\r
10011                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);\r
10012                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);\r
10013                 nBytes = MSG_SIZ - 1;\r
10014             } else {\r
10015                 (void) memcpy(msg, buffer, nBytes);\r
10016             }\r
10017             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/\r
10018 \r
10019             if(StrStr(msg, "Mailed cmail message to ") != NULL) {\r
10020                 cmailMailedMove = TRUE; /* Prevent >1 moves    */\r
10021 \r
10022                 archived = TRUE;\r
10023                 for (i = 0; i < nCmailGames; i ++) {\r
10024                     if (cmailResult[i] == CMAIL_NOT_RESULT) {\r
10025                         archived = FALSE;\r
10026                     }\r
10027                 }\r
10028                 if (   archived\r
10029                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))\r
10030                         != NULL)) {\r
10031                     sprintf(buffer, "%s/%s.%s.archive",\r
10032                             arcDir,\r
10033                             appData.cmailGameName,\r
10034                             gameInfo.date);\r
10035                     LoadGameFromFile(buffer, 1, buffer, FALSE);\r
10036                     cmailMsgLoaded = FALSE;\r
10037                 }\r
10038             }\r
10039 \r
10040             DisplayInformation(msg);\r
10041             pclose(commandOutput);\r
10042         }\r
10043     } else {\r
10044         if ((*cmailMsg) != '\0') {\r
10045             DisplayInformation(cmailMsg);\r
10046         }\r
10047     }\r
10048 \r
10049     return;\r
10050 #endif /* !WIN32 */\r
10051 }\r
10052 \r
10053 char *\r
10054 CmailMsg()\r
10055 {\r
10056 #if WIN32\r
10057     return NULL;\r
10058 #else\r
10059     int  prependComma = 0;\r
10060     char number[5];\r
10061     char string[MSG_SIZ];       /* Space for game-list */\r
10062     int  i;\r
10063     \r
10064     if (!cmailMsgLoaded) return "";\r
10065 \r
10066     if (cmailMailedMove) {\r
10067         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));\r
10068     } else {\r
10069         /* Create a list of games left */\r
10070         sprintf(string, "[");\r
10071         for (i = 0; i < nCmailGames; i ++) {\r
10072             if (! (   cmailMoveRegistered[i]\r
10073                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {\r
10074                 if (prependComma) {\r
10075                     sprintf(number, ",%d", i + 1);\r
10076                 } else {\r
10077                     sprintf(number, "%d", i + 1);\r
10078                     prependComma = 1;\r
10079                 }\r
10080                 \r
10081                 strcat(string, number);\r
10082             }\r
10083         }\r
10084         strcat(string, "]");\r
10085 \r
10086         if (nCmailMovesRegistered + nCmailResults == 0) {\r
10087             switch (nCmailGames) {\r
10088               case 1:\r
10089                 sprintf(cmailMsg,\r
10090                         _("Still need to make move for game\n"));\r
10091                 break;\r
10092                 \r
10093               case 2:\r
10094                 sprintf(cmailMsg,\r
10095                         _("Still need to make moves for both games\n"));\r
10096                 break;\r
10097                 \r
10098               default:\r
10099                 sprintf(cmailMsg,\r
10100                         _("Still need to make moves for all %d games\n"),\r
10101                         nCmailGames);\r
10102                 break;\r
10103             }\r
10104         } else {\r
10105             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {\r
10106               case 1:\r
10107                 sprintf(cmailMsg,\r
10108                         _("Still need to make a move for game %s\n"),\r
10109                         string);\r
10110                 break;\r
10111                 \r
10112               case 0:\r
10113                 if (nCmailResults == nCmailGames) {\r
10114                     sprintf(cmailMsg, _("No unfinished games\n"));\r
10115                 } else {\r
10116                     sprintf(cmailMsg, _("Ready to send mail\n"));\r
10117                 }\r
10118                 break;\r
10119                 \r
10120               default:\r
10121                 sprintf(cmailMsg,\r
10122                         _("Still need to make moves for games %s\n"),\r
10123                         string);\r
10124             }\r
10125         }\r
10126     }\r
10127     return cmailMsg;\r
10128 #endif /* WIN32 */\r
10129 }\r
10130 \r
10131 void\r
10132 ResetGameEvent()\r
10133 {\r
10134     if (gameMode == Training)\r
10135       SetTrainingModeOff();\r
10136 \r
10137     Reset(TRUE, TRUE);\r
10138     cmailMsgLoaded = FALSE;\r
10139     if (appData.icsActive) {\r
10140       SendToICS(ics_prefix);\r
10141       SendToICS("refresh\n");\r
10142     }\r
10143 }\r
10144 \r
10145 void\r
10146 ExitEvent(status)\r
10147      int status;\r
10148 {\r
10149     exiting++;\r
10150     if (exiting > 2) {\r
10151       /* Give up on clean exit */\r
10152       exit(status);\r
10153     }\r
10154     if (exiting > 1) {\r
10155       /* Keep trying for clean exit */\r
10156       return;\r
10157     }\r
10158 \r
10159     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);\r
10160 \r
10161     if (telnetISR != NULL) {\r
10162       RemoveInputSource(telnetISR);\r
10163     }\r
10164     if (icsPR != NoProc) {\r
10165       DestroyChildProcess(icsPR, TRUE);\r
10166     }\r
10167 #if 0\r
10168     /* Save game if resource set and not already saved by GameEnds() */\r
10169     if ((gameInfo.resultDetails == NULL || errorExitFlag )\r
10170                              && forwardMostMove > 0) {\r
10171       if (*appData.saveGameFile != NULLCHAR) {\r
10172         SaveGameToFile(appData.saveGameFile, TRUE);\r
10173       } else if (appData.autoSaveGames) {\r
10174         AutoSaveGame();\r
10175       }\r
10176       if (*appData.savePositionFile != NULLCHAR) {\r
10177         SavePositionToFile(appData.savePositionFile);\r
10178       }\r
10179     }\r
10180     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
10181 #else\r
10182     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */\r
10183     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);\r
10184 #endif\r
10185     /* [HGM] crash: the above GameEnds() is a dud if another one was running */\r
10186     /* make sure this other one finishes before killing it!                  */\r
10187     if(endingGame) { int count = 0;\r
10188         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");\r
10189         while(endingGame && count++ < 10) DoSleep(1);\r
10190         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");\r
10191     }\r
10192 \r
10193     /* Kill off chess programs */\r
10194     if (first.pr != NoProc) {\r
10195         ExitAnalyzeMode();\r
10196         \r
10197         DoSleep( appData.delayBeforeQuit );\r
10198         SendToProgram("quit\n", &first);\r
10199         DoSleep( appData.delayAfterQuit );\r
10200         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );\r
10201     }\r
10202     if (second.pr != NoProc) {\r
10203         DoSleep( appData.delayBeforeQuit );\r
10204         SendToProgram("quit\n", &second);\r
10205         DoSleep( appData.delayAfterQuit );\r
10206         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );\r
10207     }\r
10208     if (first.isr != NULL) {\r
10209         RemoveInputSource(first.isr);\r
10210     }\r
10211     if (second.isr != NULL) {\r
10212         RemoveInputSource(second.isr);\r
10213     }\r
10214 \r
10215     ShutDownFrontEnd();\r
10216     exit(status);\r
10217 }\r
10218 \r
10219 void\r
10220 PauseEvent()\r
10221 {\r
10222     if (appData.debugMode)\r
10223         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);\r
10224     if (pausing) {\r
10225         pausing = FALSE;\r
10226         ModeHighlight();\r
10227         if (gameMode == MachinePlaysWhite ||\r
10228             gameMode == MachinePlaysBlack) {\r
10229             StartClocks();\r
10230         } else {\r
10231             DisplayBothClocks();\r
10232         }\r
10233         if (gameMode == PlayFromGameFile) {\r
10234             if (appData.timeDelay >= 0) \r
10235                 AutoPlayGameLoop();\r
10236         } else if (gameMode == IcsExamining && pauseExamInvalid) {\r
10237             Reset(FALSE, TRUE);\r
10238             SendToICS(ics_prefix);\r
10239             SendToICS("refresh\n");\r
10240         } else if (currentMove < forwardMostMove) {\r
10241             ForwardInner(forwardMostMove);\r
10242         }\r
10243         pauseExamInvalid = FALSE;\r
10244     } else {\r
10245         switch (gameMode) {\r
10246           default:\r
10247             return;\r
10248           case IcsExamining:\r
10249             pauseExamForwardMostMove = forwardMostMove;\r
10250             pauseExamInvalid = FALSE;\r
10251             /* fall through */\r
10252           case IcsObserving:\r
10253           case IcsPlayingWhite:\r
10254           case IcsPlayingBlack:\r
10255             pausing = TRUE;\r
10256             ModeHighlight();\r
10257             return;\r
10258           case PlayFromGameFile:\r
10259             (void) StopLoadGameTimer();\r
10260             pausing = TRUE;\r
10261             ModeHighlight();\r
10262             break;\r
10263           case BeginningOfGame:\r
10264             if (appData.icsActive) return;\r
10265             /* else fall through */\r
10266           case MachinePlaysWhite:\r
10267           case MachinePlaysBlack:\r
10268           case TwoMachinesPlay:\r
10269             if (forwardMostMove == 0)\r
10270               return;           /* don't pause if no one has moved */\r
10271             if ((gameMode == MachinePlaysWhite &&\r
10272                  !WhiteOnMove(forwardMostMove)) ||\r
10273                 (gameMode == MachinePlaysBlack &&\r
10274                  WhiteOnMove(forwardMostMove))) {\r
10275                 StopClocks();\r
10276             }\r
10277             pausing = TRUE;\r
10278             ModeHighlight();\r
10279             break;\r
10280         }\r
10281     }\r
10282 }\r
10283 \r
10284 void\r
10285 EditCommentEvent()\r
10286 {\r
10287     char title[MSG_SIZ];\r
10288 \r
10289     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {\r
10290         strcpy(title, _("Edit comment"));\r
10291     } else {\r
10292         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,\r
10293                 WhiteOnMove(currentMove - 1) ? " " : ".. ",\r
10294                 parseList[currentMove - 1]);\r
10295     }\r
10296 \r
10297     EditCommentPopUp(currentMove, title, commentList[currentMove]);\r
10298 }\r
10299 \r
10300 \r
10301 void\r
10302 EditTagsEvent()\r
10303 {\r
10304     char *tags = PGNTags(&gameInfo);\r
10305     EditTagsPopUp(tags);\r
10306     free(tags);\r
10307 }\r
10308 \r
10309 void\r
10310 AnalyzeModeEvent()\r
10311 {\r
10312     if (appData.noChessProgram || gameMode == AnalyzeMode)\r
10313       return;\r
10314 \r
10315     if (gameMode != AnalyzeFile) {\r
10316         if (!appData.icsEngineAnalyze) {\r
10317                EditGameEvent();\r
10318                if (gameMode != EditGame) return;\r
10319         }\r
10320         ResurrectChessProgram();\r
10321         SendToProgram("analyze\n", &first);\r
10322         first.analyzing = TRUE;\r
10323         /*first.maybeThinking = TRUE;*/\r
10324         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10325         AnalysisPopUp(_("Analysis"),\r
10326                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));\r
10327     }\r
10328     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;\r
10329     pausing = FALSE;\r
10330     ModeHighlight();\r
10331     SetGameInfo();\r
10332 \r
10333     StartAnalysisClock();\r
10334     GetTimeMark(&lastNodeCountTime);\r
10335     lastNodeCount = 0;\r
10336 }\r
10337 \r
10338 void\r
10339 AnalyzeFileEvent()\r
10340 {\r
10341     if (appData.noChessProgram || gameMode == AnalyzeFile)\r
10342       return;\r
10343 \r
10344     if (gameMode != AnalyzeMode) {\r
10345         EditGameEvent();\r
10346         if (gameMode != EditGame) return;\r
10347         ResurrectChessProgram();\r
10348         SendToProgram("analyze\n", &first);\r
10349         first.analyzing = TRUE;\r
10350         /*first.maybeThinking = TRUE;*/\r
10351         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10352         AnalysisPopUp(_("Analysis"),\r
10353                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));\r
10354     }\r
10355     gameMode = AnalyzeFile;\r
10356     pausing = FALSE;\r
10357     ModeHighlight();\r
10358     SetGameInfo();\r
10359 \r
10360     StartAnalysisClock();\r
10361     GetTimeMark(&lastNodeCountTime);\r
10362     lastNodeCount = 0;\r
10363 }\r
10364 \r
10365 void\r
10366 MachineWhiteEvent()\r
10367 {\r
10368     char buf[MSG_SIZ];\r
10369     char *bookHit = NULL;\r
10370 \r
10371     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))\r
10372       return;\r
10373 \r
10374 \r
10375     if (gameMode == PlayFromGameFile || \r
10376         gameMode == TwoMachinesPlay  || \r
10377         gameMode == Training         || \r
10378         gameMode == AnalyzeMode      || \r
10379         gameMode == EndOfGame)\r
10380         EditGameEvent();\r
10381 \r
10382     if (gameMode == EditPosition) \r
10383         EditPositionDone();\r
10384 \r
10385     if (!WhiteOnMove(currentMove)) {\r
10386         DisplayError(_("It is not White's turn"), 0);\r
10387         return;\r
10388     }\r
10389   \r
10390     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
10391       ExitAnalyzeMode();\r
10392 \r
10393     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
10394         gameMode == AnalyzeFile)\r
10395         TruncateGame();\r
10396 \r
10397     ResurrectChessProgram();    /* in case it isn't running */\r
10398     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */\r
10399         gameMode = MachinePlaysWhite;\r
10400         ResetClocks();\r
10401     } else\r
10402     gameMode = MachinePlaysWhite;\r
10403     pausing = FALSE;\r
10404     ModeHighlight();\r
10405     SetGameInfo();\r
10406     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10407     DisplayTitle(buf);\r
10408     if (first.sendName) {\r
10409       sprintf(buf, "name %s\n", gameInfo.black);\r
10410       SendToProgram(buf, &first);\r
10411     }\r
10412     if (first.sendTime) {\r
10413       if (first.useColors) {\r
10414         SendToProgram("black\n", &first); /*gnu kludge*/\r
10415       }\r
10416       SendTimeRemaining(&first, TRUE);\r
10417     }\r
10418     if (first.useColors) {\r
10419       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately\r
10420     }\r
10421     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move\r
10422     SetMachineThinkingEnables();\r
10423     first.maybeThinking = TRUE;\r
10424     StartClocks();\r
10425 \r
10426     if (appData.autoFlipView && !flipView) {\r
10427       flipView = !flipView;\r
10428       DrawPosition(FALSE, NULL);\r
10429       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;\r
10430     }\r
10431 \r
10432     if(bookHit) { // [HGM] book: simulate book reply\r
10433         static char bookMove[MSG_SIZ]; // a bit generous?\r
10434 \r
10435         programStats.nodes = programStats.depth = programStats.time = \r
10436         programStats.score = programStats.got_only_move = 0;\r
10437         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10438 \r
10439         strcpy(bookMove, "move ");\r
10440         strcat(bookMove, bookHit);\r
10441         HandleMachineMove(bookMove, &first);\r
10442     }\r
10443 }\r
10444 \r
10445 void\r
10446 MachineBlackEvent()\r
10447 {\r
10448     char buf[MSG_SIZ];\r
10449    char *bookHit = NULL;\r
10450 \r
10451     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))\r
10452         return;\r
10453 \r
10454 \r
10455     if (gameMode == PlayFromGameFile || \r
10456         gameMode == TwoMachinesPlay  || \r
10457         gameMode == Training         || \r
10458         gameMode == AnalyzeMode      || \r
10459         gameMode == EndOfGame)\r
10460         EditGameEvent();\r
10461 \r
10462     if (gameMode == EditPosition) \r
10463         EditPositionDone();\r
10464 \r
10465     if (WhiteOnMove(currentMove)) {\r
10466         DisplayError(_("It is not Black's turn"), 0);\r
10467         return;\r
10468     }\r
10469     \r
10470     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
10471       ExitAnalyzeMode();\r
10472 \r
10473     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
10474         gameMode == AnalyzeFile)\r
10475         TruncateGame();\r
10476 \r
10477     ResurrectChessProgram();    /* in case it isn't running */\r
10478     gameMode = MachinePlaysBlack;\r
10479     pausing = FALSE;\r
10480     ModeHighlight();\r
10481     SetGameInfo();\r
10482     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10483     DisplayTitle(buf);\r
10484     if (first.sendName) {\r
10485       sprintf(buf, "name %s\n", gameInfo.white);\r
10486       SendToProgram(buf, &first);\r
10487     }\r
10488     if (first.sendTime) {\r
10489       if (first.useColors) {\r
10490         SendToProgram("white\n", &first); /*gnu kludge*/\r
10491       }\r
10492       SendTimeRemaining(&first, FALSE);\r
10493     }\r
10494     if (first.useColors) {\r
10495       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately\r
10496     }\r
10497     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move\r
10498     SetMachineThinkingEnables();\r
10499     first.maybeThinking = TRUE;\r
10500     StartClocks();\r
10501 \r
10502     if (appData.autoFlipView && flipView) {\r
10503       flipView = !flipView;\r
10504       DrawPosition(FALSE, NULL);\r
10505       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;\r
10506     }\r
10507     if(bookHit) { // [HGM] book: simulate book reply\r
10508         static char bookMove[MSG_SIZ]; // a bit generous?\r
10509 \r
10510         programStats.nodes = programStats.depth = programStats.time = \r
10511         programStats.score = programStats.got_only_move = 0;\r
10512         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10513 \r
10514         strcpy(bookMove, "move ");\r
10515         strcat(bookMove, bookHit);\r
10516         HandleMachineMove(bookMove, &first);\r
10517     }\r
10518 }\r
10519 \r
10520 \r
10521 void\r
10522 DisplayTwoMachinesTitle()\r
10523 {\r
10524     char buf[MSG_SIZ];\r
10525     if (appData.matchGames > 0) {\r
10526         if (first.twoMachinesColor[0] == 'w') {\r
10527             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
10528                     gameInfo.white, gameInfo.black,\r
10529                     first.matchWins, second.matchWins,\r
10530                     matchGame - 1 - (first.matchWins + second.matchWins));\r
10531         } else {\r
10532             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
10533                     gameInfo.white, gameInfo.black,\r
10534                     second.matchWins, first.matchWins,\r
10535                     matchGame - 1 - (first.matchWins + second.matchWins));\r
10536         }\r
10537     } else {\r
10538         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10539     }\r
10540     DisplayTitle(buf);\r
10541 }\r
10542 \r
10543 void\r
10544 TwoMachinesEvent P((void))\r
10545 {\r
10546     int i;\r
10547     char buf[MSG_SIZ];\r
10548     ChessProgramState *onmove;\r
10549     char *bookHit = NULL;\r
10550     \r
10551     if (appData.noChessProgram) return;\r
10552 \r
10553     switch (gameMode) {\r
10554       case TwoMachinesPlay:\r
10555         return;\r
10556       case MachinePlaysWhite:\r
10557       case MachinePlaysBlack:\r
10558         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
10559             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);\r
10560             return;\r
10561         }\r
10562         /* fall through */\r
10563       case BeginningOfGame:\r
10564       case PlayFromGameFile:\r
10565       case EndOfGame:\r
10566         EditGameEvent();\r
10567         if (gameMode != EditGame) return;\r
10568         break;\r
10569       case EditPosition:\r
10570         EditPositionDone();\r
10571         break;\r
10572       case AnalyzeMode:\r
10573       case AnalyzeFile:\r
10574         ExitAnalyzeMode();\r
10575         break;\r
10576       case EditGame:\r
10577       default:\r
10578         break;\r
10579     }\r
10580 \r
10581     forwardMostMove = currentMove;\r
10582     ResurrectChessProgram();    /* in case first program isn't running */\r
10583 \r
10584     if (second.pr == NULL) {\r
10585         StartChessProgram(&second);\r
10586         if (second.protocolVersion == 1) {\r
10587           TwoMachinesEventIfReady();\r
10588         } else {\r
10589           /* kludge: allow timeout for initial "feature" command */\r
10590           FreezeUI();\r
10591           DisplayMessage("", _("Starting second chess program"));\r
10592           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);\r
10593         }\r
10594         return;\r
10595     }\r
10596     DisplayMessage("", "");\r
10597     InitChessProgram(&second, FALSE);\r
10598     SendToProgram("force\n", &second);\r
10599     if (startedFromSetupPosition) {\r
10600         SendBoard(&second, backwardMostMove);\r
10601     if (appData.debugMode) {\r
10602         fprintf(debugFP, "Two Machines\n");\r
10603     }\r
10604     }\r
10605     for (i = backwardMostMove; i < forwardMostMove; i++) {\r
10606         SendMoveToProgram(i, &second);\r
10607     }\r
10608 \r
10609     gameMode = TwoMachinesPlay;\r
10610     pausing = FALSE;\r
10611     ModeHighlight();\r
10612     SetGameInfo();\r
10613     DisplayTwoMachinesTitle();\r
10614     firstMove = TRUE;\r
10615     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {\r
10616         onmove = &first;\r
10617     } else {\r
10618         onmove = &second;\r
10619     }\r
10620 \r
10621     SendToProgram(first.computerString, &first);\r
10622     if (first.sendName) {\r
10623       sprintf(buf, "name %s\n", second.tidy);\r
10624       SendToProgram(buf, &first);\r
10625     }\r
10626     SendToProgram(second.computerString, &second);\r
10627     if (second.sendName) {\r
10628       sprintf(buf, "name %s\n", first.tidy);\r
10629       SendToProgram(buf, &second);\r
10630     }\r
10631 \r
10632     ResetClocks();\r
10633     if (!first.sendTime || !second.sendTime) {\r
10634         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
10635         timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
10636     }\r
10637     if (onmove->sendTime) {\r
10638       if (onmove->useColors) {\r
10639         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/\r
10640       }\r
10641       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));\r
10642     }\r
10643     if (onmove->useColors) {\r
10644       SendToProgram(onmove->twoMachinesColor, onmove);\r
10645     }\r
10646     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move\r
10647 //    SendToProgram("go\n", onmove);\r
10648     onmove->maybeThinking = TRUE;\r
10649     SetMachineThinkingEnables();\r
10650 \r
10651     StartClocks();\r
10652 \r
10653     if(bookHit) { // [HGM] book: simulate book reply\r
10654         static char bookMove[MSG_SIZ]; // a bit generous?\r
10655 \r
10656         programStats.nodes = programStats.depth = programStats.time = \r
10657         programStats.score = programStats.got_only_move = 0;\r
10658         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10659 \r
10660         strcpy(bookMove, "move ");\r
10661         strcat(bookMove, bookHit);\r
10662         HandleMachineMove(bookMove, &first);\r
10663     }\r
10664 }\r
10665 \r
10666 void\r
10667 TrainingEvent()\r
10668 {\r
10669     if (gameMode == Training) {\r
10670       SetTrainingModeOff();\r
10671       gameMode = PlayFromGameFile;\r
10672       DisplayMessage("", _("Training mode off"));\r
10673     } else {\r
10674       gameMode = Training;\r
10675       animateTraining = appData.animate;\r
10676 \r
10677       /* make sure we are not already at the end of the game */\r
10678       if (currentMove < forwardMostMove) {\r
10679         SetTrainingModeOn();\r
10680         DisplayMessage("", _("Training mode on"));\r
10681       } else {\r
10682         gameMode = PlayFromGameFile;\r
10683         DisplayError(_("Already at end of game"), 0);\r
10684       }\r
10685     }\r
10686     ModeHighlight();\r
10687 }\r
10688 \r
10689 void\r
10690 IcsClientEvent()\r
10691 {\r
10692     if (!appData.icsActive) return;\r
10693     switch (gameMode) {\r
10694       case IcsPlayingWhite:\r
10695       case IcsPlayingBlack:\r
10696       case IcsObserving:\r
10697       case IcsIdle:\r
10698       case BeginningOfGame:\r
10699       case IcsExamining:\r
10700         return;\r
10701 \r
10702       case EditGame:\r
10703         break;\r
10704 \r
10705       case EditPosition:\r
10706         EditPositionDone();\r
10707         break;\r
10708 \r
10709       case AnalyzeMode:\r
10710       case AnalyzeFile:\r
10711         ExitAnalyzeMode();\r
10712         break;\r
10713         \r
10714       default:\r
10715         EditGameEvent();\r
10716         break;\r
10717     }\r
10718 \r
10719     gameMode = IcsIdle;\r
10720     ModeHighlight();\r
10721     return;\r
10722 }\r
10723 \r
10724 \r
10725 void\r
10726 EditGameEvent()\r
10727 {\r
10728     int i;\r
10729 \r
10730     switch (gameMode) {\r
10731       case Training:\r
10732         SetTrainingModeOff();\r
10733         break;\r
10734       case MachinePlaysWhite:\r
10735       case MachinePlaysBlack:\r
10736       case BeginningOfGame:\r
10737         SendToProgram("force\n", &first);\r
10738         SetUserThinkingEnables();\r
10739         break;\r
10740       case PlayFromGameFile:\r
10741         (void) StopLoadGameTimer();\r
10742         if (gameFileFP != NULL) {\r
10743             gameFileFP = NULL;\r
10744         }\r
10745         break;\r
10746       case EditPosition:\r
10747         EditPositionDone();\r
10748         break;\r
10749       case AnalyzeMode:\r
10750       case AnalyzeFile:\r
10751         ExitAnalyzeMode();\r
10752         SendToProgram("force\n", &first);\r
10753         break;\r
10754       case TwoMachinesPlay:\r
10755         GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
10756         ResurrectChessProgram();\r
10757         SetUserThinkingEnables();\r
10758         break;\r
10759       case EndOfGame:\r
10760         ResurrectChessProgram();\r
10761         break;\r
10762       case IcsPlayingBlack:\r
10763       case IcsPlayingWhite:\r
10764         DisplayError(_("Warning: You are still playing a game"), 0);\r
10765         break;\r
10766       case IcsObserving:\r
10767         DisplayError(_("Warning: You are still observing a game"), 0);\r
10768         break;\r
10769       case IcsExamining:\r
10770         DisplayError(_("Warning: You are still examining a game"), 0);\r
10771         break;\r
10772       case IcsIdle:\r
10773         break;\r
10774       case EditGame:\r
10775       default:\r
10776         return;\r
10777     }\r
10778     \r
10779     pausing = FALSE;\r
10780     StopClocks();\r
10781     first.offeredDraw = second.offeredDraw = 0;\r
10782 \r
10783     if (gameMode == PlayFromGameFile) {\r
10784         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10785         blackTimeRemaining = timeRemaining[1][currentMove];\r
10786         DisplayTitle("");\r
10787     }\r
10788 \r
10789     if (gameMode == MachinePlaysWhite ||\r
10790         gameMode == MachinePlaysBlack ||\r
10791         gameMode == TwoMachinesPlay ||\r
10792         gameMode == EndOfGame) {\r
10793         i = forwardMostMove;\r
10794         while (i > currentMove) {\r
10795             SendToProgram("undo\n", &first);\r
10796             i--;\r
10797         }\r
10798         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10799         blackTimeRemaining = timeRemaining[1][currentMove];\r
10800         DisplayBothClocks();\r
10801         if (whiteFlag || blackFlag) {\r
10802             whiteFlag = blackFlag = 0;\r
10803         }\r
10804         DisplayTitle("");\r
10805     }           \r
10806     \r
10807     gameMode = EditGame;\r
10808     ModeHighlight();\r
10809     SetGameInfo();\r
10810 }\r
10811 \r
10812 \r
10813 void\r
10814 EditPositionEvent()\r
10815 {\r
10816     if (gameMode == EditPosition) {\r
10817         EditGameEvent();\r
10818         return;\r
10819     }\r
10820     \r
10821     EditGameEvent();\r
10822     if (gameMode != EditGame) return;\r
10823     \r
10824     gameMode = EditPosition;\r
10825     ModeHighlight();\r
10826     SetGameInfo();\r
10827     if (currentMove > 0)\r
10828       CopyBoard(boards[0], boards[currentMove]);\r
10829     \r
10830     blackPlaysFirst = !WhiteOnMove(currentMove);\r
10831     ResetClocks();\r
10832     currentMove = forwardMostMove = backwardMostMove = 0;\r
10833     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
10834     DisplayMove(-1);\r
10835 }\r
10836 \r
10837 void\r
10838 ExitAnalyzeMode()\r
10839 {\r
10840     /* [DM] icsEngineAnalyze - possible call from other functions */\r
10841     if (appData.icsEngineAnalyze) {\r
10842         appData.icsEngineAnalyze = FALSE;\r
10843 \r
10844         DisplayMessage("",_("Close ICS engine analyze..."));\r
10845     }\r
10846     if (first.analysisSupport && first.analyzing) {\r
10847       SendToProgram("exit\n", &first);\r
10848       first.analyzing = FALSE;\r
10849     }\r
10850     AnalysisPopDown();\r
10851     thinkOutput[0] = NULLCHAR;\r
10852 }\r
10853 \r
10854 void\r
10855 EditPositionDone()\r
10856 {\r
10857     startedFromSetupPosition = TRUE;\r
10858     InitChessProgram(&first, FALSE);\r
10859     SendToProgram("force\n", &first);\r
10860     if (blackPlaysFirst) {\r
10861         strcpy(moveList[0], "");\r
10862         strcpy(parseList[0], "");\r
10863         currentMove = forwardMostMove = backwardMostMove = 1;\r
10864         CopyBoard(boards[1], boards[0]);\r
10865         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */\r
10866         { int i;\r
10867           epStatus[1] = epStatus[0];\r
10868           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];\r
10869         }\r
10870     } else {\r
10871         currentMove = forwardMostMove = backwardMostMove = 0;\r
10872     }\r
10873     SendBoard(&first, forwardMostMove);\r
10874     if (appData.debugMode) {\r
10875         fprintf(debugFP, "EditPosDone\n");\r
10876     }\r
10877     DisplayTitle("");\r
10878     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
10879     timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
10880     gameMode = EditGame;\r
10881     ModeHighlight();\r
10882     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
10883     ClearHighlights(); /* [AS] */\r
10884 }\r
10885 \r
10886 /* Pause for `ms' milliseconds */\r
10887 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
10888 void\r
10889 TimeDelay(ms)\r
10890      long ms;\r
10891 {\r
10892     TimeMark m1, m2;\r
10893 \r
10894     GetTimeMark(&m1);\r
10895     do {\r
10896         GetTimeMark(&m2);\r
10897     } while (SubtractTimeMarks(&m2, &m1) < ms);\r
10898 }\r
10899 \r
10900 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
10901 void\r
10902 SendMultiLineToICS(buf)\r
10903      char *buf;\r
10904 {\r
10905     char temp[MSG_SIZ+1], *p;\r
10906     int len;\r
10907 \r
10908     len = strlen(buf);\r
10909     if (len > MSG_SIZ)\r
10910       len = MSG_SIZ;\r
10911   \r
10912     strncpy(temp, buf, len);\r
10913     temp[len] = 0;\r
10914 \r
10915     p = temp;\r
10916     while (*p) {\r
10917         if (*p == '\n' || *p == '\r')\r
10918           *p = ' ';\r
10919         ++p;\r
10920     }\r
10921 \r
10922     strcat(temp, "\n");\r
10923     SendToICS(temp);\r
10924     SendToPlayer(temp, strlen(temp));\r
10925 }\r
10926 \r
10927 void\r
10928 SetWhiteToPlayEvent()\r
10929 {\r
10930     if (gameMode == EditPosition) {\r
10931         blackPlaysFirst = FALSE;\r
10932         DisplayBothClocks();    /* works because currentMove is 0 */\r
10933     } else if (gameMode == IcsExamining) {\r
10934         SendToICS(ics_prefix);\r
10935         SendToICS("tomove white\n");\r
10936     }\r
10937 }\r
10938 \r
10939 void\r
10940 SetBlackToPlayEvent()\r
10941 {\r
10942     if (gameMode == EditPosition) {\r
10943         blackPlaysFirst = TRUE;\r
10944         currentMove = 1;        /* kludge */\r
10945         DisplayBothClocks();\r
10946         currentMove = 0;\r
10947     } else if (gameMode == IcsExamining) {\r
10948         SendToICS(ics_prefix);\r
10949         SendToICS("tomove black\n");\r
10950     }\r
10951 }\r
10952 \r
10953 void\r
10954 EditPositionMenuEvent(selection, x, y)\r
10955      ChessSquare selection;\r
10956      int x, y;\r
10957 {\r
10958     char buf[MSG_SIZ];\r
10959     ChessSquare piece = boards[0][y][x];\r
10960 \r
10961     if (gameMode != EditPosition && gameMode != IcsExamining) return;\r
10962 \r
10963     switch (selection) {\r
10964       case ClearBoard:\r
10965         if (gameMode == IcsExamining && ics_type == ICS_FICS) {\r
10966             SendToICS(ics_prefix);\r
10967             SendToICS("bsetup clear\n");\r
10968         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {\r
10969             SendToICS(ics_prefix);\r
10970             SendToICS("clearboard\n");\r
10971         } else {\r
10972             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;\r
10973                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */\r
10974                 for (y = 0; y < BOARD_HEIGHT; y++) {\r
10975                     if (gameMode == IcsExamining) {\r
10976                         if (boards[currentMove][y][x] != EmptySquare) {\r
10977                             sprintf(buf, "%sx@%c%c\n", ics_prefix,\r
10978                                     AAA + x, ONE + y);\r
10979                             SendToICS(buf);\r
10980                         }\r
10981                     } else {\r
10982                         boards[0][y][x] = p;\r
10983                     }\r
10984                 }\r
10985             }\r
10986         }\r
10987         if (gameMode == EditPosition) {\r
10988             DrawPosition(FALSE, boards[0]);\r
10989         }\r
10990         break;\r
10991 \r
10992       case WhitePlay:\r
10993         SetWhiteToPlayEvent();\r
10994         break;\r
10995 \r
10996       case BlackPlay:\r
10997         SetBlackToPlayEvent();\r
10998         break;\r
10999 \r
11000       case EmptySquare:\r
11001         if (gameMode == IcsExamining) {\r
11002             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);\r
11003             SendToICS(buf);\r
11004         } else {\r
11005             boards[0][y][x] = EmptySquare;\r
11006             DrawPosition(FALSE, boards[0]);\r
11007         }\r
11008         break;\r
11009 \r
11010       case PromotePiece:\r
11011         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||\r
11012            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {\r
11013             selection = (ChessSquare) (PROMOTED piece);\r
11014         } else if(piece == EmptySquare) selection = WhiteSilver;\r
11015         else selection = (ChessSquare)((int)piece - 1);\r
11016         goto defaultlabel;\r
11017 \r
11018       case DemotePiece:\r
11019         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||\r
11020            piece > (int)BlackMan && piece <= (int)BlackKing   ) {\r
11021             selection = (ChessSquare) (DEMOTED piece);\r
11022         } else if(piece == EmptySquare) selection = BlackSilver;\r
11023         else selection = (ChessSquare)((int)piece + 1);       \r
11024         goto defaultlabel;\r
11025 \r
11026       case WhiteQueen:\r
11027       case BlackQueen:\r
11028         if(gameInfo.variant == VariantShatranj ||\r
11029            gameInfo.variant == VariantXiangqi  ||\r
11030            gameInfo.variant == VariantCourier    )\r
11031             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);\r
11032         goto defaultlabel;\r
11033 \r
11034       case WhiteKing:\r
11035       case BlackKing:\r
11036         if(gameInfo.variant == VariantXiangqi)\r
11037             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);\r
11038         if(gameInfo.variant == VariantKnightmate)\r
11039             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);\r
11040       default:\r
11041         defaultlabel:\r
11042         if (gameMode == IcsExamining) {\r
11043             sprintf(buf, "%s%c@%c%c\n", ics_prefix,\r
11044                     PieceToChar(selection), AAA + x, ONE + y);\r
11045             SendToICS(buf);\r
11046         } else {\r
11047             boards[0][y][x] = selection;\r
11048             DrawPosition(FALSE, boards[0]);\r
11049         }\r
11050         break;\r
11051     }\r
11052 }\r
11053 \r
11054 \r
11055 void\r
11056 DropMenuEvent(selection, x, y)\r
11057      ChessSquare selection;\r
11058      int x, y;\r
11059 {\r
11060     ChessMove moveType;\r
11061 \r
11062     switch (gameMode) {\r
11063       case IcsPlayingWhite:\r
11064       case MachinePlaysBlack:\r
11065         if (!WhiteOnMove(currentMove)) {\r
11066             DisplayMoveError(_("It is Black's turn"));\r
11067             return;\r
11068         }\r
11069         moveType = WhiteDrop;\r
11070         break;\r
11071       case IcsPlayingBlack:\r
11072       case MachinePlaysWhite:\r
11073         if (WhiteOnMove(currentMove)) {\r
11074             DisplayMoveError(_("It is White's turn"));\r
11075             return;\r
11076         }\r
11077         moveType = BlackDrop;\r
11078         break;\r
11079       case EditGame:\r
11080         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
11081         break;\r
11082       default:\r
11083         return;\r
11084     }\r
11085 \r
11086     if (moveType == BlackDrop && selection < BlackPawn) {\r
11087       selection = (ChessSquare) ((int) selection\r
11088                                  + (int) BlackPawn - (int) WhitePawn);\r
11089     }\r
11090     if (boards[currentMove][y][x] != EmptySquare) {\r
11091         DisplayMoveError(_("That square is occupied"));\r
11092         return;\r
11093     }\r
11094 \r
11095     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);\r
11096 }\r
11097 \r
11098 void\r
11099 AcceptEvent()\r
11100 {\r
11101     /* Accept a pending offer of any kind from opponent */\r
11102     \r
11103     if (appData.icsActive) {\r
11104         SendToICS(ics_prefix);\r
11105         SendToICS("accept\n");\r
11106     } else if (cmailMsgLoaded) {\r
11107         if (currentMove == cmailOldMove &&\r
11108             commentList[cmailOldMove] != NULL &&\r
11109             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11110                    "Black offers a draw" : "White offers a draw")) {\r
11111             TruncateGame();\r
11112             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
11113             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
11114         } else {\r
11115             DisplayError(_("There is no pending offer on this move"), 0);\r
11116             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
11117         }\r
11118     } else {\r
11119         /* Not used for offers from chess program */\r
11120     }\r
11121 }\r
11122 \r
11123 void\r
11124 DeclineEvent()\r
11125 {\r
11126     /* Decline a pending offer of any kind from opponent */\r
11127     \r
11128     if (appData.icsActive) {\r
11129         SendToICS(ics_prefix);\r
11130         SendToICS("decline\n");\r
11131     } else if (cmailMsgLoaded) {\r
11132         if (currentMove == cmailOldMove &&\r
11133             commentList[cmailOldMove] != NULL &&\r
11134             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11135                    "Black offers a draw" : "White offers a draw")) {\r
11136 #ifdef NOTDEF\r
11137             AppendComment(cmailOldMove, "Draw declined");\r
11138             DisplayComment(cmailOldMove - 1, "Draw declined");\r
11139 #endif /*NOTDEF*/\r
11140         } else {\r
11141             DisplayError(_("There is no pending offer on this move"), 0);\r
11142         }\r
11143     } else {\r
11144         /* Not used for offers from chess program */\r
11145     }\r
11146 }\r
11147 \r
11148 void\r
11149 RematchEvent()\r
11150 {\r
11151     /* Issue ICS rematch command */\r
11152     if (appData.icsActive) {\r
11153         SendToICS(ics_prefix);\r
11154         SendToICS("rematch\n");\r
11155     }\r
11156 }\r
11157 \r
11158 void\r
11159 CallFlagEvent()\r
11160 {\r
11161     /* Call your opponent's flag (claim a win on time) */\r
11162     if (appData.icsActive) {\r
11163         SendToICS(ics_prefix);\r
11164         SendToICS("flag\n");\r
11165     } else {\r
11166         switch (gameMode) {\r
11167           default:\r
11168             return;\r
11169           case MachinePlaysWhite:\r
11170             if (whiteFlag) {\r
11171                 if (blackFlag)\r
11172                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
11173                            GE_PLAYER);\r
11174                 else\r
11175                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);\r
11176             } else {\r
11177                 DisplayError(_("Your opponent is not out of time"), 0);\r
11178             }\r
11179             break;\r
11180           case MachinePlaysBlack:\r
11181             if (blackFlag) {\r
11182                 if (whiteFlag)\r
11183                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
11184                            GE_PLAYER);\r
11185                 else\r
11186                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);\r
11187             } else {\r
11188                 DisplayError(_("Your opponent is not out of time"), 0);\r
11189             }\r
11190             break;\r
11191         }\r
11192     }\r
11193 }\r
11194 \r
11195 void\r
11196 DrawEvent()\r
11197 {\r
11198     /* Offer draw or accept pending draw offer from opponent */\r
11199     \r
11200     if (appData.icsActive) {\r
11201         /* Note: tournament rules require draw offers to be\r
11202            made after you make your move but before you punch\r
11203            your clock.  Currently ICS doesn't let you do that;\r
11204            instead, you immediately punch your clock after making\r
11205            a move, but you can offer a draw at any time. */\r
11206         \r
11207         SendToICS(ics_prefix);\r
11208         SendToICS("draw\n");\r
11209     } else if (cmailMsgLoaded) {\r
11210         if (currentMove == cmailOldMove &&\r
11211             commentList[cmailOldMove] != NULL &&\r
11212             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11213                    "Black offers a draw" : "White offers a draw")) {\r
11214             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
11215             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
11216         } else if (currentMove == cmailOldMove + 1) {\r
11217             char *offer = WhiteOnMove(cmailOldMove) ?\r
11218               "White offers a draw" : "Black offers a draw";\r
11219             AppendComment(currentMove, offer);\r
11220             DisplayComment(currentMove - 1, offer);\r
11221             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;\r
11222         } else {\r
11223             DisplayError(_("You must make your move before offering a draw"), 0);\r
11224             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
11225         }\r
11226     } else if (first.offeredDraw) {\r
11227         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
11228     } else {\r
11229         if (first.sendDrawOffers) {\r
11230             SendToProgram("draw\n", &first);\r
11231             userOfferedDraw = TRUE;\r
11232         }\r
11233     }\r
11234 }\r
11235 \r
11236 void\r
11237 AdjournEvent()\r
11238 {\r
11239     /* Offer Adjourn or accept pending Adjourn offer from opponent */\r
11240     \r
11241     if (appData.icsActive) {\r
11242         SendToICS(ics_prefix);\r
11243         SendToICS("adjourn\n");\r
11244     } else {\r
11245         /* Currently GNU Chess doesn't offer or accept Adjourns */\r
11246     }\r
11247 }\r
11248 \r
11249 \r
11250 void\r
11251 AbortEvent()\r
11252 {\r
11253     /* Offer Abort or accept pending Abort offer from opponent */\r
11254     \r
11255     if (appData.icsActive) {\r
11256         SendToICS(ics_prefix);\r
11257         SendToICS("abort\n");\r
11258     } else {\r
11259         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);\r
11260     }\r
11261 }\r
11262 \r
11263 void\r
11264 ResignEvent()\r
11265 {\r
11266     /* Resign.  You can do this even if it's not your turn. */\r
11267     \r
11268     if (appData.icsActive) {\r
11269         SendToICS(ics_prefix);\r
11270         SendToICS("resign\n");\r
11271     } else {\r
11272         switch (gameMode) {\r
11273           case MachinePlaysWhite:\r
11274             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
11275             break;\r
11276           case MachinePlaysBlack:\r
11277             GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
11278             break;\r
11279           case EditGame:\r
11280             if (cmailMsgLoaded) {\r
11281                 TruncateGame();\r
11282                 if (WhiteOnMove(cmailOldMove)) {\r
11283                     GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
11284                 } else {\r
11285                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
11286                 }\r
11287                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;\r
11288             }\r
11289             break;\r
11290           default:\r
11291             break;\r
11292         }\r
11293     }\r
11294 }\r
11295 \r
11296 \r
11297 void\r
11298 StopObservingEvent()\r
11299 {\r
11300     /* Stop observing current games */\r
11301     SendToICS(ics_prefix);\r
11302     SendToICS("unobserve\n");\r
11303 }\r
11304 \r
11305 void\r
11306 StopExaminingEvent()\r
11307 {\r
11308     /* Stop observing current game */\r
11309     SendToICS(ics_prefix);\r
11310     SendToICS("unexamine\n");\r
11311 }\r
11312 \r
11313 void\r
11314 ForwardInner(target)\r
11315      int target;\r
11316 {\r
11317     int limit;\r
11318 \r
11319     if (appData.debugMode)\r
11320         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",\r
11321                 target, currentMove, forwardMostMove);\r
11322 \r
11323     if (gameMode == EditPosition)\r
11324       return;\r
11325 \r
11326     if (gameMode == PlayFromGameFile && !pausing)\r
11327       PauseEvent();\r
11328     \r
11329     if (gameMode == IcsExamining && pausing)\r
11330       limit = pauseExamForwardMostMove;\r
11331     else\r
11332       limit = forwardMostMove;\r
11333     \r
11334     if (target > limit) target = limit;\r
11335 \r
11336     if (target > 0 && moveList[target - 1][0]) {\r
11337         int fromX, fromY, toX, toY;\r
11338         toX = moveList[target - 1][2] - AAA;\r
11339         toY = moveList[target - 1][3] - ONE;\r
11340         if (moveList[target - 1][1] == '@') {\r
11341             if (appData.highlightLastMove) {\r
11342                 SetHighlights(-1, -1, toX, toY);\r
11343             }\r
11344         } else {\r
11345             fromX = moveList[target - 1][0] - AAA;\r
11346             fromY = moveList[target - 1][1] - ONE;\r
11347             if (target == currentMove + 1) {\r
11348                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
11349             }\r
11350             if (appData.highlightLastMove) {\r
11351                 SetHighlights(fromX, fromY, toX, toY);\r
11352             }\r
11353         }\r
11354     }\r
11355     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
11356         gameMode == Training || gameMode == PlayFromGameFile || \r
11357         gameMode == AnalyzeFile) {\r
11358         while (currentMove < target) {\r
11359             SendMoveToProgram(currentMove++, &first);\r
11360         }\r
11361     } else {\r
11362         currentMove = target;\r
11363     }\r
11364     \r
11365     if (gameMode == EditGame || gameMode == EndOfGame) {\r
11366         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11367         blackTimeRemaining = timeRemaining[1][currentMove];\r
11368     }\r
11369     DisplayBothClocks();\r
11370     DisplayMove(currentMove - 1);\r
11371     DrawPosition(FALSE, boards[currentMove]);\r
11372     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
11373     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty\r
11374         DisplayComment(currentMove - 1, commentList[currentMove]);\r
11375     }\r
11376 }\r
11377 \r
11378 \r
11379 void\r
11380 ForwardEvent()\r
11381 {\r
11382     if (gameMode == IcsExamining && !pausing) {\r
11383         SendToICS(ics_prefix);\r
11384         SendToICS("forward\n");\r
11385     } else {\r
11386         ForwardInner(currentMove + 1);\r
11387     }\r
11388 }\r
11389 \r
11390 void\r
11391 ToEndEvent()\r
11392 {\r
11393     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11394         /* to optimze, we temporarily turn off analysis mode while we feed\r
11395          * the remaining moves to the engine. Otherwise we get analysis output\r
11396          * after each move.\r
11397          */ \r
11398         if (first.analysisSupport) {\r
11399           SendToProgram("exit\nforce\n", &first);\r
11400           first.analyzing = FALSE;\r
11401         }\r
11402     }\r
11403         \r
11404     if (gameMode == IcsExamining && !pausing) {\r
11405         SendToICS(ics_prefix);\r
11406         SendToICS("forward 999999\n");\r
11407     } else {\r
11408         ForwardInner(forwardMostMove);\r
11409     }\r
11410 \r
11411     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11412         /* we have fed all the moves, so reactivate analysis mode */\r
11413         SendToProgram("analyze\n", &first);\r
11414         first.analyzing = TRUE;\r
11415         /*first.maybeThinking = TRUE;*/\r
11416         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
11417     }\r
11418 }\r
11419 \r
11420 void\r
11421 BackwardInner(target)\r
11422      int target;\r
11423 {\r
11424     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */\r
11425 \r
11426     if (appData.debugMode)\r
11427         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",\r
11428                 target, currentMove, forwardMostMove);\r
11429 \r
11430     if (gameMode == EditPosition) return;\r
11431     if (currentMove <= backwardMostMove) {\r
11432         ClearHighlights();\r
11433         DrawPosition(full_redraw, boards[currentMove]);\r
11434         return;\r
11435     }\r
11436     if (gameMode == PlayFromGameFile && !pausing)\r
11437       PauseEvent();\r
11438     \r
11439     if (moveList[target][0]) {\r
11440         int fromX, fromY, toX, toY;\r
11441         toX = moveList[target][2] - AAA;\r
11442         toY = moveList[target][3] - ONE;\r
11443         if (moveList[target][1] == '@') {\r
11444             if (appData.highlightLastMove) {\r
11445                 SetHighlights(-1, -1, toX, toY);\r
11446             }\r
11447         } else {\r
11448             fromX = moveList[target][0] - AAA;\r
11449             fromY = moveList[target][1] - ONE;\r
11450             if (target == currentMove - 1) {\r
11451                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);\r
11452             }\r
11453             if (appData.highlightLastMove) {\r
11454                 SetHighlights(fromX, fromY, toX, toY);\r
11455             }\r
11456         }\r
11457     }\r
11458     if (gameMode == EditGame || gameMode==AnalyzeMode ||\r
11459         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
11460         while (currentMove > target) {\r
11461             SendToProgram("undo\n", &first);\r
11462             currentMove--;\r
11463         }\r
11464     } else {\r
11465         currentMove = target;\r
11466     }\r
11467     \r
11468     if (gameMode == EditGame || gameMode == EndOfGame) {\r
11469         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11470         blackTimeRemaining = timeRemaining[1][currentMove];\r
11471     }\r
11472     DisplayBothClocks();\r
11473     DisplayMove(currentMove - 1);\r
11474     DrawPosition(full_redraw, boards[currentMove]);\r
11475     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
11476     // [HGM] PV info: routine tests if comment empty\r
11477     DisplayComment(currentMove - 1, commentList[currentMove]);\r
11478 }\r
11479 \r
11480 void\r
11481 BackwardEvent()\r
11482 {\r
11483     if (gameMode == IcsExamining && !pausing) {\r
11484         SendToICS(ics_prefix);\r
11485         SendToICS("backward\n");\r
11486     } else {\r
11487         BackwardInner(currentMove - 1);\r
11488     }\r
11489 }\r
11490 \r
11491 void\r
11492 ToStartEvent()\r
11493 {\r
11494     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11495         /* to optimze, we temporarily turn off analysis mode while we undo\r
11496          * all the moves. Otherwise we get analysis output after each undo.\r
11497          */ \r
11498         if (first.analysisSupport) {\r
11499           SendToProgram("exit\nforce\n", &first);\r
11500           first.analyzing = FALSE;\r
11501         }\r
11502     }\r
11503 \r
11504     if (gameMode == IcsExamining && !pausing) {\r
11505         SendToICS(ics_prefix);\r
11506         SendToICS("backward 999999\n");\r
11507     } else {\r
11508         BackwardInner(backwardMostMove);\r
11509     }\r
11510 \r
11511     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11512         /* we have fed all the moves, so reactivate analysis mode */\r
11513         SendToProgram("analyze\n", &first);\r
11514         first.analyzing = TRUE;\r
11515         /*first.maybeThinking = TRUE;*/\r
11516         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
11517     }\r
11518 }\r
11519 \r
11520 void\r
11521 ToNrEvent(int to)\r
11522 {\r
11523   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();\r
11524   if (to >= forwardMostMove) to = forwardMostMove;\r
11525   if (to <= backwardMostMove) to = backwardMostMove;\r
11526   if (to < currentMove) {\r
11527     BackwardInner(to);\r
11528   } else {\r
11529     ForwardInner(to);\r
11530   }\r
11531 }\r
11532 \r
11533 void\r
11534 RevertEvent()\r
11535 {\r
11536     if (gameMode != IcsExamining) {\r
11537         DisplayError(_("You are not examining a game"), 0);\r
11538         return;\r
11539     }\r
11540     if (pausing) {\r
11541         DisplayError(_("You can't revert while pausing"), 0);\r
11542         return;\r
11543     }\r
11544     SendToICS(ics_prefix);\r
11545     SendToICS("revert\n");\r
11546 }\r
11547 \r
11548 void\r
11549 RetractMoveEvent()\r
11550 {\r
11551     switch (gameMode) {\r
11552       case MachinePlaysWhite:\r
11553       case MachinePlaysBlack:\r
11554         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
11555             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);\r
11556             return;\r
11557         }\r
11558         if (forwardMostMove < 2) return;\r
11559         currentMove = forwardMostMove = forwardMostMove - 2;\r
11560         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11561         blackTimeRemaining = timeRemaining[1][currentMove];\r
11562         DisplayBothClocks();\r
11563         DisplayMove(currentMove - 1);\r
11564         ClearHighlights();/*!! could figure this out*/\r
11565         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */\r
11566         SendToProgram("remove\n", &first);\r
11567         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */\r
11568         break;\r
11569 \r
11570       case BeginningOfGame:\r
11571       default:\r
11572         break;\r
11573 \r
11574       case IcsPlayingWhite:\r
11575       case IcsPlayingBlack:\r
11576         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {\r
11577             SendToICS(ics_prefix);\r
11578             SendToICS("takeback 2\n");\r
11579         } else {\r
11580             SendToICS(ics_prefix);\r
11581             SendToICS("takeback 1\n");\r
11582         }\r
11583         break;\r
11584     }\r
11585 }\r
11586 \r
11587 void\r
11588 MoveNowEvent()\r
11589 {\r
11590     ChessProgramState *cps;\r
11591 \r
11592     switch (gameMode) {\r
11593       case MachinePlaysWhite:\r
11594         if (!WhiteOnMove(forwardMostMove)) {\r
11595             DisplayError(_("It is your turn"), 0);\r
11596             return;\r
11597         }\r
11598         cps = &first;\r
11599         break;\r
11600       case MachinePlaysBlack:\r
11601         if (WhiteOnMove(forwardMostMove)) {\r
11602             DisplayError(_("It is your turn"), 0);\r
11603             return;\r
11604         }\r
11605         cps = &first;\r
11606         break;\r
11607       case TwoMachinesPlay:\r
11608         if (WhiteOnMove(forwardMostMove) ==\r
11609             (first.twoMachinesColor[0] == 'w')) {\r
11610             cps = &first;\r
11611         } else {\r
11612             cps = &second;\r
11613         }\r
11614         break;\r
11615       case BeginningOfGame:\r
11616       default:\r
11617         return;\r
11618     }\r
11619     SendToProgram("?\n", cps);\r
11620 }\r
11621 \r
11622 void\r
11623 TruncateGameEvent()\r
11624 {\r
11625     EditGameEvent();\r
11626     if (gameMode != EditGame) return;\r
11627     TruncateGame();\r
11628 }\r
11629 \r
11630 void\r
11631 TruncateGame()\r
11632 {\r
11633     if (forwardMostMove > currentMove) {\r
11634         if (gameInfo.resultDetails != NULL) {\r
11635             free(gameInfo.resultDetails);\r
11636             gameInfo.resultDetails = NULL;\r
11637             gameInfo.result = GameUnfinished;\r
11638         }\r
11639         forwardMostMove = currentMove;\r
11640         HistorySet(parseList, backwardMostMove, forwardMostMove,\r
11641                    currentMove-1);\r
11642     }\r
11643 }\r
11644 \r
11645 void\r
11646 HintEvent()\r
11647 {\r
11648     if (appData.noChessProgram) return;\r
11649     switch (gameMode) {\r
11650       case MachinePlaysWhite:\r
11651         if (WhiteOnMove(forwardMostMove)) {\r
11652             DisplayError(_("Wait until your turn"), 0);\r
11653             return;\r
11654         }\r
11655         break;\r
11656       case BeginningOfGame:\r
11657       case MachinePlaysBlack:\r
11658         if (!WhiteOnMove(forwardMostMove)) {\r
11659             DisplayError(_("Wait until your turn"), 0);\r
11660             return;\r
11661         }\r
11662         break;\r
11663       default:\r
11664         DisplayError(_("No hint available"), 0);\r
11665         return;\r
11666     }\r
11667     SendToProgram("hint\n", &first);\r
11668     hintRequested = TRUE;\r
11669 }\r
11670 \r
11671 void\r
11672 BookEvent()\r
11673 {\r
11674     if (appData.noChessProgram) return;\r
11675     switch (gameMode) {\r
11676       case MachinePlaysWhite:\r
11677         if (WhiteOnMove(forwardMostMove)) {\r
11678             DisplayError(_("Wait until your turn"), 0);\r
11679             return;\r
11680         }\r
11681         break;\r
11682       case BeginningOfGame:\r
11683       case MachinePlaysBlack:\r
11684         if (!WhiteOnMove(forwardMostMove)) {\r
11685             DisplayError(_("Wait until your turn"), 0);\r
11686             return;\r
11687         }\r
11688         break;\r
11689       case EditPosition:\r
11690         EditPositionDone();\r
11691         break;\r
11692       case TwoMachinesPlay:\r
11693         return;\r
11694       default:\r
11695         break;\r
11696     }\r
11697     SendToProgram("bk\n", &first);\r
11698     bookOutput[0] = NULLCHAR;\r
11699     bookRequested = TRUE;\r
11700 }\r
11701 \r
11702 void\r
11703 AboutGameEvent()\r
11704 {\r
11705     char *tags = PGNTags(&gameInfo);\r
11706     TagsPopUp(tags, CmailMsg());\r
11707     free(tags);\r
11708 }\r
11709 \r
11710 /* end button procedures */\r
11711 \r
11712 void\r
11713 PrintPosition(fp, move)\r
11714      FILE *fp;\r
11715      int move;\r
11716 {\r
11717     int i, j;\r
11718     \r
11719     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
11720         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
11721             char c = PieceToChar(boards[move][i][j]);\r
11722             fputc(c == 'x' ? '.' : c, fp);\r
11723             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);\r
11724         }\r
11725     }\r
11726     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))\r
11727       fprintf(fp, "white to play\n");\r
11728     else\r
11729       fprintf(fp, "black to play\n");\r
11730 }\r
11731 \r
11732 void\r
11733 PrintOpponents(fp)\r
11734      FILE *fp;\r
11735 {\r
11736     if (gameInfo.white != NULL) {\r
11737         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);\r
11738     } else {\r
11739         fprintf(fp, "\n");\r
11740     }\r
11741 }\r
11742 \r
11743 /* Find last component of program's own name, using some heuristics */\r
11744 void\r
11745 TidyProgramName(prog, host, buf)\r
11746      char *prog, *host, buf[MSG_SIZ];\r
11747 {\r
11748     char *p, *q;\r
11749     int local = (strcmp(host, "localhost") == 0);\r
11750     while (!local && (p = strchr(prog, ';')) != NULL) {\r
11751         p++;\r
11752         while (*p == ' ') p++;\r
11753         prog = p;\r
11754     }\r
11755     if (*prog == '"' || *prog == '\'') {\r
11756         q = strchr(prog + 1, *prog);\r
11757     } else {\r
11758         q = strchr(prog, ' ');\r
11759     }\r
11760     if (q == NULL) q = prog + strlen(prog);\r
11761     p = q;\r
11762     while (p >= prog && *p != '/' && *p != '\\') p--;\r
11763     p++;\r
11764     if(p == prog && *p == '"') p++;\r
11765     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;\r
11766     memcpy(buf, p, q - p);\r
11767     buf[q - p] = NULLCHAR;\r
11768     if (!local) {\r
11769         strcat(buf, "@");\r
11770         strcat(buf, host);\r
11771     }\r
11772 }\r
11773 \r
11774 char *\r
11775 TimeControlTagValue()\r
11776 {\r
11777     char buf[MSG_SIZ];\r
11778     if (!appData.clockMode) {\r
11779         strcpy(buf, "-");\r
11780     } else if (movesPerSession > 0) {\r
11781         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);\r
11782     } else if (timeIncrement == 0) {\r
11783         sprintf(buf, "%ld", timeControl/1000);\r
11784     } else {\r
11785         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);\r
11786     }\r
11787     return StrSave(buf);\r
11788 }\r
11789 \r
11790 void\r
11791 SetGameInfo()\r
11792 {\r
11793     /* This routine is used only for certain modes */\r
11794     VariantClass v = gameInfo.variant;\r
11795     ClearGameInfo(&gameInfo);\r
11796     gameInfo.variant = v;\r
11797 \r
11798     switch (gameMode) {\r
11799       case MachinePlaysWhite:\r
11800         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11801         gameInfo.site = StrSave(HostName());\r
11802         gameInfo.date = PGNDate();\r
11803         gameInfo.round = StrSave("-");\r
11804         gameInfo.white = StrSave(first.tidy);\r
11805         gameInfo.black = StrSave(UserName());\r
11806         gameInfo.timeControl = TimeControlTagValue();\r
11807         break;\r
11808 \r
11809       case MachinePlaysBlack:\r
11810         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11811         gameInfo.site = StrSave(HostName());\r
11812         gameInfo.date = PGNDate();\r
11813         gameInfo.round = StrSave("-");\r
11814         gameInfo.white = StrSave(UserName());\r
11815         gameInfo.black = StrSave(first.tidy);\r
11816         gameInfo.timeControl = TimeControlTagValue();\r
11817         break;\r
11818 \r
11819       case TwoMachinesPlay:\r
11820         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11821         gameInfo.site = StrSave(HostName());\r
11822         gameInfo.date = PGNDate();\r
11823         if (matchGame > 0) {\r
11824             char buf[MSG_SIZ];\r
11825             sprintf(buf, "%d", matchGame);\r
11826             gameInfo.round = StrSave(buf);\r
11827         } else {\r
11828             gameInfo.round = StrSave("-");\r
11829         }\r
11830         if (first.twoMachinesColor[0] == 'w') {\r
11831             gameInfo.white = StrSave(first.tidy);\r
11832             gameInfo.black = StrSave(second.tidy);\r
11833         } else {\r
11834             gameInfo.white = StrSave(second.tidy);\r
11835             gameInfo.black = StrSave(first.tidy);\r
11836         }\r
11837         gameInfo.timeControl = TimeControlTagValue();\r
11838         break;\r
11839 \r
11840       case EditGame:\r
11841         gameInfo.event = StrSave("Edited game");\r
11842         gameInfo.site = StrSave(HostName());\r
11843         gameInfo.date = PGNDate();\r
11844         gameInfo.round = StrSave("-");\r
11845         gameInfo.white = StrSave("-");\r
11846         gameInfo.black = StrSave("-");\r
11847         break;\r
11848 \r
11849       case EditPosition:\r
11850         gameInfo.event = StrSave("Edited position");\r
11851         gameInfo.site = StrSave(HostName());\r
11852         gameInfo.date = PGNDate();\r
11853         gameInfo.round = StrSave("-");\r
11854         gameInfo.white = StrSave("-");\r
11855         gameInfo.black = StrSave("-");\r
11856         break;\r
11857 \r
11858       case IcsPlayingWhite:\r
11859       case IcsPlayingBlack:\r
11860       case IcsObserving:\r
11861       case IcsExamining:\r
11862         break;\r
11863 \r
11864       case PlayFromGameFile:\r
11865         gameInfo.event = StrSave("Game from non-PGN file");\r
11866         gameInfo.site = StrSave(HostName());\r
11867         gameInfo.date = PGNDate();\r
11868         gameInfo.round = StrSave("-");\r
11869         gameInfo.white = StrSave("?");\r
11870         gameInfo.black = StrSave("?");\r
11871         break;\r
11872 \r
11873       default:\r
11874         break;\r
11875     }\r
11876 }\r
11877 \r
11878 void\r
11879 ReplaceComment(index, text)\r
11880      int index;\r
11881      char *text;\r
11882 {\r
11883     int len;\r
11884 \r
11885     while (*text == '\n') text++;\r
11886     len = strlen(text);\r
11887     while (len > 0 && text[len - 1] == '\n') len--;\r
11888 \r
11889     if (commentList[index] != NULL)\r
11890       free(commentList[index]);\r
11891 \r
11892     if (len == 0) {\r
11893         commentList[index] = NULL;\r
11894         return;\r
11895     }\r
11896     commentList[index] = (char *) malloc(len + 2);\r
11897     strncpy(commentList[index], text, len);\r
11898     commentList[index][len] = '\n';\r
11899     commentList[index][len + 1] = NULLCHAR;\r
11900 }\r
11901 \r
11902 void\r
11903 CrushCRs(text)\r
11904      char *text;\r
11905 {\r
11906   char *p = text;\r
11907   char *q = text;\r
11908   char ch;\r
11909 \r
11910   do {\r
11911     ch = *p++;\r
11912     if (ch == '\r') continue;\r
11913     *q++ = ch;\r
11914   } while (ch != '\0');\r
11915 }\r
11916 \r
11917 void\r
11918 AppendComment(index, text)\r
11919      int index;\r
11920      char *text;\r
11921 {\r
11922     int oldlen, len;\r
11923     char *old;\r
11924 \r
11925     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */\r
11926 \r
11927     CrushCRs(text);\r
11928     while (*text == '\n') text++;\r
11929     len = strlen(text);\r
11930     while (len > 0 && text[len - 1] == '\n') len--;\r
11931 \r
11932     if (len == 0) return;\r
11933 \r
11934     if (commentList[index] != NULL) {\r
11935         old = commentList[index];\r
11936         oldlen = strlen(old);\r
11937         commentList[index] = (char *) malloc(oldlen + len + 2);\r
11938         strcpy(commentList[index], old);\r
11939         free(old);\r
11940         strncpy(&commentList[index][oldlen], text, len);\r
11941         commentList[index][oldlen + len] = '\n';\r
11942         commentList[index][oldlen + len + 1] = NULLCHAR;\r
11943     } else {\r
11944         commentList[index] = (char *) malloc(len + 2);\r
11945         strncpy(commentList[index], text, len);\r
11946         commentList[index][len] = '\n';\r
11947         commentList[index][len + 1] = NULLCHAR;\r
11948     }\r
11949 }\r
11950 \r
11951 static char * FindStr( char * text, char * sub_text )\r
11952 {\r
11953     char * result = strstr( text, sub_text );\r
11954 \r
11955     if( result != NULL ) {\r
11956         result += strlen( sub_text );\r
11957     }\r
11958 \r
11959     return result;\r
11960 }\r
11961 \r
11962 /* [AS] Try to extract PV info from PGN comment */\r
11963 /* [HGM] PV time: and then remove it, to prevent it appearing twice */\r
11964 char *GetInfoFromComment( int index, char * text )\r
11965 {\r
11966     char * sep = text;\r
11967 \r
11968     if( text != NULL && index > 0 ) {\r
11969         int score = 0;\r
11970         int depth = 0;\r
11971         int time = -1, sec = 0, deci;\r
11972         char * s_eval = FindStr( text, "[%eval " );\r
11973         char * s_emt = FindStr( text, "[%emt " );\r
11974 \r
11975         if( s_eval != NULL || s_emt != NULL ) {\r
11976             /* New style */\r
11977             char delim;\r
11978 \r
11979             if( s_eval != NULL ) {\r
11980                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {\r
11981                     return text;\r
11982                 }\r
11983 \r
11984                 if( delim != ']' ) {\r
11985                     return text;\r
11986                 }\r
11987             }\r
11988 \r
11989             if( s_emt != NULL ) {\r
11990             }\r
11991         }\r
11992         else {\r
11993             /* We expect something like: [+|-]nnn.nn/dd */\r
11994             int score_lo = 0;\r
11995 \r
11996             sep = strchr( text, '/' );\r
11997             if( sep == NULL || sep < (text+4) ) {\r
11998                 return text;\r
11999             }\r
12000 \r
12001             time = -1; sec = -1; deci = -1;\r
12002             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&\r
12003                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&\r
12004                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&\r
12005                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {\r
12006                 return text;\r
12007             }\r
12008 \r
12009             if( score_lo < 0 || score_lo >= 100 ) {\r
12010                 return text;\r
12011             }\r
12012 \r
12013             if(sec >= 0) time = 600*time + 10*sec; else\r
12014             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec\r
12015 \r
12016             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;\r
12017 \r
12018             /* [HGM] PV time: now locate end of PV info */\r
12019             while( *++sep >= '0' && *sep <= '9'); // strip depth\r
12020             if(time >= 0)\r
12021             while( *++sep >= '0' && *sep <= '9'); // strip time\r
12022             if(sec >= 0)\r
12023             while( *++sep >= '0' && *sep <= '9'); // strip seconds\r
12024             if(deci >= 0)\r
12025             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds\r
12026             while(*sep == ' ') sep++;\r
12027         }\r
12028 \r
12029         if( depth <= 0 ) {\r
12030             return text;\r
12031         }\r
12032 \r
12033         if( time < 0 ) {\r
12034             time = -1;\r
12035         }\r
12036 \r
12037         pvInfoList[index-1].depth = depth;\r
12038         pvInfoList[index-1].score = score;\r
12039         pvInfoList[index-1].time  = 10*time; // centi-sec\r
12040     }\r
12041     return sep;\r
12042 }\r
12043 \r
12044 void\r
12045 SendToProgram(message, cps)\r
12046      char *message;\r
12047      ChessProgramState *cps;\r
12048 {\r
12049     int count, outCount, error;\r
12050     char buf[MSG_SIZ];\r
12051 \r
12052     if (cps->pr == NULL) return;\r
12053     Attention(cps);\r
12054     \r
12055     if (appData.debugMode) {\r
12056         TimeMark now;\r
12057         GetTimeMark(&now);\r
12058         fprintf(debugFP, "%ld >%-6s: %s", \r
12059                 SubtractTimeMarks(&now, &programStartTime),\r
12060                 cps->which, message);\r
12061     }\r
12062     \r
12063     count = strlen(message);\r
12064     outCount = OutputToProcess(cps->pr, message, count, &error);\r
12065     if (outCount < count && !exiting \r
12066                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */\r
12067         sprintf(buf, _("Error writing to %s chess program"), cps->which);\r
12068         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
12069             if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
12070                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
12071                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);\r
12072             } else {\r
12073                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
12074             }\r
12075             gameInfo.resultDetails = buf;\r
12076         }\r
12077         DisplayFatalError(buf, error, 1);\r
12078     }\r
12079 }\r
12080 \r
12081 void\r
12082 ReceiveFromProgram(isr, closure, message, count, error)\r
12083      InputSourceRef isr;\r
12084      VOIDSTAR closure;\r
12085      char *message;\r
12086      int count;\r
12087      int error;\r
12088 {\r
12089     char *end_str;\r
12090     char buf[MSG_SIZ];\r
12091     ChessProgramState *cps = (ChessProgramState *)closure;\r
12092 \r
12093     if (isr != cps->isr) return; /* Killed intentionally */\r
12094     if (count <= 0) {\r
12095         if (count == 0) {\r
12096             sprintf(buf,\r
12097                     _("Error: %s chess program (%s) exited unexpectedly"),\r
12098                     cps->which, cps->program);\r
12099         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
12100                 if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
12101                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
12102                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);\r
12103                 } else {\r
12104                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
12105                 }\r
12106                 gameInfo.resultDetails = buf;\r
12107             }\r
12108             RemoveInputSource(cps->isr);\r
12109             DisplayFatalError(buf, 0, 1);\r
12110         } else {\r
12111             sprintf(buf,\r
12112                     _("Error reading from %s chess program (%s)"),\r
12113                     cps->which, cps->program);\r
12114             RemoveInputSource(cps->isr);\r
12115 \r
12116             /* [AS] Program is misbehaving badly... kill it */\r
12117             if( count == -2 ) {\r
12118                 DestroyChildProcess( cps->pr, 9 );\r
12119                 cps->pr = NoProc;\r
12120             }\r
12121 \r
12122             DisplayFatalError(buf, error, 1);\r
12123         }\r
12124         return;\r
12125     }\r
12126     \r
12127     if ((end_str = strchr(message, '\r')) != NULL)\r
12128       *end_str = NULLCHAR;\r
12129     if ((end_str = strchr(message, '\n')) != NULL)\r
12130       *end_str = NULLCHAR;\r
12131     \r
12132     if (appData.debugMode) {\r
12133         TimeMark now; int print = 1;\r
12134         char *quote = ""; char c; int i;\r
12135 \r
12136         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */\r
12137                 char start = message[0];\r
12138                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing\r
12139                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && \r
12140                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&\r
12141                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&\r
12142                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&\r
12143                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&\r
12144                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')\r
12145                         { quote = "# "; print = (appData.engineComments == 2); }\r
12146                 message[0] = start; // restore original message\r
12147         }\r
12148         if(print) {\r
12149                 GetTimeMark(&now);\r
12150                 fprintf(debugFP, "%ld <%-6s: %s%s\n", \r
12151                         SubtractTimeMarks(&now, &programStartTime), cps->which, \r
12152                         quote,\r
12153                         message);\r
12154         }\r
12155     }\r
12156 \r
12157     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */\r
12158     if (appData.icsEngineAnalyze) {\r
12159         if (strstr(message, "whisper") != NULL ||\r
12160              strstr(message, "kibitz") != NULL || \r
12161             strstr(message, "tellics") != NULL) return;\r
12162     }\r
12163 \r
12164     HandleMachineMove(message, cps);\r
12165 }\r
12166 \r
12167 \r
12168 void\r
12169 SendTimeControl(cps, mps, tc, inc, sd, st)\r
12170      ChessProgramState *cps;\r
12171      int mps, inc, sd, st;\r
12172      long tc;\r
12173 {\r
12174     char buf[MSG_SIZ];\r
12175     int seconds;\r
12176 \r
12177     if( timeControl_2 > 0 ) {\r
12178         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {\r
12179             tc = timeControl_2;\r
12180         }\r
12181     }\r
12182     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */\r
12183     inc /= cps->timeOdds;\r
12184     st  /= cps->timeOdds;\r
12185 \r
12186     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */\r
12187 \r
12188     if (st > 0) {\r
12189       /* Set exact time per move, normally using st command */\r
12190       if (cps->stKludge) {\r
12191         /* GNU Chess 4 has no st command; uses level in a nonstandard way */\r
12192         seconds = st % 60;\r
12193         if (seconds == 0) {\r
12194           sprintf(buf, "level 1 %d\n", st/60);\r
12195         } else {\r
12196           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);\r
12197         }\r
12198       } else {\r
12199         sprintf(buf, "st %d\n", st);\r
12200       }\r
12201     } else {\r
12202       /* Set conventional or incremental time control, using level command */\r
12203       if (seconds == 0) {\r
12204         /* Note old gnuchess bug -- minutes:seconds used to not work.\r
12205            Fixed in later versions, but still avoid :seconds\r
12206            when seconds is 0. */\r
12207         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);\r
12208       } else {\r
12209         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,\r
12210                 seconds, inc/1000);\r
12211       }\r
12212     }\r
12213     SendToProgram(buf, cps);\r
12214 \r
12215     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */\r
12216     /* Orthogonally, limit search to given depth */\r
12217     if (sd > 0) {\r
12218       if (cps->sdKludge) {\r
12219         sprintf(buf, "depth\n%d\n", sd);\r
12220       } else {\r
12221         sprintf(buf, "sd %d\n", sd);\r
12222       }\r
12223       SendToProgram(buf, cps);\r
12224     }\r
12225 \r
12226     if(cps->nps > 0) { /* [HGM] nps */\r
12227         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!\r
12228         else {\r
12229                 sprintf(buf, "nps %d\n", cps->nps);\r
12230               SendToProgram(buf, cps);\r
12231         }\r
12232     }\r
12233 }\r
12234 \r
12235 ChessProgramState *WhitePlayer()\r
12236 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */\r
12237 {\r
12238     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || \r
12239        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)\r
12240         return &second;\r
12241     return &first;\r
12242 }\r
12243 \r
12244 void\r
12245 SendTimeRemaining(cps, machineWhite)\r
12246      ChessProgramState *cps;\r
12247      int /*boolean*/ machineWhite;\r
12248 {\r
12249     char message[MSG_SIZ];\r
12250     long time, otime;\r
12251 \r
12252     /* Note: this routine must be called when the clocks are stopped\r
12253        or when they have *just* been set or switched; otherwise\r
12254        it will be off by the time since the current tick started.\r
12255     */\r
12256     if (machineWhite) {\r
12257         time = whiteTimeRemaining / 10;\r
12258         otime = blackTimeRemaining / 10;\r
12259     } else {\r
12260         time = blackTimeRemaining / 10;\r
12261         otime = whiteTimeRemaining / 10;\r
12262     }\r
12263     /* [HGM] translate opponent's time by time-odds factor */\r
12264     otime = (otime * cps->other->timeOdds) / cps->timeOdds;\r
12265     if (appData.debugMode) {\r
12266         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);\r
12267     }\r
12268 \r
12269     if (time <= 0) time = 1;\r
12270     if (otime <= 0) otime = 1;\r
12271     \r
12272     sprintf(message, "time %ld\n", time);\r
12273     SendToProgram(message, cps);\r
12274 \r
12275     sprintf(message, "otim %ld\n", otime);\r
12276     SendToProgram(message, cps);\r
12277 }\r
12278 \r
12279 int\r
12280 BoolFeature(p, name, loc, cps)\r
12281      char **p;\r
12282      char *name;\r
12283      int *loc;\r
12284      ChessProgramState *cps;\r
12285 {\r
12286   char buf[MSG_SIZ];\r
12287   int len = strlen(name);\r
12288   int val;\r
12289   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
12290     (*p) += len + 1;\r
12291     sscanf(*p, "%d", &val);\r
12292     *loc = (val != 0);\r
12293     while (**p && **p != ' ') (*p)++;\r
12294     sprintf(buf, "accepted %s\n", name);\r
12295     SendToProgram(buf, cps);\r
12296     return TRUE;\r
12297   }\r
12298   return FALSE;\r
12299 }\r
12300 \r
12301 int\r
12302 IntFeature(p, name, loc, cps)\r
12303      char **p;\r
12304      char *name;\r
12305      int *loc;\r
12306      ChessProgramState *cps;\r
12307 {\r
12308   char buf[MSG_SIZ];\r
12309   int len = strlen(name);\r
12310   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
12311     (*p) += len + 1;\r
12312     sscanf(*p, "%d", loc);\r
12313     while (**p && **p != ' ') (*p)++;\r
12314     sprintf(buf, "accepted %s\n", name);\r
12315     SendToProgram(buf, cps);\r
12316     return TRUE;\r
12317   }\r
12318   return FALSE;\r
12319 }\r
12320 \r
12321 int\r
12322 StringFeature(p, name, loc, cps)\r
12323      char **p;\r
12324      char *name;\r
12325      char loc[];\r
12326      ChessProgramState *cps;\r
12327 {\r
12328   char buf[MSG_SIZ];\r
12329   int len = strlen(name);\r
12330   if (strncmp((*p), name, len) == 0\r
12331       && (*p)[len] == '=' && (*p)[len+1] == '\"') {\r
12332     (*p) += len + 2;\r
12333     sscanf(*p, "%[^\"]", loc);\r
12334     while (**p && **p != '\"') (*p)++;\r
12335     if (**p == '\"') (*p)++;\r
12336     sprintf(buf, "accepted %s\n", name);\r
12337     SendToProgram(buf, cps);\r
12338     return TRUE;\r
12339   }\r
12340   return FALSE;\r
12341 }\r
12342 \r
12343 int \r
12344 ParseOption(Option *opt, ChessProgramState *cps)\r
12345 // [HGM] options: process the string that defines an engine option, and determine\r
12346 // name, type, default value, and allowed value range\r
12347 {\r
12348         char *p, *q, buf[MSG_SIZ];\r
12349         int n, min = (-1)<<31, max = 1<<31, def;\r
12350 \r
12351         if(p = strstr(opt->name, " -spin ")) {\r
12352             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;\r
12353             if(max < min) max = min; // enforce consistency\r
12354             if(def < min) def = min;\r
12355             if(def > max) def = max;\r
12356             opt->value = def;\r
12357             opt->min = min;\r
12358             opt->max = max;\r
12359             opt->type = Spin;\r
12360         } else if(p = strstr(opt->name, " -string ")) {\r
12361             opt->textValue = p+9;\r
12362             opt->type = TextBox;\r
12363         } else if(p = strstr(opt->name, " -check ")) {\r
12364             if(sscanf(p, " -check %d", &def) < 1) return FALSE;\r
12365             opt->value = (def != 0);\r
12366             opt->type = CheckBox;\r
12367         } else if(p = strstr(opt->name, " -combo ")) {\r
12368             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type\r
12369             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices\r
12370             opt->value = n = 0;\r
12371             while(q = StrStr(q, " /// ")) {\r
12372                 n++; *q = 0;    // count choices, and null-terminate each of them\r
12373                 q += 5;\r
12374                 if(*q == '*') { // remember default, which is marked with * prefix\r
12375                     q++;\r
12376                     opt->value = n;\r
12377                 }\r
12378                 cps->comboList[cps->comboCnt++] = q;\r
12379             }\r
12380             cps->comboList[cps->comboCnt++] = NULL;\r
12381             opt->max = n + 1;\r
12382             opt->type = ComboBox;\r
12383         } else if(p = strstr(opt->name, " -button")) {\r
12384             opt->type = Button;\r
12385         } else if(p = strstr(opt->name, " -save")) {\r
12386             opt->type = SaveButton;\r
12387         } else return FALSE;\r
12388         *p = 0; // terminate option name\r
12389         // now look if the command-line options define a setting for this engine option.\r
12390         if(cps->optionSettings && cps->optionSettings[0])\r
12391             p = strstr(cps->optionSettings, opt->name); else p = NULL;\r
12392         if(p && (p == cps->optionSettings || p[-1] == ',')) {\r
12393                 sprintf(buf, "option %s", p);\r
12394                 if(p = strstr(buf, ",")) *p = 0;\r
12395                 strcat(buf, "\n");\r
12396                 SendToProgram(buf, cps);\r
12397         }\r
12398         return TRUE;\r
12399 }\r
12400 \r
12401 void\r
12402 FeatureDone(cps, val)\r
12403      ChessProgramState* cps;\r
12404      int val;\r
12405 {\r
12406   DelayedEventCallback cb = GetDelayedEvent();\r
12407   if ((cb == InitBackEnd3 && cps == &first) ||\r
12408       (cb == TwoMachinesEventIfReady && cps == &second)) {\r
12409     CancelDelayedEvent();\r
12410     ScheduleDelayedEvent(cb, val ? 1 : 3600000);\r
12411   }\r
12412   cps->initDone = val;\r
12413 }\r
12414 \r
12415 /* Parse feature command from engine */\r
12416 void\r
12417 ParseFeatures(args, cps)\r
12418      char* args;\r
12419      ChessProgramState *cps;  \r
12420 {\r
12421   char *p = args;\r
12422   char *q;\r
12423   int val;\r
12424   char buf[MSG_SIZ];\r
12425 \r
12426   for (;;) {\r
12427     while (*p == ' ') p++;\r
12428     if (*p == NULLCHAR) return;\r
12429 \r
12430     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;\r
12431     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    \r
12432     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    \r
12433     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    \r
12434     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    \r
12435     if (BoolFeature(&p, "reuse", &val, cps)) {\r
12436       /* Engine can disable reuse, but can't enable it if user said no */\r
12437       if (!val) cps->reuse = FALSE;\r
12438       continue;\r
12439     }\r
12440     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;\r
12441     if (StringFeature(&p, "myname", &cps->tidy, cps)) {\r
12442       if (gameMode == TwoMachinesPlay) {\r
12443         DisplayTwoMachinesTitle();\r
12444       } else {\r
12445         DisplayTitle("");\r
12446       }\r
12447       continue;\r
12448     }\r
12449     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;\r
12450     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;\r
12451     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;\r
12452     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;\r
12453     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;\r
12454     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;\r
12455     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;\r
12456     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;\r
12457     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */\r
12458     if (IntFeature(&p, "done", &val, cps)) {\r
12459       FeatureDone(cps, val);\r
12460       continue;\r
12461     }\r
12462     /* Added by Tord: */\r
12463     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;\r
12464     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;\r
12465     /* End of additions by Tord */\r
12466 \r
12467     /* [HGM] added features: */\r
12468     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;\r
12469     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;\r
12470     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;\r
12471     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;\r
12472     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;\r
12473     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;\r
12474     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {\r
12475         ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature\r
12476         if(cps->nrOptions >= MAX_OPTIONS) {\r
12477             cps->nrOptions--;\r
12478             sprintf(buf, "%s engine has too many options\n", cps->which);\r
12479             DisplayError(buf, 0);\r
12480         }\r
12481         continue;\r
12482     }\r
12483     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;\r
12484     /* End of additions by HGM */\r
12485 \r
12486     /* unknown feature: complain and skip */\r
12487     q = p;\r
12488     while (*q && *q != '=') q++;\r
12489     sprintf(buf, "rejected %.*s\n", q-p, p);\r
12490     SendToProgram(buf, cps);\r
12491     p = q;\r
12492     if (*p == '=') {\r
12493       p++;\r
12494       if (*p == '\"') {\r
12495         p++;\r
12496         while (*p && *p != '\"') p++;\r
12497         if (*p == '\"') p++;\r
12498       } else {\r
12499         while (*p && *p != ' ') p++;\r
12500       }\r
12501     }\r
12502   }\r
12503 \r
12504 }\r
12505 \r
12506 void\r
12507 PeriodicUpdatesEvent(newState)\r
12508      int newState;\r
12509 {\r
12510     if (newState == appData.periodicUpdates)\r
12511       return;\r
12512 \r
12513     appData.periodicUpdates=newState;\r
12514 \r
12515     /* Display type changes, so update it now */\r
12516     DisplayAnalysis();\r
12517 \r
12518     /* Get the ball rolling again... */\r
12519     if (newState) {\r
12520         AnalysisPeriodicEvent(1);\r
12521         StartAnalysisClock();\r
12522     }\r
12523 }\r
12524 \r
12525 void\r
12526 PonderNextMoveEvent(newState)\r
12527      int newState;\r
12528 {\r
12529     if (newState == appData.ponderNextMove) return;\r
12530     if (gameMode == EditPosition) EditPositionDone();\r
12531     if (newState) {\r
12532         SendToProgram("hard\n", &first);\r
12533         if (gameMode == TwoMachinesPlay) {\r
12534             SendToProgram("hard\n", &second);\r
12535         }\r
12536     } else {\r
12537         SendToProgram("easy\n", &first);\r
12538         thinkOutput[0] = NULLCHAR;\r
12539         if (gameMode == TwoMachinesPlay) {\r
12540             SendToProgram("easy\n", &second);\r
12541         }\r
12542     }\r
12543     appData.ponderNextMove = newState;\r
12544 }\r
12545 \r
12546 void\r
12547 NewSettingEvent(option, command, value)\r
12548      char *command;\r
12549      int option, value;\r
12550 {\r
12551     char buf[MSG_SIZ];\r
12552 \r
12553     if (gameMode == EditPosition) EditPositionDone();\r
12554     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);\r
12555     SendToProgram(buf, &first);\r
12556     if (gameMode == TwoMachinesPlay) {\r
12557         SendToProgram(buf, &second);\r
12558     }\r
12559 }\r
12560 \r
12561 void\r
12562 ShowThinkingEvent()\r
12563 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup\r
12564 {\r
12565     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated\r
12566     int newState = appData.showThinking\r
12567         // [HGM] thinking: other features now need thinking output as well\r
12568         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();\r
12569     \r
12570     if (oldState == newState) return;\r
12571     oldState = newState;\r
12572     if (gameMode == EditPosition) EditPositionDone();\r
12573     if (oldState) {\r
12574         SendToProgram("post\n", &first);\r
12575         if (gameMode == TwoMachinesPlay) {\r
12576             SendToProgram("post\n", &second);\r
12577         }\r
12578     } else {\r
12579         SendToProgram("nopost\n", &first);\r
12580         thinkOutput[0] = NULLCHAR;\r
12581         if (gameMode == TwoMachinesPlay) {\r
12582             SendToProgram("nopost\n", &second);\r
12583         }\r
12584     }\r
12585 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!\r
12586 }\r
12587 \r
12588 void\r
12589 AskQuestionEvent(title, question, replyPrefix, which)\r
12590      char *title; char *question; char *replyPrefix; char *which;\r
12591 {\r
12592   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;\r
12593   if (pr == NoProc) return;\r
12594   AskQuestion(title, question, replyPrefix, pr);\r
12595 }\r
12596 \r
12597 void\r
12598 DisplayMove(moveNumber)\r
12599      int moveNumber;\r
12600 {\r
12601     char message[MSG_SIZ];\r
12602     char res[MSG_SIZ];\r
12603     char cpThinkOutput[MSG_SIZ];\r
12604 \r
12605     if(appData.noGUI) return; // [HGM] fast: suppress display of moves\r
12606     \r
12607     if (moveNumber == forwardMostMove - 1 || \r
12608         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
12609 \r
12610         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));\r
12611 \r
12612         if (strchr(cpThinkOutput, '\n')) {\r
12613             *strchr(cpThinkOutput, '\n') = NULLCHAR;\r
12614         }\r
12615     } else {\r
12616         *cpThinkOutput = NULLCHAR;\r
12617     }\r
12618 \r
12619     /* [AS] Hide thinking from human user */\r
12620     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {\r
12621         *cpThinkOutput = NULLCHAR;\r
12622         if( thinkOutput[0] != NULLCHAR ) {\r
12623             int i;\r
12624 \r
12625             for( i=0; i<=hiddenThinkOutputState; i++ ) {\r
12626                 cpThinkOutput[i] = '.';\r
12627             }\r
12628             cpThinkOutput[i] = NULLCHAR;\r
12629             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;\r
12630         }\r
12631     }\r
12632 \r
12633     if (moveNumber == forwardMostMove - 1 &&\r
12634         gameInfo.resultDetails != NULL) {\r
12635         if (gameInfo.resultDetails[0] == NULLCHAR) {\r
12636             sprintf(res, " %s", PGNResult(gameInfo.result));\r
12637         } else {\r
12638             sprintf(res, " {%s} %s",\r
12639                     gameInfo.resultDetails, PGNResult(gameInfo.result));\r
12640         }\r
12641     } else {\r
12642         res[0] = NULLCHAR;\r
12643     }\r
12644 \r
12645     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
12646         DisplayMessage(res, cpThinkOutput);\r
12647     } else {\r
12648         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,\r
12649                 WhiteOnMove(moveNumber) ? " " : ".. ",\r
12650                 parseList[moveNumber], res);\r
12651         DisplayMessage(message, cpThinkOutput);\r
12652     }\r
12653 }\r
12654 \r
12655 void\r
12656 DisplayAnalysisText(text)\r
12657      char *text;\r
12658 {\r
12659     char buf[MSG_SIZ];\r
12660 \r
12661     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile \r
12662                || appData.icsEngineAnalyze) {\r
12663         sprintf(buf, "Analysis (%s)", first.tidy);\r
12664         AnalysisPopUp(buf, text);\r
12665     }\r
12666 }\r
12667 \r
12668 static int\r
12669 only_one_move(str)\r
12670      char *str;\r
12671 {\r
12672     while (*str && isspace(*str)) ++str;\r
12673     while (*str && !isspace(*str)) ++str;\r
12674     if (!*str) return 1;\r
12675     while (*str && isspace(*str)) ++str;\r
12676     if (!*str) return 1;\r
12677     return 0;\r
12678 }\r
12679 \r
12680 void\r
12681 DisplayAnalysis()\r
12682 {\r
12683     char buf[MSG_SIZ];\r
12684     char lst[MSG_SIZ / 2];\r
12685     double nps;\r
12686     static char *xtra[] = { "", " (--)", " (++)" };\r
12687     int h, m, s, cs;\r
12688   \r
12689     if (programStats.time == 0) {\r
12690         programStats.time = 1;\r
12691     }\r
12692   \r
12693     if (programStats.got_only_move) {\r
12694         safeStrCpy(buf, programStats.movelist, sizeof(buf));\r
12695     } else {\r
12696         safeStrCpy( lst, programStats.movelist, sizeof(lst));\r
12697 \r
12698         nps = (u64ToDouble(programStats.nodes) /\r
12699              ((double)programStats.time /100.0));\r
12700 \r
12701         cs = programStats.time % 100;\r
12702         s = programStats.time / 100;\r
12703         h = (s / (60*60));\r
12704         s = s - h*60*60;\r
12705         m = (s/60);\r
12706         s = s - m*60;\r
12707 \r
12708         if (programStats.moves_left > 0 && appData.periodicUpdates) {\r
12709           if (programStats.move_name[0] != NULLCHAR) {\r
12710             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12711                     programStats.depth,\r
12712                     programStats.nr_moves-programStats.moves_left,\r
12713                     programStats.nr_moves, programStats.move_name,\r
12714                     ((float)programStats.score)/100.0, 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           } else {\r
12719             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12720                     programStats.depth,\r
12721                     programStats.nr_moves-programStats.moves_left,\r
12722                     programStats.nr_moves, ((float)programStats.score)/100.0,\r
12723                     lst,\r
12724                     only_one_move(lst)?\r
12725                     xtra[programStats.got_fail] : "",\r
12726                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12727           }\r
12728         } else {\r
12729             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12730                     programStats.depth,\r
12731                     ((float)programStats.score)/100.0,\r
12732                     lst,\r
12733                     only_one_move(lst)?\r
12734                     xtra[programStats.got_fail] : "",\r
12735                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12736         }\r
12737     }\r
12738     DisplayAnalysisText(buf);\r
12739 }\r
12740 \r
12741 void\r
12742 DisplayComment(moveNumber, text)\r
12743      int moveNumber;\r
12744      char *text;\r
12745 {\r
12746     char title[MSG_SIZ];\r
12747     char buf[8000]; // comment can be long!\r
12748     int score, depth;\r
12749 \r
12750     if( appData.autoDisplayComment ) {\r
12751         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
12752             strcpy(title, "Comment");\r
12753         } else {\r
12754             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,\r
12755                     WhiteOnMove(moveNumber) ? " " : ".. ",\r
12756                     parseList[moveNumber]);\r
12757         }\r
12758     } else title[0] = 0;\r
12759 \r
12760     // [HGM] PV info: display PV info together with (or as) comment\r
12761     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {\r
12762         if(text == NULL) text = "";                                           \r
12763         score = pvInfoList[moveNumber].score;\r
12764         sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,\r
12765                               depth, (pvInfoList[moveNumber].time+50)/100, text);\r
12766         CommentPopUp(title, buf);\r
12767     } else\r
12768     if (text != NULL)\r
12769         CommentPopUp(title, text);\r
12770 }\r
12771 \r
12772 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it\r
12773  * might be busy thinking or pondering.  It can be omitted if your\r
12774  * gnuchess is configured to stop thinking immediately on any user\r
12775  * input.  However, that gnuchess feature depends on the FIONREAD\r
12776  * ioctl, which does not work properly on some flavors of Unix.\r
12777  */\r
12778 void\r
12779 Attention(cps)\r
12780      ChessProgramState *cps;\r
12781 {\r
12782 #if ATTENTION\r
12783     if (!cps->useSigint) return;\r
12784     if (appData.noChessProgram || (cps->pr == NoProc)) return;\r
12785     switch (gameMode) {\r
12786       case MachinePlaysWhite:\r
12787       case MachinePlaysBlack:\r
12788       case TwoMachinesPlay:\r
12789       case IcsPlayingWhite:\r
12790       case IcsPlayingBlack:\r
12791       case AnalyzeMode:\r
12792       case AnalyzeFile:\r
12793         /* Skip if we know it isn't thinking */\r
12794         if (!cps->maybeThinking) return;\r
12795         if (appData.debugMode)\r
12796           fprintf(debugFP, "Interrupting %s\n", cps->which);\r
12797         InterruptChildProcess(cps->pr);\r
12798         cps->maybeThinking = FALSE;\r
12799         break;\r
12800       default:\r
12801         break;\r
12802     }\r
12803 #endif /*ATTENTION*/\r
12804 }\r
12805 \r
12806 int\r
12807 CheckFlags()\r
12808 {\r
12809     if (whiteTimeRemaining <= 0) {\r
12810         if (!whiteFlag) {\r
12811             whiteFlag = TRUE;\r
12812             if (appData.icsActive) {\r
12813                 if (appData.autoCallFlag &&\r
12814                     gameMode == IcsPlayingBlack && !blackFlag) {\r
12815                   SendToICS(ics_prefix);\r
12816                   SendToICS("flag\n");\r
12817                 }\r
12818             } else {\r
12819                 if (blackFlag) {\r
12820                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));\r
12821                 } else {\r
12822                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));\r
12823                     if (appData.autoCallFlag) {\r
12824                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);\r
12825                         return TRUE;\r
12826                     }\r
12827                 }\r
12828             }\r
12829         }\r
12830     }\r
12831     if (blackTimeRemaining <= 0) {\r
12832         if (!blackFlag) {\r
12833             blackFlag = TRUE;\r
12834             if (appData.icsActive) {\r
12835                 if (appData.autoCallFlag &&\r
12836                     gameMode == IcsPlayingWhite && !whiteFlag) {\r
12837                   SendToICS(ics_prefix);\r
12838                   SendToICS("flag\n");\r
12839                 }\r
12840             } else {\r
12841                 if (whiteFlag) {\r
12842                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));\r
12843                 } else {\r
12844                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));\r
12845                     if (appData.autoCallFlag) {\r
12846                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);\r
12847                         return TRUE;\r
12848                     }\r
12849                 }\r
12850             }\r
12851         }\r
12852     }\r
12853     return FALSE;\r
12854 }\r
12855 \r
12856 void\r
12857 CheckTimeControl()\r
12858 {\r
12859     if (!appData.clockMode || appData.icsActive ||\r
12860         gameMode == PlayFromGameFile || forwardMostMove == 0) return;\r
12861 \r
12862     /*\r
12863      * add time to clocks when time control is achieved ([HGM] now also used for increment)\r
12864      */\r
12865     if ( !WhiteOnMove(forwardMostMove) )\r
12866         /* White made time control */\r
12867         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
12868         /* [HGM] time odds: correct new time quota for time odds! */\r
12869                                             / WhitePlayer()->timeOdds;\r
12870       else\r
12871         /* Black made time control */\r
12872         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
12873                                             / WhitePlayer()->other->timeOdds;\r
12874 }\r
12875 \r
12876 void\r
12877 DisplayBothClocks()\r
12878 {\r
12879     int wom = gameMode == EditPosition ?\r
12880       !blackPlaysFirst : WhiteOnMove(currentMove);\r
12881     DisplayWhiteClock(whiteTimeRemaining, wom);\r
12882     DisplayBlackClock(blackTimeRemaining, !wom);\r
12883 }\r
12884 \r
12885 \r
12886 /* Timekeeping seems to be a portability nightmare.  I think everyone\r
12887    has ftime(), but I'm really not sure, so I'm including some ifdefs\r
12888    to use other calls if you don't.  Clocks will be less accurate if\r
12889    you have neither ftime nor gettimeofday.\r
12890 */\r
12891 \r
12892 /* VS 2008 requires the #include outside of the function */\r
12893 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME\r
12894 #include <sys/timeb.h>\r
12895 #endif\r
12896 \r
12897 /* Get the current time as a TimeMark */\r
12898 void\r
12899 GetTimeMark(tm)\r
12900      TimeMark *tm;\r
12901 {\r
12902 #if HAVE_GETTIMEOFDAY\r
12903 \r
12904     struct timeval timeVal;\r
12905     struct timezone timeZone;\r
12906 \r
12907     gettimeofday(&timeVal, &timeZone);\r
12908     tm->sec = (long) timeVal.tv_sec; \r
12909     tm->ms = (int) (timeVal.tv_usec / 1000L);\r
12910 \r
12911 #else /*!HAVE_GETTIMEOFDAY*/\r
12912 #if HAVE_FTIME\r
12913 \r
12914 // include <sys/timeb.h> / moved to just above start of function\r
12915     struct timeb timeB;\r
12916 \r
12917     ftime(&timeB);\r
12918     tm->sec = (long) timeB.time;\r
12919     tm->ms = (int) timeB.millitm;\r
12920 \r
12921 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/\r
12922     tm->sec = (long) time(NULL);\r
12923     tm->ms = 0;\r
12924 #endif\r
12925 #endif\r
12926 }\r
12927 \r
12928 /* Return the difference in milliseconds between two\r
12929    time marks.  We assume the difference will fit in a long!\r
12930 */\r
12931 long\r
12932 SubtractTimeMarks(tm2, tm1)\r
12933      TimeMark *tm2, *tm1;\r
12934 {\r
12935     return 1000L*(tm2->sec - tm1->sec) +\r
12936            (long) (tm2->ms - tm1->ms);\r
12937 }\r
12938 \r
12939 \r
12940 /*\r
12941  * Code to manage the game clocks.\r
12942  *\r
12943  * In tournament play, black starts the clock and then white makes a move.\r
12944  * We give the human user a slight advantage if he is playing white---the\r
12945  * clocks don't run until he makes his first move, so it takes zero time.\r
12946  * Also, we don't account for network lag, so we could get out of sync\r
12947  * with GNU Chess's clock -- but then, referees are always right.  \r
12948  */\r
12949 \r
12950 static TimeMark tickStartTM;\r
12951 static long intendedTickLength;\r
12952 \r
12953 long\r
12954 NextTickLength(timeRemaining)\r
12955      long timeRemaining;\r
12956 {\r
12957     long nominalTickLength, nextTickLength;\r
12958 \r
12959     if (timeRemaining > 0L && timeRemaining <= 10000L)\r
12960       nominalTickLength = 100L;\r
12961     else\r
12962       nominalTickLength = 1000L;\r
12963     nextTickLength = timeRemaining % nominalTickLength;\r
12964     if (nextTickLength <= 0) nextTickLength += nominalTickLength;\r
12965 \r
12966     return nextTickLength;\r
12967 }\r
12968 \r
12969 /* Adjust clock one minute up or down */\r
12970 void\r
12971 AdjustClock(Boolean which, int dir)\r
12972 {\r
12973     if(which) blackTimeRemaining += 60000*dir;\r
12974     else      whiteTimeRemaining += 60000*dir;\r
12975     DisplayBothClocks();\r
12976 }\r
12977 \r
12978 /* Stop clocks and reset to a fresh time control */\r
12979 void\r
12980 ResetClocks() \r
12981 {\r
12982     (void) StopClockTimer();\r
12983     if (appData.icsActive) {\r
12984         whiteTimeRemaining = blackTimeRemaining = 0;\r
12985     } else { /* [HGM] correct new time quote for time odds */\r
12986         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;\r
12987         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;\r
12988     }\r
12989     if (whiteFlag || blackFlag) {\r
12990         DisplayTitle("");\r
12991         whiteFlag = blackFlag = FALSE;\r
12992     }\r
12993     DisplayBothClocks();\r
12994 }\r
12995 \r
12996 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */\r
12997 \r
12998 /* Decrement running clock by amount of time that has passed */\r
12999 void\r
13000 DecrementClocks()\r
13001 {\r
13002     long timeRemaining;\r
13003     long lastTickLength, fudge;\r
13004     TimeMark now;\r
13005 \r
13006     if (!appData.clockMode) return;\r
13007     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;\r
13008         \r
13009     GetTimeMark(&now);\r
13010 \r
13011     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13012 \r
13013     /* Fudge if we woke up a little too soon */\r
13014     fudge = intendedTickLength - lastTickLength;\r
13015     if (fudge < 0 || fudge > FUDGE) fudge = 0;\r
13016 \r
13017     if (WhiteOnMove(forwardMostMove)) {\r
13018         if(whiteNPS >= 0) lastTickLength = 0;\r
13019         timeRemaining = whiteTimeRemaining -= lastTickLength;\r
13020         DisplayWhiteClock(whiteTimeRemaining - fudge,\r
13021                           WhiteOnMove(currentMove));\r
13022     } else {\r
13023         if(blackNPS >= 0) lastTickLength = 0;\r
13024         timeRemaining = blackTimeRemaining -= lastTickLength;\r
13025         DisplayBlackClock(blackTimeRemaining - fudge,\r
13026                           !WhiteOnMove(currentMove));\r
13027     }\r
13028 \r
13029     if (CheckFlags()) return;\r
13030         \r
13031     tickStartTM = now;\r
13032     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;\r
13033     StartClockTimer(intendedTickLength);\r
13034 \r
13035     /* if the time remaining has fallen below the alarm threshold, sound the\r
13036      * alarm. if the alarm has sounded and (due to a takeback or time control\r
13037      * with increment) the time remaining has increased to a level above the\r
13038      * threshold, reset the alarm so it can sound again. \r
13039      */\r
13040     \r
13041     if (appData.icsActive && appData.icsAlarm) {\r
13042 \r
13043         /* make sure we are dealing with the user's clock */\r
13044         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||\r
13045                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))\r
13046            )) return;\r
13047 \r
13048         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {\r
13049             alarmSounded = FALSE;\r
13050         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { \r
13051             PlayAlarmSound();\r
13052             alarmSounded = TRUE;\r
13053         }\r
13054     }\r
13055 }\r
13056 \r
13057 \r
13058 /* A player has just moved, so stop the previously running\r
13059    clock and (if in clock mode) start the other one.\r
13060    We redisplay both clocks in case we're in ICS mode, because\r
13061    ICS gives us an update to both clocks after every move.\r
13062    Note that this routine is called *after* forwardMostMove\r
13063    is updated, so the last fractional tick must be subtracted\r
13064    from the color that is *not* on move now.\r
13065 */\r
13066 void\r
13067 SwitchClocks()\r
13068 {\r
13069     long lastTickLength;\r
13070     TimeMark now;\r
13071     int flagged = FALSE;\r
13072 \r
13073     GetTimeMark(&now);\r
13074 \r
13075     if (StopClockTimer() && appData.clockMode) {\r
13076         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13077         if (WhiteOnMove(forwardMostMove)) {\r
13078             if(blackNPS >= 0) lastTickLength = 0;\r
13079             blackTimeRemaining -= lastTickLength;\r
13080            /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
13081 //         if(pvInfoList[forwardMostMove-1].time == -1)\r
13082                  pvInfoList[forwardMostMove-1].time =               // use GUI time\r
13083                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;\r
13084         } else {\r
13085            if(whiteNPS >= 0) lastTickLength = 0;\r
13086            whiteTimeRemaining -= lastTickLength;\r
13087            /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
13088 //         if(pvInfoList[forwardMostMove-1].time == -1)\r
13089                  pvInfoList[forwardMostMove-1].time = \r
13090                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;\r
13091         }\r
13092         flagged = CheckFlags();\r
13093     }\r
13094     CheckTimeControl();\r
13095 \r
13096     if (flagged || !appData.clockMode) return;\r
13097 \r
13098     switch (gameMode) {\r
13099       case MachinePlaysBlack:\r
13100       case MachinePlaysWhite:\r
13101       case BeginningOfGame:\r
13102         if (pausing) return;\r
13103         break;\r
13104 \r
13105       case EditGame:\r
13106       case PlayFromGameFile:\r
13107       case IcsExamining:\r
13108         return;\r
13109 \r
13110       default:\r
13111         break;\r
13112     }\r
13113 \r
13114     tickStartTM = now;\r
13115     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
13116       whiteTimeRemaining : blackTimeRemaining);\r
13117     StartClockTimer(intendedTickLength);\r
13118 }\r
13119         \r
13120 \r
13121 /* Stop both clocks */\r
13122 void\r
13123 StopClocks()\r
13124 {       \r
13125     long lastTickLength;\r
13126     TimeMark now;\r
13127 \r
13128     if (!StopClockTimer()) return;\r
13129     if (!appData.clockMode) return;\r
13130 \r
13131     GetTimeMark(&now);\r
13132 \r
13133     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13134     if (WhiteOnMove(forwardMostMove)) {\r
13135         if(whiteNPS >= 0) lastTickLength = 0;\r
13136         whiteTimeRemaining -= lastTickLength;\r
13137         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));\r
13138     } else {\r
13139         if(blackNPS >= 0) lastTickLength = 0;\r
13140         blackTimeRemaining -= lastTickLength;\r
13141         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));\r
13142     }\r
13143     CheckFlags();\r
13144 }\r
13145         \r
13146 /* Start clock of player on move.  Time may have been reset, so\r
13147    if clock is already running, stop and restart it. */\r
13148 void\r
13149 StartClocks()\r
13150 {\r
13151     (void) StopClockTimer(); /* in case it was running already */\r
13152     DisplayBothClocks();\r
13153     if (CheckFlags()) return;\r
13154 \r
13155     if (!appData.clockMode) return;\r
13156     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;\r
13157 \r
13158     GetTimeMark(&tickStartTM);\r
13159     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
13160       whiteTimeRemaining : blackTimeRemaining);\r
13161 \r
13162    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */\r
13163     whiteNPS = blackNPS = -1; \r
13164     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'\r
13165        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white\r
13166         whiteNPS = first.nps;\r
13167     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'\r
13168        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black\r
13169         blackNPS = first.nps;\r
13170     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode\r
13171         whiteNPS = second.nps;\r
13172     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')\r
13173         blackNPS = second.nps;\r
13174     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);\r
13175 \r
13176     StartClockTimer(intendedTickLength);\r
13177 }\r
13178 \r
13179 char *\r
13180 TimeString(ms)\r
13181      long ms;\r
13182 {\r
13183     long second, minute, hour, day;\r
13184     char *sign = "";\r
13185     static char buf[32];\r
13186     \r
13187     if (ms > 0 && ms <= 9900) {\r
13188       /* convert milliseconds to tenths, rounding up */\r
13189       double tenths = floor( ((double)(ms + 99L)) / 100.00 );\r
13190 \r
13191       sprintf(buf, " %03.1f ", tenths/10.0);\r
13192       return buf;\r
13193     }\r
13194 \r
13195     /* convert milliseconds to seconds, rounding up */\r
13196     /* use floating point to avoid strangeness of integer division\r
13197        with negative dividends on many machines */\r
13198     second = (long) floor(((double) (ms + 999L)) / 1000.0);\r
13199 \r
13200     if (second < 0) {\r
13201         sign = "-";\r
13202         second = -second;\r
13203     }\r
13204     \r
13205     day = second / (60 * 60 * 24);\r
13206     second = second % (60 * 60 * 24);\r
13207     hour = second / (60 * 60);\r
13208     second = second % (60 * 60);\r
13209     minute = second / 60;\r
13210     second = second % 60;\r
13211     \r
13212     if (day > 0)\r
13213       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",\r
13214               sign, day, hour, minute, second);\r
13215     else if (hour > 0)\r
13216       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);\r
13217     else\r
13218       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);\r
13219     \r
13220     return buf;\r
13221 }\r
13222 \r
13223 \r
13224 /*\r
13225  * This is necessary because some C libraries aren't ANSI C compliant yet.\r
13226  */\r
13227 char *\r
13228 StrStr(string, match)\r
13229      char *string, *match;\r
13230 {\r
13231     int i, length;\r
13232     \r
13233     length = strlen(match);\r
13234     \r
13235     for (i = strlen(string) - length; i >= 0; i--, string++)\r
13236       if (!strncmp(match, string, length))\r
13237         return string;\r
13238     \r
13239     return NULL;\r
13240 }\r
13241 \r
13242 char *\r
13243 StrCaseStr(string, match)\r
13244      char *string, *match;\r
13245 {\r
13246     int i, j, length;\r
13247     \r
13248     length = strlen(match);\r
13249     \r
13250     for (i = strlen(string) - length; i >= 0; i--, string++) {\r
13251         for (j = 0; j < length; j++) {\r
13252             if (ToLower(match[j]) != ToLower(string[j]))\r
13253               break;\r
13254         }\r
13255         if (j == length) return string;\r
13256     }\r
13257 \r
13258     return NULL;\r
13259 }\r
13260 \r
13261 #ifndef _amigados\r
13262 int\r
13263 StrCaseCmp(s1, s2)\r
13264      char *s1, *s2;\r
13265 {\r
13266     char c1, c2;\r
13267     \r
13268     for (;;) {\r
13269         c1 = ToLower(*s1++);\r
13270         c2 = ToLower(*s2++);\r
13271         if (c1 > c2) return 1;\r
13272         if (c1 < c2) return -1;\r
13273         if (c1 == NULLCHAR) return 0;\r
13274     }\r
13275 }\r
13276 \r
13277 \r
13278 int\r
13279 ToLower(c)\r
13280      int c;\r
13281 {\r
13282     return isupper(c) ? tolower(c) : c;\r
13283 }\r
13284 \r
13285 \r
13286 int\r
13287 ToUpper(c)\r
13288      int c;\r
13289 {\r
13290     return islower(c) ? toupper(c) : c;\r
13291 }\r
13292 #endif /* !_amigados    */\r
13293 \r
13294 char *\r
13295 StrSave(s)\r
13296      char *s;\r
13297 {\r
13298     char *ret;\r
13299 \r
13300     if ((ret = (char *) malloc(strlen(s) + 1))) {\r
13301         strcpy(ret, s);\r
13302     }\r
13303     return ret;\r
13304 }\r
13305 \r
13306 char *\r
13307 StrSavePtr(s, savePtr)\r
13308      char *s, **savePtr;\r
13309 {\r
13310     if (*savePtr) {\r
13311         free(*savePtr);\r
13312     }\r
13313     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {\r
13314         strcpy(*savePtr, s);\r
13315     }\r
13316     return(*savePtr);\r
13317 }\r
13318 \r
13319 char *\r
13320 PGNDate()\r
13321 {\r
13322     time_t clock;\r
13323     struct tm *tm;\r
13324     char buf[MSG_SIZ];\r
13325 \r
13326     clock = time((time_t *)NULL);\r
13327     tm = localtime(&clock);\r
13328     sprintf(buf, "%04d.%02d.%02d",\r
13329             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);\r
13330     return StrSave(buf);\r
13331 }\r
13332 \r
13333 \r
13334 char *\r
13335 PositionToFEN(move, overrideCastling)\r
13336      int move;\r
13337      char *overrideCastling;\r
13338 {\r
13339     int i, j, fromX, fromY, toX, toY;\r
13340     int whiteToPlay;\r
13341     char buf[128];\r
13342     char *p, *q;\r
13343     int emptycount;\r
13344     ChessSquare piece;\r
13345 \r
13346     whiteToPlay = (gameMode == EditPosition) ?\r
13347       !blackPlaysFirst : (move % 2 == 0);\r
13348     p = buf;\r
13349 \r
13350     /* Piece placement data */\r
13351     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
13352         emptycount = 0;\r
13353         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
13354             if (boards[move][i][j] == EmptySquare) {\r
13355                 emptycount++;\r
13356             } else { ChessSquare piece = boards[move][i][j];\r
13357                 if (emptycount > 0) {\r
13358                     if(emptycount<10) /* [HGM] can be >= 10 */\r
13359                         *p++ = '0' + emptycount;\r
13360                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
13361                     emptycount = 0;\r
13362                 }\r
13363                 if(PieceToChar(piece) == '+') {\r
13364                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */\r
13365                     *p++ = '+';\r
13366                     piece = (ChessSquare)(DEMOTED piece);\r
13367                 } \r
13368                 *p++ = PieceToChar(piece);\r
13369                 if(p[-1] == '~') {\r
13370                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */\r
13371                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));\r
13372                     *p++ = '~';\r
13373                 }\r
13374             }\r
13375         }\r
13376         if (emptycount > 0) {\r
13377             if(emptycount<10) /* [HGM] can be >= 10 */\r
13378                 *p++ = '0' + emptycount;\r
13379             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
13380             emptycount = 0;\r
13381         }\r
13382         *p++ = '/';\r
13383     }\r
13384     *(p - 1) = ' ';\r
13385 \r
13386     /* [HGM] print Crazyhouse or Shogi holdings */\r
13387     if( gameInfo.holdingsWidth ) {\r
13388         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */\r
13389         q = p;\r
13390         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */\r
13391             piece = boards[move][i][BOARD_WIDTH-1];\r
13392             if( piece != EmptySquare )\r
13393               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)\r
13394                   *p++ = PieceToChar(piece);\r
13395         }\r
13396         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */\r
13397             piece = boards[move][BOARD_HEIGHT-i-1][0];\r
13398             if( piece != EmptySquare )\r
13399               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)\r
13400                   *p++ = PieceToChar(piece);\r
13401         }\r
13402 \r
13403         if( q == p ) *p++ = '-';\r
13404         *p++ = ']';\r
13405         *p++ = ' ';\r
13406     }\r
13407 \r
13408     /* Active color */\r
13409     *p++ = whiteToPlay ? 'w' : 'b';\r
13410     *p++ = ' ';\r
13411 \r
13412   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines\r
13413     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';\r
13414   } else {\r
13415   if(nrCastlingRights) {\r
13416      q = p;\r
13417      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {\r
13418        /* [HGM] write directly from rights */\r
13419            if(castlingRights[move][2] >= 0 &&\r
13420               castlingRights[move][0] >= 0   )\r
13421                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';\r
13422            if(castlingRights[move][2] >= 0 &&\r
13423               castlingRights[move][1] >= 0   )\r
13424                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';\r
13425            if(castlingRights[move][5] >= 0 &&\r
13426               castlingRights[move][3] >= 0   )\r
13427                 *p++ = castlingRights[move][3] + AAA;\r
13428            if(castlingRights[move][5] >= 0 &&\r
13429               castlingRights[move][4] >= 0   )\r
13430                 *p++ = castlingRights[move][4] + AAA;\r
13431      } else {\r
13432 \r
13433         /* [HGM] write true castling rights */\r
13434         if( nrCastlingRights == 6 ) {\r
13435             if(castlingRights[move][0] == BOARD_RGHT-1 &&\r
13436                castlingRights[move][2] >= 0  ) *p++ = 'K';\r
13437             if(castlingRights[move][1] == BOARD_LEFT &&\r
13438                castlingRights[move][2] >= 0  ) *p++ = 'Q';\r
13439             if(castlingRights[move][3] == BOARD_RGHT-1 &&\r
13440                castlingRights[move][5] >= 0  ) *p++ = 'k';\r
13441             if(castlingRights[move][4] == BOARD_LEFT &&\r
13442                castlingRights[move][5] >= 0  ) *p++ = 'q';\r
13443         }\r
13444      }\r
13445      if (q == p) *p++ = '-'; /* No castling rights */\r
13446      *p++ = ' ';\r
13447   }\r
13448 \r
13449   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
13450      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
13451     /* En passant target square */\r
13452     if (move > backwardMostMove) {\r
13453         fromX = moveList[move - 1][0] - AAA;\r
13454         fromY = moveList[move - 1][1] - ONE;\r
13455         toX = moveList[move - 1][2] - AAA;\r
13456         toY = moveList[move - 1][3] - ONE;\r
13457         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&\r
13458             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&\r
13459             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&\r
13460             fromX == toX) {\r
13461             /* 2-square pawn move just happened */\r
13462             *p++ = toX + AAA;\r
13463             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';\r
13464         } else {\r
13465             *p++ = '-';\r
13466         }\r
13467     } else {\r
13468         *p++ = '-';\r
13469     }\r
13470     *p++ = ' ';\r
13471   }\r
13472   }\r
13473 \r
13474     /* [HGM] find reversible plies */\r
13475     {   int i = 0, j=move;\r
13476 \r
13477         if (appData.debugMode) { int k;\r
13478             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);\r
13479             for(k=backwardMostMove; k<=forwardMostMove; k++)\r
13480                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);\r
13481 \r
13482         }\r
13483 \r
13484         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;\r
13485         if( j == backwardMostMove ) i += initialRulePlies;\r
13486         sprintf(p, "%d ", i);\r
13487         p += i>=100 ? 4 : i >= 10 ? 3 : 2;\r
13488     }\r
13489     /* Fullmove number */\r
13490     sprintf(p, "%d", (move / 2) + 1);\r
13491     \r
13492     return StrSave(buf);\r
13493 }\r
13494 \r
13495 Boolean\r
13496 ParseFEN(board, blackPlaysFirst, fen)\r
13497     Board board;\r
13498      int *blackPlaysFirst;\r
13499      char *fen;\r
13500 {\r
13501     int i, j;\r
13502     char *p;\r
13503     int emptycount;\r
13504     ChessSquare piece;\r
13505 \r
13506     p = fen;\r
13507 \r
13508     /* [HGM] by default clear Crazyhouse holdings, if present */\r
13509     if(gameInfo.holdingsWidth) {\r
13510        for(i=0; i<BOARD_HEIGHT; i++) {\r
13511            board[i][0]             = EmptySquare; /* black holdings */\r
13512            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */\r
13513            board[i][1]             = (ChessSquare) 0; /* black counts */\r
13514            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */\r
13515        }\r
13516     }\r
13517 \r
13518     /* Piece placement data */\r
13519     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
13520         j = 0;\r
13521         for (;;) {\r
13522             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {\r
13523                 if (*p == '/') p++;\r
13524                 emptycount = gameInfo.boardWidth - j;\r
13525                 while (emptycount--)\r
13526                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13527                 break;\r
13528 #if(BOARD_SIZE >= 10)\r
13529             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */\r
13530                 p++; emptycount=10;\r
13531                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
13532                 while (emptycount--)\r
13533                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13534 #endif\r
13535             } else if (isdigit(*p)) {\r
13536                 emptycount = *p++ - '0';\r
13537                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */\r
13538                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
13539                 while (emptycount--)\r
13540                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13541             } else if (*p == '+' || isalpha(*p)) {\r
13542                 if (j >= gameInfo.boardWidth) return FALSE;\r
13543                 if(*p=='+') {\r
13544                     piece = CharToPiece(*++p);\r
13545                     if(piece == EmptySquare) return FALSE; /* unknown piece */\r
13546                     piece = (ChessSquare) (PROMOTED piece ); p++;\r
13547                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */\r
13548                 } else piece = CharToPiece(*p++);\r
13549 \r
13550                 if(piece==EmptySquare) return FALSE; /* unknown piece */\r
13551                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */\r
13552                     piece = (ChessSquare) (PROMOTED piece);\r
13553                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */\r
13554                     p++;\r
13555                 }\r
13556                 board[i][(j++)+gameInfo.holdingsWidth] = piece;\r
13557             } else {\r
13558                 return FALSE;\r
13559             }\r
13560         }\r
13561     }\r
13562     while (*p == '/' || *p == ' ') p++;\r
13563 \r
13564     /* [HGM] look for Crazyhouse holdings here */\r
13565     while(*p==' ') p++;\r
13566     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {\r
13567         if(*p == '[') p++;\r
13568         if(*p == '-' ) *p++; /* empty holdings */ else {\r
13569             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */\r
13570             /* if we would allow FEN reading to set board size, we would   */\r
13571             /* have to add holdings and shift the board read so far here   */\r
13572             while( (piece = CharToPiece(*p) ) != EmptySquare ) {\r
13573                 *p++;\r
13574                 if((int) piece >= (int) BlackPawn ) {\r
13575                     i = (int)piece - (int)BlackPawn;\r
13576                     i = PieceToNumber((ChessSquare)i);\r
13577                     if( i >= gameInfo.holdingsSize ) return FALSE;\r
13578                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */\r
13579                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */\r
13580                 } else {\r
13581                     i = (int)piece - (int)WhitePawn;\r
13582                     i = PieceToNumber((ChessSquare)i);\r
13583                     if( i >= gameInfo.holdingsSize ) return FALSE;\r
13584                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */\r
13585                     board[i][BOARD_WIDTH-2]++;          /* black holdings */\r
13586                 }\r
13587             }\r
13588         }\r
13589         if(*p == ']') *p++;\r
13590     }\r
13591 \r
13592     while(*p == ' ') p++;\r
13593 \r
13594     /* Active color */\r
13595     switch (*p++) {\r
13596       case 'w':\r
13597         *blackPlaysFirst = FALSE;\r
13598         break;\r
13599       case 'b': \r
13600         *blackPlaysFirst = TRUE;\r
13601         break;\r
13602       default:\r
13603         return FALSE;\r
13604     }\r
13605 \r
13606     /* [HGM] We NO LONGER ignore the rest of the FEN notation */\r
13607     /* return the extra info in global variiables             */\r
13608 \r
13609     /* set defaults in case FEN is incomplete */\r
13610     FENepStatus = EP_UNKNOWN;\r
13611     for(i=0; i<nrCastlingRights; i++ ) {\r
13612         FENcastlingRights[i] =\r
13613             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];\r
13614     }   /* assume possible unless obviously impossible */\r
13615     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;\r
13616     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;\r
13617     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;\r
13618     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;\r
13619     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;\r
13620     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;\r
13621     FENrulePlies = 0;\r
13622 \r
13623     while(*p==' ') p++;\r
13624     if(nrCastlingRights) {\r
13625       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {\r
13626           /* castling indicator present, so default becomes no castlings */\r
13627           for(i=0; i<nrCastlingRights; i++ ) {\r
13628                  FENcastlingRights[i] = -1;\r
13629           }\r
13630       }\r
13631       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||\r
13632              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&\r
13633              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||\r
13634              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {\r
13635         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;\r
13636 \r
13637         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {\r
13638             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;\r
13639             if(board[0             ][i] == WhiteKing) whiteKingFile = i;\r
13640         }\r
13641         switch(c) {\r
13642           case'K':\r
13643               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);\r
13644               FENcastlingRights[0] = i != whiteKingFile ? i : -1;\r
13645               FENcastlingRights[2] = whiteKingFile;\r
13646               break;\r
13647           case'Q':\r
13648               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);\r
13649               FENcastlingRights[1] = i != whiteKingFile ? i : -1;\r
13650               FENcastlingRights[2] = whiteKingFile;\r
13651               break;\r
13652           case'k':\r
13653               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);\r
13654               FENcastlingRights[3] = i != blackKingFile ? i : -1;\r
13655               FENcastlingRights[5] = blackKingFile;\r
13656               break;\r
13657           case'q':\r
13658               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);\r
13659               FENcastlingRights[4] = i != blackKingFile ? i : -1;\r
13660               FENcastlingRights[5] = blackKingFile;\r
13661           case '-':\r
13662               break;\r
13663           default: /* FRC castlings */\r
13664               if(c >= 'a') { /* black rights */\r
13665                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
13666                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;\r
13667                   if(i == BOARD_RGHT) break;\r
13668                   FENcastlingRights[5] = i;\r
13669                   c -= AAA;\r
13670                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||\r
13671                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;\r
13672                   if(c > i)\r
13673                       FENcastlingRights[3] = c;\r
13674                   else\r
13675                       FENcastlingRights[4] = c;\r
13676               } else { /* white rights */\r
13677                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
13678                     if(board[0][i] == WhiteKing) break;\r
13679                   if(i == BOARD_RGHT) break;\r
13680                   FENcastlingRights[2] = i;\r
13681                   c -= AAA - 'a' + 'A';\r
13682                   if(board[0][c] >= WhiteKing) break;\r
13683                   if(c > i)\r
13684                       FENcastlingRights[0] = c;\r
13685                   else\r
13686                       FENcastlingRights[1] = c;\r
13687               }\r
13688         }\r
13689       }\r
13690     if (appData.debugMode) {\r
13691         fprintf(debugFP, "FEN castling rights:");\r
13692         for(i=0; i<nrCastlingRights; i++)\r
13693         fprintf(debugFP, " %d", FENcastlingRights[i]);\r
13694         fprintf(debugFP, "\n");\r
13695     }\r
13696 \r
13697       while(*p==' ') p++;\r
13698     }\r
13699 \r
13700     /* read e.p. field in games that know e.p. capture */\r
13701     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
13702        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
13703       if(*p=='-') {\r
13704         p++; FENepStatus = EP_NONE;\r
13705       } else {\r
13706          char c = *p++ - AAA;\r
13707 \r
13708          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;\r
13709          if(*p >= '0' && *p <='9') *p++;\r
13710          FENepStatus = c;\r
13711       }\r
13712     }\r
13713 \r
13714 \r
13715     if(sscanf(p, "%d", &i) == 1) {\r
13716         FENrulePlies = i; /* 50-move ply counter */\r
13717         /* (The move number is still ignored)    */\r
13718     }\r
13719 \r
13720     return TRUE;\r
13721 }\r
13722       \r
13723 void\r
13724 EditPositionPasteFEN(char *fen)\r
13725 {\r
13726   if (fen != NULL) {\r
13727     Board initial_position;\r
13728 \r
13729     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {\r
13730       DisplayError(_("Bad FEN position in clipboard"), 0);\r
13731       return ;\r
13732     } else {\r
13733       int savedBlackPlaysFirst = blackPlaysFirst;\r
13734       EditPositionEvent();\r
13735       blackPlaysFirst = savedBlackPlaysFirst;\r
13736       CopyBoard(boards[0], initial_position);\r
13737           /* [HGM] copy FEN attributes as well */\r
13738           {   int i;\r
13739               initialRulePlies = FENrulePlies;\r
13740               epStatus[0] = FENepStatus;\r
13741               for( i=0; i<nrCastlingRights; i++ )\r
13742                   castlingRights[0][i] = FENcastlingRights[i];\r
13743           }\r
13744       EditPositionDone();\r
13745       DisplayBothClocks();\r
13746       DrawPosition(FALSE, boards[currentMove]);\r
13747     }\r
13748   }\r
13749 }\r