some small bugfixes
[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                             OutputKibitz(suppressKibitz, parse);\r
2280                         } else {\r
2281                             char tmp[MSG_SIZ];\r
2282                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);\r
2283                             SendToPlayer(tmp, strlen(tmp));\r
2284                         }\r
2285                     }\r
2286                     started = STARTED_NONE;\r
2287                 } else {\r
2288                     /* Don't match patterns against characters in chatter */\r
2289                     i++;\r
2290                     continue;\r
2291                 }\r
2292             }\r
2293             if (started == STARTED_CHATTER) {\r
2294                 if (buf[i] != '\n') {\r
2295                     /* Don't match patterns against characters in chatter */\r
2296                     i++;\r
2297                     continue;\r
2298                 }\r
2299                 started = STARTED_NONE;\r
2300             }\r
2301 \r
2302             /* Kludge to deal with rcmd protocol */\r
2303             if (firstTime && looking_at(buf, &i, "\001*")) {\r
2304                 DisplayFatalError(&buf[1], 0, 1);\r
2305                 continue;\r
2306             } else {\r
2307                 firstTime = FALSE;\r
2308             }\r
2309 \r
2310             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {\r
2311                 ics_type = ICS_ICC;\r
2312                 ics_prefix = "/";\r
2313                 if (appData.debugMode)\r
2314                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2315                 continue;\r
2316             }\r
2317             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {\r
2318                 ics_type = ICS_FICS;\r
2319                 ics_prefix = "$";\r
2320                 if (appData.debugMode)\r
2321                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2322                 continue;\r
2323             }\r
2324             if (!loggedOn && looking_at(buf, &i, "chess.net")) {\r
2325                 ics_type = ICS_CHESSNET;\r
2326                 ics_prefix = "/";\r
2327                 if (appData.debugMode)\r
2328                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2329                 continue;\r
2330             }\r
2331 \r
2332             if (!loggedOn &&\r
2333                 (looking_at(buf, &i, "\"*\" is *a registered name") ||\r
2334                  looking_at(buf, &i, "Logging you in as \"*\"") ||\r
2335                  looking_at(buf, &i, "will be \"*\""))) {\r
2336               strcpy(ics_handle, star_match[0]);\r
2337               continue;\r
2338             }\r
2339 \r
2340             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {\r
2341               char buf[MSG_SIZ];\r
2342               sprintf(buf, "%s@%s", ics_handle, appData.icsHost);\r
2343               DisplayIcsInteractionTitle(buf);\r
2344               have_set_title = TRUE;\r
2345             }\r
2346 \r
2347             /* skip finger notes */\r
2348             if (started == STARTED_NONE &&\r
2349                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||\r
2350                  (buf[i] == '1' && buf[i+1] == '0')) &&\r
2351                 buf[i+2] == ':' && buf[i+3] == ' ') {\r
2352               started = STARTED_CHATTER;\r
2353               i += 3;\r
2354               continue;\r
2355             }\r
2356 \r
2357             /* skip formula vars */\r
2358             if (started == STARTED_NONE &&\r
2359                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {\r
2360               started = STARTED_CHATTER;\r
2361               i += 3;\r
2362               continue;\r
2363             }\r
2364 \r
2365             oldi = i;\r
2366             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window\r
2367             if (appData.autoKibitz && started == STARTED_NONE && \r
2368                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze\r
2369                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {\r
2370                 if(looking_at(buf, &i, "* kibitzes: ") &&\r
2371                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || \r
2372                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent\r
2373                         suppressKibitz = TRUE;\r
2374                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]\r
2375                                 && (gameMode == IcsPlayingWhite)) ||\r
2376                            (StrStr(star_match[0], gameInfo.black) == star_match[0]\r
2377                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz\r
2378                             started = STARTED_CHATTER; // own kibitz we simply discard\r
2379                         else {\r
2380                             started = STARTED_COMMENT; // make sure it will be collected in parse[]\r
2381                             parse_pos = 0; parse[0] = NULLCHAR;\r
2382                             savingComment = TRUE;\r
2383                             suppressKibitz = gameMode != IcsObserving ? 2 :\r
2384                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;\r
2385                         } \r
2386                         continue;\r
2387                 } else\r
2388                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz\r
2389                     started = STARTED_CHATTER;\r
2390                     suppressKibitz = TRUE;\r
2391                 }\r
2392             } // [HGM] kibitz: end of patch\r
2393 \r
2394             if (appData.zippyTalk || appData.zippyPlay) {\r
2395                 /* [DM] Backup address for color zippy lines */\r
2396                 backup = i;\r
2397 #if ZIPPY\r
2398        #ifdef WIN32\r
2399                if (loggedOn == TRUE)\r
2400                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||\r
2401                           (appData.zippyPlay && ZippyMatch(buf, &backup)));\r
2402        #else\r
2403                 if (ZippyControl(buf, &i) ||\r
2404                     ZippyConverse(buf, &i) ||\r
2405                     (appData.zippyPlay && ZippyMatch(buf, &i))) {\r
2406                       loggedOn = TRUE;\r
2407                       if (!appData.colorize) continue;\r
2408                 }\r
2409        #endif\r
2410 #endif\r
2411             } // [DM] 'else { ' deleted\r
2412                 if (/* Don't color "message" or "messages" output */\r
2413                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||\r
2414                     looking_at(buf, &i, "*. * at *:*: ") ||\r
2415                     looking_at(buf, &i, "--* (*:*): ") ||\r
2416                     /* Regular tells and says */\r
2417                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||\r
2418                     looking_at(buf, &i, "* (your partner) tells you: ") ||\r
2419                     looking_at(buf, &i, "* says: ") ||\r
2420                     /* Message notifications (same color as tells) */\r
2421                     looking_at(buf, &i, "* has left a message ") ||\r
2422                     looking_at(buf, &i, "* just sent you a message:\n") ||\r
2423                     /* Whispers and kibitzes */\r
2424                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||\r
2425                     looking_at(buf, &i, "* kibitzes: ") ||\r
2426                     /* Channel tells */\r
2427                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {\r
2428 \r
2429                   if (tkind == 1 && strchr(star_match[0], ':')) {\r
2430                       /* Avoid "tells you:" spoofs in channels */\r
2431                      tkind = 3;\r
2432                   }\r
2433                   if (star_match[0][0] == NULLCHAR ||\r
2434                       strchr(star_match[0], ' ') ||\r
2435                       (tkind == 3 && strchr(star_match[1], ' '))) {\r
2436                     /* Reject bogus matches */\r
2437                     i = oldi;\r
2438                   } else {\r
2439                     if (appData.colorize) {\r
2440                       if (oldi > next_out) {\r
2441                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2442                         next_out = oldi;\r
2443                       }\r
2444                       switch (tkind) {\r
2445                       case 1:\r
2446                         Colorize(ColorTell, FALSE);\r
2447                         curColor = ColorTell;\r
2448                         break;\r
2449                       case 2:\r
2450                         Colorize(ColorKibitz, FALSE);\r
2451                         curColor = ColorKibitz;\r
2452                         break;\r
2453                       case 3:\r
2454                         p = strrchr(star_match[1], '(');\r
2455                         if (p == NULL) {\r
2456                           p = star_match[1];\r
2457                         } else {\r
2458                           p++;\r
2459                         }\r
2460                         if (atoi(p) == 1) {\r
2461                           Colorize(ColorChannel1, FALSE);\r
2462                           curColor = ColorChannel1;\r
2463                         } else {\r
2464                           Colorize(ColorChannel, FALSE);\r
2465                           curColor = ColorChannel;\r
2466                         }\r
2467                         break;\r
2468                       case 5:\r
2469                         curColor = ColorNormal;\r
2470                         break;\r
2471                       }\r
2472                     }\r
2473                     if (started == STARTED_NONE && appData.autoComment &&\r
2474                         (gameMode == IcsObserving ||\r
2475                          gameMode == IcsPlayingWhite ||\r
2476                          gameMode == IcsPlayingBlack)) {\r
2477                       parse_pos = i - oldi;\r
2478                       memcpy(parse, &buf[oldi], parse_pos);\r
2479                       parse[parse_pos] = NULLCHAR;\r
2480                       started = STARTED_COMMENT;\r
2481                       savingComment = TRUE;\r
2482                     } else {\r
2483                       started = STARTED_CHATTER;\r
2484                       savingComment = FALSE;\r
2485                     }\r
2486                     loggedOn = TRUE;\r
2487                     continue;\r
2488                   }\r
2489                 }\r
2490 \r
2491                 if (looking_at(buf, &i, "* s-shouts: ") ||\r
2492                     looking_at(buf, &i, "* c-shouts: ")) {\r
2493                     if (appData.colorize) {\r
2494                         if (oldi > next_out) {\r
2495                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2496                             next_out = oldi;\r
2497                         }\r
2498                         Colorize(ColorSShout, FALSE);\r
2499                         curColor = ColorSShout;\r
2500                     }\r
2501                     loggedOn = TRUE;\r
2502                     started = STARTED_CHATTER;\r
2503                     continue;\r
2504                 }\r
2505 \r
2506                 if (looking_at(buf, &i, "--->")) {\r
2507                     loggedOn = TRUE;\r
2508                     continue;\r
2509                 }\r
2510 \r
2511                 if (looking_at(buf, &i, "* shouts: ") ||\r
2512                     looking_at(buf, &i, "--> ")) {\r
2513                     if (appData.colorize) {\r
2514                         if (oldi > next_out) {\r
2515                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2516                             next_out = oldi;\r
2517                         }\r
2518                         Colorize(ColorShout, FALSE);\r
2519                         curColor = ColorShout;\r
2520                     }\r
2521                     loggedOn = TRUE;\r
2522                     started = STARTED_CHATTER;\r
2523                     continue;\r
2524                 }\r
2525 \r
2526                 if (looking_at( buf, &i, "Challenge:")) {\r
2527                     if (appData.colorize) {\r
2528                         if (oldi > next_out) {\r
2529                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2530                             next_out = oldi;\r
2531                         }\r
2532                         Colorize(ColorChallenge, FALSE);\r
2533                         curColor = ColorChallenge;\r
2534                     }\r
2535                     loggedOn = TRUE;\r
2536                     continue;\r
2537                 }\r
2538 \r
2539                 if (looking_at(buf, &i, "* offers you") ||\r
2540                     looking_at(buf, &i, "* offers to be") ||\r
2541                     looking_at(buf, &i, "* would like to") ||\r
2542                     looking_at(buf, &i, "* requests to") ||\r
2543                     looking_at(buf, &i, "Your opponent offers") ||\r
2544                     looking_at(buf, &i, "Your opponent requests")) {\r
2545 \r
2546                     if (appData.colorize) {\r
2547                         if (oldi > next_out) {\r
2548                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2549                             next_out = oldi;\r
2550                         }\r
2551                         Colorize(ColorRequest, FALSE);\r
2552                         curColor = ColorRequest;\r
2553                     }\r
2554                     continue;\r
2555                 }\r
2556 \r
2557                 if (looking_at(buf, &i, "* (*) seeking")) {\r
2558                     if (appData.colorize) {\r
2559                         if (oldi > next_out) {\r
2560                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2561                             next_out = oldi;\r
2562                         }\r
2563                         Colorize(ColorSeek, FALSE);\r
2564                         curColor = ColorSeek;\r
2565                     }\r
2566                     continue;\r
2567             }\r
2568 \r
2569             if (looking_at(buf, &i, "\\   ")) {\r
2570                 if (prevColor != ColorNormal) {\r
2571                     if (oldi > next_out) {\r
2572                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2573                         next_out = oldi;\r
2574                     }\r
2575                     Colorize(prevColor, TRUE);\r
2576                     curColor = prevColor;\r
2577                 }\r
2578                 if (savingComment) {\r
2579                     parse_pos = i - oldi;\r
2580                     memcpy(parse, &buf[oldi], parse_pos);\r
2581                     parse[parse_pos] = NULLCHAR;\r
2582                     started = STARTED_COMMENT;\r
2583                 } else {\r
2584                     started = STARTED_CHATTER;\r
2585                 }\r
2586                 continue;\r
2587             }\r
2588 \r
2589             if (looking_at(buf, &i, "Black Strength :") ||\r
2590                 looking_at(buf, &i, "<<< style 10 board >>>") ||\r
2591                 looking_at(buf, &i, "<10>") ||\r
2592                 looking_at(buf, &i, "#@#")) {\r
2593                 /* Wrong board style */\r
2594                 loggedOn = TRUE;\r
2595                 SendToICS(ics_prefix);\r
2596                 SendToICS("set style 12\n");\r
2597                 SendToICS(ics_prefix);\r
2598                 SendToICS("refresh\n");\r
2599                 continue;\r
2600             }\r
2601             \r
2602             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {\r
2603                 ICSInitScript();\r
2604                 have_sent_ICS_logon = 1;\r
2605                 continue;\r
2606             }\r
2607               \r
2608             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && \r
2609                 (looking_at(buf, &i, "\n<12> ") ||\r
2610                  looking_at(buf, &i, "<12> "))) {\r
2611                 loggedOn = TRUE;\r
2612                 if (oldi > next_out) {\r
2613                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2614                 }\r
2615                 next_out = i;\r
2616                 started = STARTED_BOARD;\r
2617                 parse_pos = 0;\r
2618                 continue;\r
2619             }\r
2620 \r
2621             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||\r
2622                 looking_at(buf, &i, "<b1> ")) {\r
2623                 if (oldi > next_out) {\r
2624                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2625                 }\r
2626                 next_out = i;\r
2627                 started = STARTED_HOLDINGS;\r
2628                 parse_pos = 0;\r
2629                 continue;\r
2630             }\r
2631 \r
2632             if (looking_at(buf, &i, "* *vs. * *--- *")) {\r
2633                 loggedOn = TRUE;\r
2634                 /* Header for a move list -- first line */\r
2635 \r
2636                 switch (ics_getting_history) {\r
2637                   case H_FALSE:\r
2638                     switch (gameMode) {\r
2639                       case IcsIdle:\r
2640                       case BeginningOfGame:\r
2641                         /* User typed "moves" or "oldmoves" while we\r
2642                            were idle.  Pretend we asked for these\r
2643                            moves and soak them up so user can step\r
2644                            through them and/or save them.\r
2645                            */\r
2646                         Reset(FALSE, TRUE);\r
2647                         gameMode = IcsObserving;\r
2648                         ModeHighlight();\r
2649                         ics_gamenum = -1;\r
2650                         ics_getting_history = H_GOT_UNREQ_HEADER;\r
2651                         break;\r
2652                       case EditGame: /*?*/\r
2653                       case EditPosition: /*?*/\r
2654                         /* Should above feature work in these modes too? */\r
2655                         /* For now it doesn't */\r
2656                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2657                         break;\r
2658                       default:\r
2659                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2660                         break;\r
2661                     }\r
2662                     break;\r
2663                   case H_REQUESTED:\r
2664                     /* Is this the right one? */\r
2665                     if (gameInfo.white && gameInfo.black &&\r
2666                         strcmp(gameInfo.white, star_match[0]) == 0 &&\r
2667                         strcmp(gameInfo.black, star_match[2]) == 0) {\r
2668                         /* All is well */\r
2669                         ics_getting_history = H_GOT_REQ_HEADER;\r
2670                     }\r
2671                     break;\r
2672                   case H_GOT_REQ_HEADER:\r
2673                   case H_GOT_UNREQ_HEADER:\r
2674                   case H_GOT_UNWANTED_HEADER:\r
2675                   case H_GETTING_MOVES:\r
2676                     /* Should not happen */\r
2677                     DisplayError(_("Error gathering move list: two headers"), 0);\r
2678                     ics_getting_history = H_FALSE;\r
2679                     break;\r
2680                 }\r
2681 \r
2682                 /* Save player ratings into gameInfo if needed */\r
2683                 if ((ics_getting_history == H_GOT_REQ_HEADER ||\r
2684                      ics_getting_history == H_GOT_UNREQ_HEADER) &&\r
2685                     (gameInfo.whiteRating == -1 ||\r
2686                      gameInfo.blackRating == -1)) {\r
2687 \r
2688                     gameInfo.whiteRating = string_to_rating(star_match[1]);\r
2689                     gameInfo.blackRating = string_to_rating(star_match[3]);\r
2690                     if (appData.debugMode)\r
2691                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), \r
2692                               gameInfo.whiteRating, gameInfo.blackRating);\r
2693                 }\r
2694                 continue;\r
2695             }\r
2696 \r
2697             if (looking_at(buf, &i,\r
2698               "* * match, initial time: * minute*, increment: * second")) {\r
2699                 /* Header for a move list -- second line */\r
2700                 /* Initial board will follow if this is a wild game */\r
2701                 if (gameInfo.event != NULL) free(gameInfo.event);\r
2702                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);\r
2703                 gameInfo.event = StrSave(str);\r
2704                 /* [HGM] we switched variant. Translate boards if needed. */\r
2705                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));\r
2706                 continue;\r
2707             }\r
2708 \r
2709             if (looking_at(buf, &i, "Move  ")) {\r
2710                 /* Beginning of a move list */\r
2711                 switch (ics_getting_history) {\r
2712                   case H_FALSE:\r
2713                     /* Normally should not happen */\r
2714                     /* Maybe user hit reset while we were parsing */\r
2715                     break;\r
2716                   case H_REQUESTED:\r
2717                     /* Happens if we are ignoring a move list that is not\r
2718                      * the one we just requested.  Common if the user\r
2719                      * tries to observe two games without turning off\r
2720                      * getMoveList */\r
2721                     break;\r
2722                   case H_GETTING_MOVES:\r
2723                     /* Should not happen */\r
2724                     DisplayError(_("Error gathering move list: nested"), 0);\r
2725                     ics_getting_history = H_FALSE;\r
2726                     break;\r
2727                   case H_GOT_REQ_HEADER:\r
2728                     ics_getting_history = H_GETTING_MOVES;\r
2729                     started = STARTED_MOVES;\r
2730                     parse_pos = 0;\r
2731                     if (oldi > next_out) {\r
2732                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2733                     }\r
2734                     break;\r
2735                   case H_GOT_UNREQ_HEADER:\r
2736                     ics_getting_history = H_GETTING_MOVES;\r
2737                     started = STARTED_MOVES_NOHIDE;\r
2738                     parse_pos = 0;\r
2739                     break;\r
2740                   case H_GOT_UNWANTED_HEADER:\r
2741                     ics_getting_history = H_FALSE;\r
2742                     break;\r
2743                 }\r
2744                 continue;\r
2745             }                           \r
2746             \r
2747             if (looking_at(buf, &i, "% ") ||\r
2748                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)\r
2749                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book\r
2750                 savingComment = FALSE;\r
2751                 switch (started) {\r
2752                   case STARTED_MOVES:\r
2753                   case STARTED_MOVES_NOHIDE:\r
2754                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);\r
2755                     parse[parse_pos + i - oldi] = NULLCHAR;\r
2756                     ParseGameHistory(parse);\r
2757 #if ZIPPY\r
2758                     if (appData.zippyPlay && first.initDone) {\r
2759                         FeedMovesToProgram(&first, forwardMostMove);\r
2760                         if (gameMode == IcsPlayingWhite) {\r
2761                             if (WhiteOnMove(forwardMostMove)) {\r
2762                                 if (first.sendTime) {\r
2763                                   if (first.useColors) {\r
2764                                     SendToProgram("black\n", &first); \r
2765                                   }\r
2766                                   SendTimeRemaining(&first, TRUE);\r
2767                                 }\r
2768 #if 0\r
2769                                 if (first.useColors) {\r
2770                                   SendToProgram("white\ngo\n", &first);\r
2771                                 } else {\r
2772                                   SendToProgram("go\n", &first);\r
2773                                 }\r
2774 #else\r
2775                                 if (first.useColors) {\r
2776                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent\r
2777                                 }\r
2778                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos\r
2779 #endif\r
2780                                 first.maybeThinking = TRUE;\r
2781                             } else {\r
2782                                 if (first.usePlayother) {\r
2783                                   if (first.sendTime) {\r
2784                                     SendTimeRemaining(&first, TRUE);\r
2785                                   }\r
2786                                   SendToProgram("playother\n", &first);\r
2787                                   firstMove = FALSE;\r
2788                                 } else {\r
2789                                   firstMove = TRUE;\r
2790                                 }\r
2791                             }\r
2792                         } else if (gameMode == IcsPlayingBlack) {\r
2793                             if (!WhiteOnMove(forwardMostMove)) {\r
2794                                 if (first.sendTime) {\r
2795                                   if (first.useColors) {\r
2796                                     SendToProgram("white\n", &first);\r
2797                                   }\r
2798                                   SendTimeRemaining(&first, FALSE);\r
2799                                 }\r
2800 #if 0\r
2801                                 if (first.useColors) {\r
2802                                   SendToProgram("black\ngo\n", &first);\r
2803                                 } else {\r
2804                                   SendToProgram("go\n", &first);\r
2805                                 }\r
2806 #else\r
2807                                 if (first.useColors) {\r
2808                                   SendToProgram("black\n", &first);\r
2809                                 }\r
2810                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);\r
2811 #endif\r
2812                                 first.maybeThinking = TRUE;\r
2813                             } else {\r
2814                                 if (first.usePlayother) {\r
2815                                   if (first.sendTime) {\r
2816                                     SendTimeRemaining(&first, FALSE);\r
2817                                   }\r
2818                                   SendToProgram("playother\n", &first);\r
2819                                   firstMove = FALSE;\r
2820                                 } else {\r
2821                                   firstMove = TRUE;\r
2822                                 }\r
2823                             }\r
2824                         }                       \r
2825                     }\r
2826 #endif\r
2827                     if (gameMode == IcsObserving && ics_gamenum == -1) {\r
2828                         /* Moves came from oldmoves or moves command\r
2829                            while we weren't doing anything else.\r
2830                            */\r
2831                         currentMove = forwardMostMove;\r
2832                         ClearHighlights();/*!!could figure this out*/\r
2833                         flipView = appData.flipView;\r
2834                         DrawPosition(FALSE, boards[currentMove]);\r
2835                         DisplayBothClocks();\r
2836                         sprintf(str, "%s vs. %s",\r
2837                                 gameInfo.white, gameInfo.black);\r
2838                         DisplayTitle(str);\r
2839                         gameMode = IcsIdle;\r
2840                     } else {\r
2841                         /* Moves were history of an active game */\r
2842                         if (gameInfo.resultDetails != NULL) {\r
2843                             free(gameInfo.resultDetails);\r
2844                             gameInfo.resultDetails = NULL;\r
2845                         }\r
2846                     }\r
2847                     HistorySet(parseList, backwardMostMove,\r
2848                                forwardMostMove, currentMove-1);\r
2849                     DisplayMove(currentMove - 1);\r
2850                     if (started == STARTED_MOVES) next_out = i;\r
2851                     started = STARTED_NONE;\r
2852                     ics_getting_history = H_FALSE;\r
2853                     break;\r
2854 \r
2855                   case STARTED_OBSERVE:\r
2856                     started = STARTED_NONE;\r
2857                     SendToICS(ics_prefix);\r
2858                     SendToICS("refresh\n");\r
2859                     break;\r
2860 \r
2861                   default:\r
2862                     break;\r
2863                 }\r
2864                 if(bookHit) { // [HGM] book: simulate book reply\r
2865                     static char bookMove[MSG_SIZ]; // a bit generous?\r
2866 \r
2867                     programStats.nodes = programStats.depth = programStats.time = \r
2868                     programStats.score = programStats.got_only_move = 0;\r
2869                     sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
2870 \r
2871                     strcpy(bookMove, "move ");\r
2872                     strcat(bookMove, bookHit);\r
2873                     HandleMachineMove(bookMove, &first);\r
2874                 }\r
2875                 continue;\r
2876             }\r
2877             \r
2878             if ((started == STARTED_MOVES || started == STARTED_BOARD ||\r
2879                  started == STARTED_HOLDINGS ||\r
2880                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {\r
2881                 /* Accumulate characters in move list or board */\r
2882                 parse[parse_pos++] = buf[i];\r
2883             }\r
2884             \r
2885             /* Start of game messages.  Mostly we detect start of game\r
2886                when the first board image arrives.  On some versions\r
2887                of the ICS, though, we need to do a "refresh" after starting\r
2888                to observe in order to get the current board right away. */\r
2889             if (looking_at(buf, &i, "Adding game * to observation list")) {\r
2890                 started = STARTED_OBSERVE;\r
2891                 continue;\r
2892             }\r
2893 \r
2894             /* Handle auto-observe */\r
2895             if (appData.autoObserve &&\r
2896                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&\r
2897                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {\r
2898                 char *player;\r
2899                 /* Choose the player that was highlighted, if any. */\r
2900                 if (star_match[0][0] == '\033' ||\r
2901                     star_match[1][0] != '\033') {\r
2902                     player = star_match[0];\r
2903                 } else {\r
2904                     player = star_match[2];\r
2905                 }\r
2906                 sprintf(str, "%sobserve %s\n",\r
2907                         ics_prefix, StripHighlightAndTitle(player));\r
2908                 SendToICS(str);\r
2909 \r
2910                 /* Save ratings from notify string */\r
2911                 strcpy(player1Name, star_match[0]);\r
2912                 player1Rating = string_to_rating(star_match[1]);\r
2913                 strcpy(player2Name, star_match[2]);\r
2914                 player2Rating = string_to_rating(star_match[3]);\r
2915 \r
2916                 if (appData.debugMode)\r
2917                   fprintf(debugFP, \r
2918                           "Ratings from 'Game notification:' %s %d, %s %d\n",\r
2919                           player1Name, player1Rating,\r
2920                           player2Name, player2Rating);\r
2921 \r
2922                 continue;\r
2923             }\r
2924 \r
2925             /* Deal with automatic examine mode after a game,\r
2926                and with IcsObserving -> IcsExamining transition */\r
2927             if (looking_at(buf, &i, "Entering examine mode for game *") ||\r
2928                 looking_at(buf, &i, "has made you an examiner of game *")) {\r
2929 \r
2930                 int gamenum = atoi(star_match[0]);\r
2931                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&\r
2932                     gamenum == ics_gamenum) {\r
2933                     /* We were already playing or observing this game;\r
2934                        no need to refetch history */\r
2935                     gameMode = IcsExamining;\r
2936                     if (pausing) {\r
2937                         pauseExamForwardMostMove = forwardMostMove;\r
2938                     } else if (currentMove < forwardMostMove) {\r
2939                         ForwardInner(forwardMostMove);\r
2940                     }\r
2941                 } else {\r
2942                     /* I don't think this case really can happen */\r
2943                     SendToICS(ics_prefix);\r
2944                     SendToICS("refresh\n");\r
2945                 }\r
2946                 continue;\r
2947             }    \r
2948             \r
2949             /* Error messages */\r
2950             if (ics_user_moved) {\r
2951                 if (looking_at(buf, &i, "Illegal move") ||\r
2952                     looking_at(buf, &i, "Not a legal move") ||\r
2953                     looking_at(buf, &i, "Your king is in check") ||\r
2954                     looking_at(buf, &i, "It isn't your turn") ||\r
2955                     looking_at(buf, &i, "It is not your move")) {\r
2956                     /* Illegal move */\r
2957                     ics_user_moved = 0;\r
2958                     if (forwardMostMove > backwardMostMove) {\r
2959                         currentMove = --forwardMostMove;\r
2960                         DisplayMove(currentMove - 1); /* before DMError */\r
2961                         DisplayMoveError(_("Illegal move (rejected by ICS)"));\r
2962                         DrawPosition(FALSE, boards[currentMove]);\r
2963                         SwitchClocks();\r
2964                         DisplayBothClocks();\r
2965                     }\r
2966                     continue;\r
2967                 }\r
2968             }\r
2969 \r
2970             if (looking_at(buf, &i, "still have time") ||\r
2971                 looking_at(buf, &i, "not out of time") ||\r
2972                 looking_at(buf, &i, "either player is out of time") ||\r
2973                 looking_at(buf, &i, "has timeseal; checking")) {\r
2974                 /* We must have called his flag a little too soon */\r
2975                 whiteFlag = blackFlag = FALSE;\r
2976                 continue;\r
2977             }\r
2978 \r
2979             if (looking_at(buf, &i, "added * seconds to") ||\r
2980                 looking_at(buf, &i, "seconds were added to")) {\r
2981                 /* Update the clocks */\r
2982                 SendToICS(ics_prefix);\r
2983                 SendToICS("refresh\n");\r
2984                 continue;\r
2985             }\r
2986 \r
2987             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {\r
2988                 ics_clock_paused = TRUE;\r
2989                 StopClocks();\r
2990                 continue;\r
2991             }\r
2992 \r
2993             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {\r
2994                 ics_clock_paused = FALSE;\r
2995                 StartClocks();\r
2996                 continue;\r
2997             }\r
2998 \r
2999             /* Grab player ratings from the Creating: message.\r
3000                Note we have to check for the special case when\r
3001                the ICS inserts things like [white] or [black]. */\r
3002             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||\r
3003                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {\r
3004                 /* star_matches:\r
3005                    0    player 1 name (not necessarily white)\r
3006                    1    player 1 rating\r
3007                    2    empty, white, or black (IGNORED)\r
3008                    3    player 2 name (not necessarily black)\r
3009                    4    player 2 rating\r
3010                    \r
3011                    The names/ratings are sorted out when the game\r
3012                    actually starts (below).\r
3013                 */\r
3014                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));\r
3015                 player1Rating = string_to_rating(star_match[1]);\r
3016                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));\r
3017                 player2Rating = string_to_rating(star_match[4]);\r
3018 \r
3019                 if (appData.debugMode)\r
3020                   fprintf(debugFP, \r
3021                           "Ratings from 'Creating:' %s %d, %s %d\n",\r
3022                           player1Name, player1Rating,\r
3023                           player2Name, player2Rating);\r
3024 \r
3025                 continue;\r
3026             }\r
3027             \r
3028             /* Improved generic start/end-of-game messages */\r
3029             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||\r
3030                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){\r
3031                 /* If tkind == 0: */\r
3032                 /* star_match[0] is the game number */\r
3033                 /*           [1] is the white player's name */\r
3034                 /*           [2] is the black player's name */\r
3035                 /* For end-of-game: */\r
3036                 /*           [3] is the reason for the game end */\r
3037                 /*           [4] is a PGN end game-token, preceded by " " */\r
3038                 /* For start-of-game: */\r
3039                 /*           [3] begins with "Creating" or "Continuing" */\r
3040                 /*           [4] is " *" or empty (don't care). */\r
3041                 int gamenum = atoi(star_match[0]);\r
3042                 char *whitename, *blackname, *why, *endtoken;\r
3043                 ChessMove endtype = (ChessMove) 0;\r
3044 \r
3045                 if (tkind == 0) {\r
3046                   whitename = star_match[1];\r
3047                   blackname = star_match[2];\r
3048                   why = star_match[3];\r
3049                   endtoken = star_match[4];\r
3050                 } else {\r
3051                   whitename = star_match[1];\r
3052                   blackname = star_match[3];\r
3053                   why = star_match[5];\r
3054                   endtoken = star_match[6];\r
3055                 }\r
3056 \r
3057                 /* Game start messages */\r
3058                 if (strncmp(why, "Creating ", 9) == 0 ||\r
3059                     strncmp(why, "Continuing ", 11) == 0) {\r
3060                     gs_gamenum = gamenum;\r
3061                     strcpy(gs_kind, strchr(why, ' ') + 1);\r
3062 #if ZIPPY\r
3063                     if (appData.zippyPlay) {\r
3064                         ZippyGameStart(whitename, blackname);\r
3065                     }\r
3066 #endif /*ZIPPY*/\r
3067                     continue;\r
3068                 }\r
3069 \r
3070                 /* Game end messages */\r
3071                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||\r
3072                     ics_gamenum != gamenum) {\r
3073                     continue;\r
3074                 }\r
3075                 while (endtoken[0] == ' ') endtoken++;\r
3076                 switch (endtoken[0]) {\r
3077                   case '*':\r
3078                   default:\r
3079                     endtype = GameUnfinished;\r
3080                     break;\r
3081                   case '0':\r
3082                     endtype = BlackWins;\r
3083                     break;\r
3084                   case '1':\r
3085                     if (endtoken[1] == '/')\r
3086                       endtype = GameIsDrawn;\r
3087                     else\r
3088                       endtype = WhiteWins;\r
3089                     break;\r
3090                 }\r
3091                 GameEnds(endtype, why, GE_ICS);\r
3092 #if ZIPPY\r
3093                 if (appData.zippyPlay && first.initDone) {\r
3094                     ZippyGameEnd(endtype, why);\r
3095                     if (first.pr == NULL) {\r
3096                       /* Start the next process early so that we'll\r
3097                          be ready for the next challenge */\r
3098                       StartChessProgram(&first);\r
3099                     }\r
3100                     /* Send "new" early, in case this command takes\r
3101                        a long time to finish, so that we'll be ready\r
3102                        for the next challenge. */\r
3103                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'\r
3104                     Reset(TRUE, TRUE);\r
3105                 }\r
3106 #endif /*ZIPPY*/\r
3107                 continue;\r
3108             }\r
3109 \r
3110             if (looking_at(buf, &i, "Removing game * from observation") ||\r
3111                 looking_at(buf, &i, "no longer observing game *") ||\r
3112                 looking_at(buf, &i, "Game * (*) has no examiners")) {\r
3113                 if (gameMode == IcsObserving &&\r
3114                     atoi(star_match[0]) == ics_gamenum)\r
3115                   {\r
3116                       /* icsEngineAnalyze */\r
3117                       if (appData.icsEngineAnalyze) {\r
3118                             ExitAnalyzeMode();\r
3119                             ModeHighlight();\r
3120                       }\r
3121                       StopClocks();\r
3122                       gameMode = IcsIdle;\r
3123                       ics_gamenum = -1;\r
3124                       ics_user_moved = FALSE;\r
3125                   }\r
3126                 continue;\r
3127             }\r
3128 \r
3129             if (looking_at(buf, &i, "no longer examining game *")) {\r
3130                 if (gameMode == IcsExamining &&\r
3131                     atoi(star_match[0]) == ics_gamenum)\r
3132                   {\r
3133                       gameMode = IcsIdle;\r
3134                       ics_gamenum = -1;\r
3135                       ics_user_moved = FALSE;\r
3136                   }\r
3137                 continue;\r
3138             }\r
3139 \r
3140             /* Advance leftover_start past any newlines we find,\r
3141                so only partial lines can get reparsed */\r
3142             if (looking_at(buf, &i, "\n")) {\r
3143                 prevColor = curColor;\r
3144                 if (curColor != ColorNormal) {\r
3145                     if (oldi > next_out) {\r
3146                         SendToPlayer(&buf[next_out], oldi - next_out);\r
3147                         next_out = oldi;\r
3148                     }\r
3149                     Colorize(ColorNormal, FALSE);\r
3150                     curColor = ColorNormal;\r
3151                 }\r
3152                 if (started == STARTED_BOARD) {\r
3153                     started = STARTED_NONE;\r
3154                     parse[parse_pos] = NULLCHAR;\r
3155                     ParseBoard12(parse);\r
3156                     ics_user_moved = 0;\r
3157 \r
3158                     /* Send premove here */\r
3159                     if (appData.premove) {\r
3160                       char str[MSG_SIZ];\r
3161                       if (currentMove == 0 &&\r
3162                           gameMode == IcsPlayingWhite &&\r
3163                           appData.premoveWhite) {\r
3164                         sprintf(str, "%s%s\n", ics_prefix,\r
3165                                 appData.premoveWhiteText);\r
3166                         if (appData.debugMode)\r
3167                           fprintf(debugFP, "Sending premove:\n");\r
3168                         SendToICS(str);\r
3169                       } else if (currentMove == 1 &&\r
3170                                  gameMode == IcsPlayingBlack &&\r
3171                                  appData.premoveBlack) {\r
3172                         sprintf(str, "%s%s\n", ics_prefix,\r
3173                                 appData.premoveBlackText);\r
3174                         if (appData.debugMode)\r
3175                           fprintf(debugFP, "Sending premove:\n");\r
3176                         SendToICS(str);\r
3177                       } else if (gotPremove) {\r
3178                         gotPremove = 0;\r
3179                         ClearPremoveHighlights();\r
3180                         if (appData.debugMode)\r
3181                           fprintf(debugFP, "Sending premove:\n");\r
3182                           UserMoveEvent(premoveFromX, premoveFromY, \r
3183                                         premoveToX, premoveToY, \r
3184                                         premovePromoChar);\r
3185                       }\r
3186                     }\r
3187 \r
3188                     /* Usually suppress following prompt */\r
3189                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {\r
3190                         if (looking_at(buf, &i, "*% ")) {\r
3191                             savingComment = FALSE;\r
3192                         }\r
3193                     }\r
3194                     next_out = i;\r
3195                 } else if (started == STARTED_HOLDINGS) {\r
3196                     int gamenum;\r
3197                     char new_piece[MSG_SIZ];\r
3198                     started = STARTED_NONE;\r
3199                     parse[parse_pos] = NULLCHAR;\r
3200                     if (appData.debugMode)\r
3201                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",\r
3202                                                         parse, currentMove);\r
3203                     if (sscanf(parse, " game %d", &gamenum) == 1 &&\r
3204                         gamenum == ics_gamenum) {\r
3205                         if (gameInfo.variant == VariantNormal) {\r
3206                           /* [HGM] We seem to switch variant during a game!\r
3207                            * Presumably no holdings were displayed, so we have\r
3208                            * to move the position two files to the right to\r
3209                            * create room for them!\r
3210                            */\r
3211                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */\r
3212                           /* Get a move list just to see the header, which\r
3213                              will tell us whether this is really bug or zh */\r
3214                           if (ics_getting_history == H_FALSE) {\r
3215                             ics_getting_history = H_REQUESTED;\r
3216                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3217                             SendToICS(str);\r
3218                           }\r
3219                         }\r
3220                         new_piece[0] = NULLCHAR;\r
3221                         sscanf(parse, "game %d white [%s black [%s <- %s",\r
3222                                &gamenum, white_holding, black_holding,\r
3223                                new_piece);\r
3224                         white_holding[strlen(white_holding)-1] = NULLCHAR;\r
3225                         black_holding[strlen(black_holding)-1] = NULLCHAR;\r
3226                         /* [HGM] copy holdings to board holdings area */\r
3227                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);\r
3228                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);\r
3229 #if ZIPPY\r
3230                         if (appData.zippyPlay && first.initDone) {\r
3231                             ZippyHoldings(white_holding, black_holding,\r
3232                                           new_piece);\r
3233                         }\r
3234 #endif /*ZIPPY*/\r
3235                         if (tinyLayout || smallLayout) {\r
3236                             char wh[16], bh[16];\r
3237                             PackHolding(wh, white_holding);\r
3238                             PackHolding(bh, black_holding);\r
3239                             sprintf(str, "[%s-%s] %s-%s", wh, bh,\r
3240                                     gameInfo.white, gameInfo.black);\r
3241                         } else {\r
3242                             sprintf(str, "%s [%s] vs. %s [%s]",\r
3243                                     gameInfo.white, white_holding,\r
3244                                     gameInfo.black, black_holding);\r
3245                         }\r
3246 \r
3247                         DrawPosition(FALSE, boards[currentMove]);\r
3248                         DisplayTitle(str);\r
3249                     }\r
3250                     /* Suppress following prompt */\r
3251                     if (looking_at(buf, &i, "*% ")) {\r
3252                         savingComment = FALSE;\r
3253                     }\r
3254                     next_out = i;\r
3255                 }\r
3256                 continue;\r
3257             }\r
3258 \r
3259             i++;                /* skip unparsed character and loop back */\r
3260         }\r
3261         \r
3262         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window\r
3263             started != STARTED_HOLDINGS && i > next_out) {\r
3264             SendToPlayer(&buf[next_out], i - next_out);\r
3265             next_out = i;\r
3266         }\r
3267         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above\r
3268         \r
3269         leftover_len = buf_len - leftover_start;\r
3270         /* if buffer ends with something we couldn't parse,\r
3271            reparse it after appending the next read */\r
3272         \r
3273     } else if (count == 0) {\r
3274         RemoveInputSource(isr);\r
3275         DisplayFatalError(_("Connection closed by ICS"), 0, 0);\r
3276     } else {\r
3277         DisplayFatalError(_("Error reading from ICS"), error, 1);\r
3278     }\r
3279 }\r
3280 \r
3281 \r
3282 /* Board style 12 looks like this:\r
3283    \r
3284    <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
3285    \r
3286  * The "<12> " is stripped before it gets to this routine.  The two\r
3287  * trailing 0's (flip state and clock ticking) are later addition, and\r
3288  * some chess servers may not have them, or may have only the first.\r
3289  * Additional trailing fields may be added in the future.  \r
3290  */\r
3291 \r
3292 #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
3293 \r
3294 #define RELATION_OBSERVING_PLAYED    0\r
3295 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */\r
3296 #define RELATION_PLAYING_MYMOVE      1\r
3297 #define RELATION_PLAYING_NOTMYMOVE  -1\r
3298 #define RELATION_EXAMINING           2\r
3299 #define RELATION_ISOLATED_BOARD     -3\r
3300 #define RELATION_STARTING_POSITION  -4   /* FICS only */\r
3301 \r
3302 void\r
3303 ParseBoard12(string)\r
3304      char *string;\r
3305\r
3306     GameMode newGameMode;\r
3307     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;\r
3308     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;\r
3309     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;\r
3310     char to_play, board_chars[200];\r
3311     char move_str[500], str[500], elapsed_time[500];\r
3312     char black[32], white[32];\r
3313     Board board;\r
3314     int prevMove = currentMove;\r
3315     int ticking = 2;\r
3316     ChessMove moveType;\r
3317     int fromX, fromY, toX, toY;\r
3318     char promoChar;\r
3319     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */\r
3320     char *bookHit = NULL; // [HGM] book\r
3321 \r
3322     fromX = fromY = toX = toY = -1;\r
3323     \r
3324     newGame = FALSE;\r
3325 \r
3326     if (appData.debugMode)\r
3327       fprintf(debugFP, _("Parsing board: %s\n"), string);\r
3328 \r
3329     move_str[0] = NULLCHAR;\r
3330     elapsed_time[0] = NULLCHAR;\r
3331     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */\r
3332         int  i = 0, j;\r
3333         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {\r
3334             if(string[i] == ' ') { ranks++; files = 0; }\r
3335             else files++;\r
3336             i++;\r
3337         }\r
3338         for(j = 0; j <i; j++) board_chars[j] = string[j];\r
3339         board_chars[i] = '\0';\r
3340         string += i + 1;\r
3341     }\r
3342     n = sscanf(string, PATTERN, &to_play, &double_push,\r
3343                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,\r
3344                &gamenum, white, black, &relation, &basetime, &increment,\r
3345                &white_stren, &black_stren, &white_time, &black_time,\r
3346                &moveNum, str, elapsed_time, move_str, &ics_flip,\r
3347                &ticking);\r
3348 \r
3349     if (n < 21) {\r
3350         sprintf(str, _("Failed to parse board string:\n\"%s\""), string);\r
3351         DisplayError(str, 0);\r
3352         return;\r
3353     }\r
3354 \r
3355     /* Convert the move number to internal form */\r
3356     moveNum = (moveNum - 1) * 2;\r
3357     if (to_play == 'B') moveNum++;\r
3358     if (moveNum >= MAX_MOVES) {\r
3359       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),\r
3360                         0, 1);\r
3361       return;\r
3362     }\r
3363     \r
3364     switch (relation) {\r
3365       case RELATION_OBSERVING_PLAYED:\r
3366       case RELATION_OBSERVING_STATIC:\r
3367         if (gamenum == -1) {\r
3368             /* Old ICC buglet */\r
3369             relation = RELATION_OBSERVING_STATIC;\r
3370         }\r
3371         newGameMode = IcsObserving;\r
3372         break;\r
3373       case RELATION_PLAYING_MYMOVE:\r
3374       case RELATION_PLAYING_NOTMYMOVE:\r
3375         newGameMode =\r
3376           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?\r
3377             IcsPlayingWhite : IcsPlayingBlack;\r
3378         break;\r
3379       case RELATION_EXAMINING:\r
3380         newGameMode = IcsExamining;\r
3381         break;\r
3382       case RELATION_ISOLATED_BOARD:\r
3383       default:\r
3384         /* Just display this board.  If user was doing something else,\r
3385            we will forget about it until the next board comes. */ \r
3386         newGameMode = IcsIdle;\r
3387         break;\r
3388       case RELATION_STARTING_POSITION:\r
3389         newGameMode = gameMode;\r
3390         break;\r
3391     }\r
3392     \r
3393     /* Modify behavior for initial board display on move listing\r
3394        of wild games.\r
3395        */\r
3396     switch (ics_getting_history) {\r
3397       case H_FALSE:\r
3398       case H_REQUESTED:\r
3399         break;\r
3400       case H_GOT_REQ_HEADER:\r
3401       case H_GOT_UNREQ_HEADER:\r
3402         /* This is the initial position of the current game */\r
3403         gamenum = ics_gamenum;\r
3404         moveNum = 0;            /* old ICS bug workaround */\r
3405         if (to_play == 'B') {\r
3406           startedFromSetupPosition = TRUE;\r
3407           blackPlaysFirst = TRUE;\r
3408           moveNum = 1;\r
3409           if (forwardMostMove == 0) forwardMostMove = 1;\r
3410           if (backwardMostMove == 0) backwardMostMove = 1;\r
3411           if (currentMove == 0) currentMove = 1;\r
3412         }\r
3413         newGameMode = gameMode;\r
3414         relation = RELATION_STARTING_POSITION; /* ICC needs this */\r
3415         break;\r
3416       case H_GOT_UNWANTED_HEADER:\r
3417         /* This is an initial board that we don't want */\r
3418         return;\r
3419       case H_GETTING_MOVES:\r
3420         /* Should not happen */\r
3421         DisplayError(_("Error gathering move list: extra board"), 0);\r
3422         ics_getting_history = H_FALSE;\r
3423         return;\r
3424     }\r
3425     \r
3426     /* Take action if this is the first board of a new game, or of a\r
3427        different game than is currently being displayed.  */\r
3428     if (gamenum != ics_gamenum || newGameMode != gameMode ||\r
3429         relation == RELATION_ISOLATED_BOARD) {\r
3430         \r
3431         /* Forget the old game and get the history (if any) of the new one */\r
3432         if (gameMode != BeginningOfGame) {\r
3433           Reset(FALSE, TRUE);\r
3434         }\r
3435         newGame = TRUE;\r
3436         if (appData.autoRaiseBoard) BoardToTop();\r
3437         prevMove = -3;\r
3438         if (gamenum == -1) {\r
3439             newGameMode = IcsIdle;\r
3440         } else if (moveNum > 0 && newGameMode != IcsIdle &&\r
3441                    appData.getMoveList) {\r
3442             /* Need to get game history */\r
3443             ics_getting_history = H_REQUESTED;\r
3444             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3445             SendToICS(str);\r
3446         }\r
3447         \r
3448         /* Initially flip the board to have black on the bottom if playing\r
3449            black or if the ICS flip flag is set, but let the user change\r
3450            it with the Flip View button. */\r
3451         flipView = appData.autoFlipView ? \r
3452           (newGameMode == IcsPlayingBlack) || ics_flip :\r
3453           appData.flipView;\r
3454         \r
3455         /* Done with values from previous mode; copy in new ones */\r
3456         gameMode = newGameMode;\r
3457         ModeHighlight();\r
3458         ics_gamenum = gamenum;\r
3459         if (gamenum == gs_gamenum) {\r
3460             int klen = strlen(gs_kind);\r
3461             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;\r
3462             sprintf(str, "ICS %s", gs_kind);\r
3463             gameInfo.event = StrSave(str);\r
3464         } else {\r
3465             gameInfo.event = StrSave("ICS game");\r
3466         }\r
3467         gameInfo.site = StrSave(appData.icsHost);\r
3468         gameInfo.date = PGNDate();\r
3469         gameInfo.round = StrSave("-");\r
3470         gameInfo.white = StrSave(white);\r
3471         gameInfo.black = StrSave(black);\r
3472         timeControl = basetime * 60 * 1000;\r
3473         timeControl_2 = 0;\r
3474         timeIncrement = increment * 1000;\r
3475         movesPerSession = 0;\r
3476         gameInfo.timeControl = TimeControlTagValue();\r
3477         VariantSwitch(board, StringToVariant(gameInfo.event) );\r
3478   if (appData.debugMode) {\r
3479     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);\r
3480     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));\r
3481     setbuf(debugFP, NULL);\r
3482   }\r
3483 \r
3484         gameInfo.outOfBook = NULL;\r
3485         \r
3486         /* Do we have the ratings? */\r
3487         if (strcmp(player1Name, white) == 0 &&\r
3488             strcmp(player2Name, black) == 0) {\r
3489             if (appData.debugMode)\r
3490               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3491                       player1Rating, player2Rating);\r
3492             gameInfo.whiteRating = player1Rating;\r
3493             gameInfo.blackRating = player2Rating;\r
3494         } else if (strcmp(player2Name, white) == 0 &&\r
3495                    strcmp(player1Name, black) == 0) {\r
3496             if (appData.debugMode)\r
3497               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3498                       player2Rating, player1Rating);\r
3499             gameInfo.whiteRating = player2Rating;\r
3500             gameInfo.blackRating = player1Rating;\r
3501         }\r
3502         player1Name[0] = player2Name[0] = NULLCHAR;\r
3503 \r
3504         /* Silence shouts if requested */\r
3505         if (appData.quietPlay &&\r
3506             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {\r
3507             SendToICS(ics_prefix);\r
3508             SendToICS("set shout 0\n");\r
3509         }\r
3510     }\r
3511     \r
3512     /* Deal with midgame name changes */\r
3513     if (!newGame) {\r
3514         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {\r
3515             if (gameInfo.white) free(gameInfo.white);\r
3516             gameInfo.white = StrSave(white);\r
3517         }\r
3518         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {\r
3519             if (gameInfo.black) free(gameInfo.black);\r
3520             gameInfo.black = StrSave(black);\r
3521         }\r
3522     }\r
3523     \r
3524     /* Throw away game result if anything actually changes in examine mode */\r
3525     if (gameMode == IcsExamining && !newGame) {\r
3526         gameInfo.result = GameUnfinished;\r
3527         if (gameInfo.resultDetails != NULL) {\r
3528             free(gameInfo.resultDetails);\r
3529             gameInfo.resultDetails = NULL;\r
3530         }\r
3531     }\r
3532     \r
3533     /* In pausing && IcsExamining mode, we ignore boards coming\r
3534        in if they are in a different variation than we are. */\r
3535     if (pauseExamInvalid) return;\r
3536     if (pausing && gameMode == IcsExamining) {\r
3537         if (moveNum <= pauseExamForwardMostMove) {\r
3538             pauseExamInvalid = TRUE;\r
3539             forwardMostMove = pauseExamForwardMostMove;\r
3540             return;\r
3541         }\r
3542     }\r
3543     \r
3544   if (appData.debugMode) {\r
3545     fprintf(debugFP, "load %dx%d board\n", files, ranks);\r
3546   }\r
3547     /* Parse the board */\r
3548     for (k = 0; k < ranks; k++) {\r
3549       for (j = 0; j < files; j++)\r
3550         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);\r
3551       if(gameInfo.holdingsWidth > 1) {\r
3552            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;\r
3553            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;\r
3554       }\r
3555     }\r
3556     CopyBoard(boards[moveNum], board);\r
3557     if (moveNum == 0) {\r
3558         startedFromSetupPosition =\r
3559           !CompareBoards(board, initialPosition);\r
3560         if(startedFromSetupPosition)\r
3561             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */\r
3562     }\r
3563 \r
3564     /* [HGM] Set castling rights. Take the outermost Rooks,\r
3565        to make it also work for FRC opening positions. Note that board12\r
3566        is really defective for later FRC positions, as it has no way to\r
3567        indicate which Rook can castle if they are on the same side of King.\r
3568        For the initial position we grant rights to the outermost Rooks,\r
3569        and remember thos rights, and we then copy them on positions\r
3570        later in an FRC game. This means WB might not recognize castlings with\r
3571        Rooks that have moved back to their original position as illegal,\r
3572        but in ICS mode that is not its job anyway.\r
3573     */\r
3574     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)\r
3575     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;\r
3576 \r
3577         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)\r
3578             if(board[0][i] == WhiteRook) j = i;\r
3579         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3580         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)\r
3581             if(board[0][i] == WhiteRook) j = i;\r
3582         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3583         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)\r
3584             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;\r
3585         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3586         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)\r
3587             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;\r
3588         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3589 \r
3590         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }\r
3591         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)\r
3592             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;\r
3593         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)\r
3594             if(board[BOARD_HEIGHT-1][k] == bKing)\r
3595                 initialRights[5] = castlingRights[moveNum][5] = k;\r
3596     } else { int r;\r
3597         r = castlingRights[moveNum][0] = initialRights[0];\r
3598         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;\r
3599         r = castlingRights[moveNum][1] = initialRights[1];\r
3600         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;\r
3601         r = castlingRights[moveNum][3] = initialRights[3];\r
3602         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;\r
3603         r = castlingRights[moveNum][4] = initialRights[4];\r
3604         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;\r
3605         /* wildcastle kludge: always assume King has rights */\r
3606         r = castlingRights[moveNum][2] = initialRights[2];\r
3607         r = castlingRights[moveNum][5] = initialRights[5];\r
3608     }\r
3609     /* [HGM] e.p. rights. Assume that ICS sends file number here? */\r
3610     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;\r
3611 \r
3612     \r
3613     if (ics_getting_history == H_GOT_REQ_HEADER ||\r
3614         ics_getting_history == H_GOT_UNREQ_HEADER) {\r
3615         /* This was an initial position from a move list, not\r
3616            the current position */\r
3617         return;\r
3618     }\r
3619     \r
3620     /* Update currentMove and known move number limits */\r
3621     newMove = newGame || moveNum > forwardMostMove;\r
3622 \r
3623     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */\r
3624     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {\r
3625         takeback = forwardMostMove - moveNum;\r
3626         for (i = 0; i < takeback; i++) {\r
3627              if (appData.debugMode) fprintf(debugFP, "take back move\n");\r
3628              SendToProgram("undo\n", &first);\r
3629         }\r
3630     }\r
3631 \r
3632     if (newGame) {\r
3633         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3634         if (gameMode == IcsExamining && moveNum == 0) {\r
3635           /* Workaround for ICS limitation: we are not told the wild\r
3636              type when starting to examine a game.  But if we ask for\r
3637              the move list, the move list header will tell us */\r
3638             ics_getting_history = H_REQUESTED;\r
3639             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3640             SendToICS(str);\r
3641         }\r
3642     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove\r
3643                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {\r
3644         forwardMostMove = moveNum;\r
3645         if (!pausing || currentMove > forwardMostMove)\r
3646           currentMove = forwardMostMove;\r
3647     } else {\r
3648         /* New part of history that is not contiguous with old part */ \r
3649         if (pausing && gameMode == IcsExamining) {\r
3650             pauseExamInvalid = TRUE;\r
3651             forwardMostMove = pauseExamForwardMostMove;\r
3652             return;\r
3653         }\r
3654         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3655         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {\r
3656             ics_getting_history = H_REQUESTED;\r
3657             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3658             SendToICS(str);\r
3659         }\r
3660     }\r
3661     \r
3662     /* Update the clocks */\r
3663     if (strchr(elapsed_time, '.')) {\r
3664       /* Time is in ms */\r
3665       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;\r
3666       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;\r
3667     } else {\r
3668       /* Time is in seconds */\r
3669       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;\r
3670       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;\r
3671     }\r
3672       \r
3673 \r
3674 #if ZIPPY\r
3675     if (appData.zippyPlay && newGame &&\r
3676         gameMode != IcsObserving && gameMode != IcsIdle &&\r
3677         gameMode != IcsExamining)\r
3678       ZippyFirstBoard(moveNum, basetime, increment);\r
3679 #endif\r
3680     \r
3681     /* Put the move on the move list, first converting\r
3682        to canonical algebraic form. */\r
3683     if (moveNum > 0) {\r
3684   if (appData.debugMode) {\r
3685     if (appData.debugMode) { int f = forwardMostMove;\r
3686         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,\r
3687                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
3688     }\r
3689     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);\r
3690     fprintf(debugFP, "moveNum = %d\n", moveNum);\r
3691     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);\r
3692     setbuf(debugFP, NULL);\r
3693   }\r
3694         if (moveNum <= backwardMostMove) {\r
3695             /* We don't know what the board looked like before\r
3696                this move.  Punt. */\r
3697             strcpy(parseList[moveNum - 1], move_str);\r
3698             strcat(parseList[moveNum - 1], " ");\r
3699             strcat(parseList[moveNum - 1], elapsed_time);\r
3700             moveList[moveNum - 1][0] = NULLCHAR;\r
3701         } else if (strcmp(move_str, "none") == 0) {\r
3702             // [HGM] long SAN: swapped order; test for 'none' before parsing move\r
3703             /* Again, we don't know what the board looked like;\r
3704                this is really the start of the game. */\r
3705             parseList[moveNum - 1][0] = NULLCHAR;\r
3706             moveList[moveNum - 1][0] = NULLCHAR;\r
3707             backwardMostMove = moveNum;\r
3708             startedFromSetupPosition = TRUE;\r
3709             fromX = fromY = toX = toY = -1;\r
3710         } else {\r
3711           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. \r
3712           //                 So we parse the long-algebraic move string in stead of the SAN move\r
3713           int valid; char buf[MSG_SIZ], *prom;\r
3714 \r
3715           // str looks something like "Q/a1-a2"; kill the slash\r
3716           if(str[1] == '/') \r
3717                 sprintf(buf, "%c%s", str[0], str+2);\r
3718           else  strcpy(buf, str); // might be castling\r
3719           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) \r
3720                 strcat(buf, prom); // long move lacks promo specification!\r
3721           if(!appData.testLegality) {\r
3722                 if(appData.debugMode) \r
3723                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);\r
3724                 strcpy(move_str, buf);\r
3725           }\r
3726           valid = ParseOneMove(move_str, moveNum - 1, &moveType,\r
3727                                 &fromX, &fromY, &toX, &toY, &promoChar)\r
3728                || ParseOneMove(buf, moveNum - 1, &moveType,\r
3729                                 &fromX, &fromY, &toX, &toY, &promoChar);\r
3730           // end of long SAN patch\r
3731           if (valid) {\r
3732             (void) CoordsToAlgebraic(boards[moveNum - 1],\r
3733                                      PosFlags(moveNum - 1), EP_UNKNOWN,\r
3734                                      fromY, fromX, toY, toX, promoChar,\r
3735                                      parseList[moveNum-1]);\r
3736             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,\r
3737                              castlingRights[moveNum]) ) {\r
3738               case MT_NONE:\r
3739               case MT_STALEMATE:\r
3740               default:\r
3741                 break;\r
3742               case MT_CHECK:\r
3743                 if(gameInfo.variant != VariantShogi)\r
3744                     strcat(parseList[moveNum - 1], "+");\r
3745                 break;\r
3746               case MT_CHECKMATE:\r
3747                 strcat(parseList[moveNum - 1], "#");\r
3748                 break;\r
3749             }\r
3750             strcat(parseList[moveNum - 1], " ");\r
3751             strcat(parseList[moveNum - 1], elapsed_time);\r
3752             /* currentMoveString is set as a side-effect of ParseOneMove */\r
3753             strcpy(moveList[moveNum - 1], currentMoveString);\r
3754             strcat(moveList[moveNum - 1], "\n");\r
3755           } else {\r
3756             /* Move from ICS was illegal!?  Punt. */\r
3757   if (appData.debugMode) {\r
3758     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);\r
3759     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
3760   }\r
3761 #if 0\r
3762             if (appData.testLegality && appData.debugMode) {\r
3763                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);\r
3764                 DisplayError(str, 0);\r
3765             }\r
3766 #endif\r
3767             strcpy(parseList[moveNum - 1], move_str);\r
3768             strcat(parseList[moveNum - 1], " ");\r
3769             strcat(parseList[moveNum - 1], elapsed_time);\r
3770             moveList[moveNum - 1][0] = NULLCHAR;\r
3771             fromX = fromY = toX = toY = -1;\r
3772           }\r
3773         }\r
3774   if (appData.debugMode) {\r
3775     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);\r
3776     setbuf(debugFP, NULL);\r
3777   }\r
3778 \r
3779 #if ZIPPY\r
3780         /* Send move to chess program (BEFORE animating it). */\r
3781         if (appData.zippyPlay && !newGame && newMove && \r
3782            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {\r
3783 \r
3784             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||\r
3785                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {\r
3786                 if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3787                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),\r
3788                             move_str);\r
3789                     DisplayError(str, 0);\r
3790                 } else {\r
3791                     if (first.sendTime) {\r
3792                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);\r
3793                     }\r
3794                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book\r
3795                     if (firstMove && !bookHit) {\r
3796                         firstMove = FALSE;\r
3797                         if (first.useColors) {\r
3798                           SendToProgram(gameMode == IcsPlayingWhite ?\r
3799                                         "white\ngo\n" :\r
3800                                         "black\ngo\n", &first);\r
3801                         } else {\r
3802                           SendToProgram("go\n", &first);\r
3803                         }\r
3804                         first.maybeThinking = TRUE;\r
3805                     }\r
3806                 }\r
3807             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {\r
3808               if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3809                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);\r
3810                 DisplayError(str, 0);\r
3811               } else {\r
3812                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!\r
3813                 SendMoveToProgram(moveNum - 1, &first);\r
3814               }\r
3815             }\r
3816         }\r
3817 #endif\r
3818     }\r
3819 \r
3820     if (moveNum > 0 && !gotPremove) {\r
3821         /* If move comes from a remote source, animate it.  If it\r
3822            isn't remote, it will have already been animated. */\r
3823         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {\r
3824             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);\r
3825         }\r
3826         if (!pausing && appData.highlightLastMove) {\r
3827             SetHighlights(fromX, fromY, toX, toY);\r
3828         }\r
3829     }\r
3830     \r
3831     /* Start the clocks */\r
3832     whiteFlag = blackFlag = FALSE;\r
3833     appData.clockMode = !(basetime == 0 && increment == 0);\r
3834     if (ticking == 0) {\r
3835       ics_clock_paused = TRUE;\r
3836       StopClocks();\r
3837     } else if (ticking == 1) {\r
3838       ics_clock_paused = FALSE;\r
3839     }\r
3840     if (gameMode == IcsIdle ||\r
3841         relation == RELATION_OBSERVING_STATIC ||\r
3842         relation == RELATION_EXAMINING ||\r
3843         ics_clock_paused)\r
3844       DisplayBothClocks();\r
3845     else\r
3846       StartClocks();\r
3847     \r
3848     /* Display opponents and material strengths */\r
3849     if (gameInfo.variant != VariantBughouse &&\r
3850         gameInfo.variant != VariantCrazyhouse) {\r
3851         if (tinyLayout || smallLayout) {\r
3852             if(gameInfo.variant == VariantNormal)\r
3853                 sprintf(str, "%s(%d) %s(%d) {%d %d}", \r
3854                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3855                     basetime, increment);\r
3856             else\r
3857                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", \r
3858                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3859                     basetime, increment, (int) gameInfo.variant);\r
3860         } else {\r
3861             if(gameInfo.variant == VariantNormal)\r
3862                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", \r
3863                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3864                     basetime, increment);\r
3865             else\r
3866                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", \r
3867                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3868                     basetime, increment, VariantName(gameInfo.variant));\r
3869         }\r
3870         DisplayTitle(str);\r
3871   if (appData.debugMode) {\r
3872     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);\r
3873   }\r
3874     }\r
3875 \r
3876    \r
3877     /* Display the board */\r
3878     if (!pausing) {\r
3879       \r
3880       if (appData.premove)\r
3881           if (!gotPremove || \r
3882              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||\r
3883              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))\r
3884               ClearPremoveHighlights();\r
3885 \r
3886       DrawPosition(FALSE, boards[currentMove]);\r
3887       DisplayMove(moveNum - 1);\r
3888       if (appData.ringBellAfterMoves && !ics_user_moved)\r
3889         RingBell();\r
3890     }\r
3891 \r
3892     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
3893 #if ZIPPY\r
3894     if(bookHit) { // [HGM] book: simulate book reply\r
3895         static char bookMove[MSG_SIZ]; // a bit generous?\r
3896 \r
3897         programStats.nodes = programStats.depth = programStats.time = \r
3898         programStats.score = programStats.got_only_move = 0;\r
3899         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
3900 \r
3901         strcpy(bookMove, "move ");\r
3902         strcat(bookMove, bookHit);\r
3903         HandleMachineMove(bookMove, &first);\r
3904     }\r
3905 #endif\r
3906 }\r
3907 \r
3908 void\r
3909 GetMoveListEvent()\r
3910 {\r
3911     char buf[MSG_SIZ];\r
3912     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {\r
3913         ics_getting_history = H_REQUESTED;\r
3914         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);\r
3915         SendToICS(buf);\r
3916     }\r
3917 }\r
3918 \r
3919 void\r
3920 AnalysisPeriodicEvent(force)\r
3921      int force;\r
3922 {\r
3923     if (((programStats.ok_to_send == 0 || programStats.line_is_book)\r
3924          && !force) || !appData.periodicUpdates)\r
3925       return;\r
3926 \r
3927     /* Send . command to Crafty to collect stats */\r
3928     SendToProgram(".\n", &first);\r
3929 \r
3930     /* Don't send another until we get a response (this makes\r
3931        us stop sending to old Crafty's which don't understand\r
3932        the "." command (sending illegal cmds resets node count & time,\r
3933        which looks bad)) */\r
3934     programStats.ok_to_send = 0;\r
3935 }\r
3936 \r
3937 void\r
3938 SendMoveToProgram(moveNum, cps)\r
3939      int moveNum;\r
3940      ChessProgramState *cps;\r
3941 {\r
3942     char buf[MSG_SIZ];\r
3943 \r
3944     if (cps->useUsermove) {\r
3945       SendToProgram("usermove ", cps);\r
3946     }\r
3947     if (cps->useSAN) {\r
3948       char *space;\r
3949       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {\r
3950         int len = space - parseList[moveNum];\r
3951         memcpy(buf, parseList[moveNum], len);\r
3952         buf[len++] = '\n';\r
3953         buf[len] = NULLCHAR;\r
3954       } else {\r
3955         sprintf(buf, "%s\n", parseList[moveNum]);\r
3956       }\r
3957       SendToProgram(buf, cps);\r
3958     } else {\r
3959       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */\r
3960         AlphaRank(moveList[moveNum], 4);\r
3961         SendToProgram(moveList[moveNum], cps);\r
3962         AlphaRank(moveList[moveNum], 4); // and back\r
3963       } else\r
3964       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by\r
3965        * the engine. It would be nice to have a better way to identify castle \r
3966        * moves here. */\r
3967       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)\r
3968                                                                          && cps->useOOCastle) {\r
3969         int fromX = moveList[moveNum][0] - AAA; \r
3970         int fromY = moveList[moveNum][1] - ONE;\r
3971         int toX = moveList[moveNum][2] - AAA; \r
3972         int toY = moveList[moveNum][3] - ONE;\r
3973         if((boards[moveNum][fromY][fromX] == WhiteKing \r
3974             && boards[moveNum][toY][toX] == WhiteRook)\r
3975            || (boards[moveNum][fromY][fromX] == BlackKing \r
3976                && boards[moveNum][toY][toX] == BlackRook)) {\r
3977           if(toX > fromX) SendToProgram("O-O\n", cps);\r
3978           else SendToProgram("O-O-O\n", cps);\r
3979         }\r
3980         else SendToProgram(moveList[moveNum], cps);\r
3981       }\r
3982       else SendToProgram(moveList[moveNum], cps);\r
3983       /* End of additions by Tord */\r
3984     }\r
3985 \r
3986     /* [HGM] setting up the opening has brought engine in force mode! */\r
3987     /*       Send 'go' if we are in a mode where machine should play. */\r
3988     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&\r
3989         (gameMode == TwoMachinesPlay   ||\r
3990 #ifdef ZIPPY\r
3991          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||\r
3992 #endif\r
3993          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {\r
3994         SendToProgram("go\n", cps);\r
3995   if (appData.debugMode) {\r
3996     fprintf(debugFP, "(extra)\n");\r
3997   }\r
3998     }\r
3999     setboardSpoiledMachineBlack = 0;\r
4000 }\r
4001 \r
4002 void\r
4003 SendMoveToICS(moveType, fromX, fromY, toX, toY)\r
4004      ChessMove moveType;\r
4005      int fromX, fromY, toX, toY;\r
4006 {\r
4007     char user_move[MSG_SIZ];\r
4008 \r
4009     switch (moveType) {\r
4010       default:\r
4011         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),\r
4012                 (int)moveType, fromX, fromY, toX, toY);\r
4013         DisplayError(user_move + strlen("say "), 0);\r
4014         break;\r
4015       case WhiteKingSideCastle:\r
4016       case BlackKingSideCastle:\r
4017       case WhiteQueenSideCastleWild:\r
4018       case BlackQueenSideCastleWild:\r
4019       /* PUSH Fabien */\r
4020       case WhiteHSideCastleFR:\r
4021       case BlackHSideCastleFR:\r
4022       /* POP Fabien */\r
4023         sprintf(user_move, "o-o\n");\r
4024         break;\r
4025       case WhiteQueenSideCastle:\r
4026       case BlackQueenSideCastle:\r
4027       case WhiteKingSideCastleWild:\r
4028       case BlackKingSideCastleWild:\r
4029       /* PUSH Fabien */\r
4030       case WhiteASideCastleFR:\r
4031       case BlackASideCastleFR:\r
4032       /* POP Fabien */\r
4033         sprintf(user_move, "o-o-o\n");\r
4034         break;\r
4035       case WhitePromotionQueen:\r
4036       case BlackPromotionQueen:\r
4037       case WhitePromotionRook:\r
4038       case BlackPromotionRook:\r
4039       case WhitePromotionBishop:\r
4040       case BlackPromotionBishop:\r
4041       case WhitePromotionKnight:\r
4042       case BlackPromotionKnight:\r
4043       case WhitePromotionKing:\r
4044       case BlackPromotionKing:\r
4045       case WhitePromotionChancellor:\r
4046       case BlackPromotionChancellor:\r
4047       case WhitePromotionArchbishop:\r
4048       case BlackPromotionArchbishop:\r
4049         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)\r
4050             sprintf(user_move, "%c%c%c%c=%c\n",\r
4051                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4052                 PieceToChar(WhiteFerz));\r
4053         else if(gameInfo.variant == VariantGreat)\r
4054             sprintf(user_move, "%c%c%c%c=%c\n",\r
4055                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4056                 PieceToChar(WhiteMan));\r
4057         else\r
4058             sprintf(user_move, "%c%c%c%c=%c\n",\r
4059                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
4060                 PieceToChar(PromoPiece(moveType)));\r
4061         break;\r
4062       case WhiteDrop:\r
4063       case BlackDrop:\r
4064         sprintf(user_move, "%c@%c%c\n",\r
4065                 ToUpper(PieceToChar((ChessSquare) fromX)),\r
4066                 AAA + toX, ONE + toY);\r
4067         break;\r
4068       case NormalMove:\r
4069       case WhiteCapturesEnPassant:\r
4070       case BlackCapturesEnPassant:\r
4071       case IllegalMove:  /* could be a variant we don't quite understand */\r
4072         sprintf(user_move, "%c%c%c%c\n",\r
4073                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);\r
4074         break;\r
4075     }\r
4076     SendToICS(user_move);\r
4077 }\r
4078 \r
4079 void\r
4080 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)\r
4081      int rf, ff, rt, ft;\r
4082      char promoChar;\r
4083      char move[7];\r
4084 {\r
4085     if (rf == DROP_RANK) {\r
4086         sprintf(move, "%c@%c%c\n",\r
4087                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);\r
4088     } else {\r
4089         if (promoChar == 'x' || promoChar == NULLCHAR) {\r
4090             sprintf(move, "%c%c%c%c\n",\r
4091                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);\r
4092         } else {\r
4093             sprintf(move, "%c%c%c%c%c\n",\r
4094                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);\r
4095         }\r
4096     }\r
4097 }\r
4098 \r
4099 void\r
4100 ProcessICSInitScript(f)\r
4101      FILE *f;\r
4102 {\r
4103     char buf[MSG_SIZ];\r
4104 \r
4105     while (fgets(buf, MSG_SIZ, f)) {\r
4106         SendToICSDelayed(buf,(long)appData.msLoginDelay);\r
4107     }\r
4108 \r
4109     fclose(f);\r
4110 }\r
4111 \r
4112 \r
4113 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */\r
4114 void\r
4115 AlphaRank(char *move, int n)\r
4116 {\r
4117 //    char *p = move, c; int x, y;\r
4118 \r
4119     if (appData.debugMode) {\r
4120         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);\r
4121     }\r
4122 \r
4123     if(move[1]=='*' && \r
4124        move[2]>='0' && move[2]<='9' &&\r
4125        move[3]>='a' && move[3]<='x'    ) {\r
4126         move[1] = '@';\r
4127         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;\r
4128         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;\r
4129     } else\r
4130     if(move[0]>='0' && move[0]<='9' &&\r
4131        move[1]>='a' && move[1]<='x' &&\r
4132        move[2]>='0' && move[2]<='9' &&\r
4133        move[3]>='a' && move[3]<='x'    ) {\r
4134         /* input move, Shogi -> normal */\r
4135         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;\r
4136         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;\r
4137         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;\r
4138         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;\r
4139     } else\r
4140     if(move[1]=='@' &&\r
4141        move[3]>='0' && move[3]<='9' &&\r
4142        move[2]>='a' && move[2]<='x'    ) {\r
4143         move[1] = '*';\r
4144         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';\r
4145         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';\r
4146     } else\r
4147     if(\r
4148        move[0]>='a' && move[0]<='x' &&\r
4149        move[3]>='0' && move[3]<='9' &&\r
4150        move[2]>='a' && move[2]<='x'    ) {\r
4151          /* output move, normal -> Shogi */\r
4152         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';\r
4153         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';\r
4154         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';\r
4155         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';\r
4156         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';\r
4157     }\r
4158     if (appData.debugMode) {\r
4159         fprintf(debugFP, "   out = '%s'\n", move);\r
4160     }\r
4161 }\r
4162 \r
4163 /* Parser for moves from gnuchess, ICS, or user typein box */\r
4164 Boolean\r
4165 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)\r
4166      char *move;\r
4167      int moveNum;\r
4168      ChessMove *moveType;\r
4169      int *fromX, *fromY, *toX, *toY;\r
4170      char *promoChar;\r
4171 {       \r
4172     if (appData.debugMode) {\r
4173         fprintf(debugFP, "move to parse: %s\n", move);\r
4174     }\r
4175     *moveType = yylexstr(moveNum, move);\r
4176 \r
4177     switch (*moveType) {\r
4178       case WhitePromotionChancellor:\r
4179       case BlackPromotionChancellor:\r
4180       case WhitePromotionArchbishop:\r
4181       case BlackPromotionArchbishop:\r
4182       case WhitePromotionQueen:\r
4183       case BlackPromotionQueen:\r
4184       case WhitePromotionRook:\r
4185       case BlackPromotionRook:\r
4186       case WhitePromotionBishop:\r
4187       case BlackPromotionBishop:\r
4188       case WhitePromotionKnight:\r
4189       case BlackPromotionKnight:\r
4190       case WhitePromotionKing:\r
4191       case BlackPromotionKing:\r
4192       case NormalMove:\r
4193       case WhiteCapturesEnPassant:\r
4194       case BlackCapturesEnPassant:\r
4195       case WhiteKingSideCastle:\r
4196       case WhiteQueenSideCastle:\r
4197       case BlackKingSideCastle:\r
4198       case BlackQueenSideCastle:\r
4199       case WhiteKingSideCastleWild:\r
4200       case WhiteQueenSideCastleWild:\r
4201       case BlackKingSideCastleWild:\r
4202       case BlackQueenSideCastleWild:\r
4203       /* Code added by Tord: */\r
4204       case WhiteHSideCastleFR:\r
4205       case WhiteASideCastleFR:\r
4206       case BlackHSideCastleFR:\r
4207       case BlackASideCastleFR:\r
4208       /* End of code added by Tord */\r
4209       case IllegalMove:         /* bug or odd chess variant */\r
4210         *fromX = currentMoveString[0] - AAA;\r
4211         *fromY = currentMoveString[1] - ONE;\r
4212         *toX = currentMoveString[2] - AAA;\r
4213         *toY = currentMoveString[3] - ONE;\r
4214         *promoChar = currentMoveString[4];\r
4215         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||\r
4216             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {\r
4217     if (appData.debugMode) {\r
4218         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);\r
4219     }\r
4220             *fromX = *fromY = *toX = *toY = 0;\r
4221             return FALSE;\r
4222         }\r
4223         if (appData.testLegality) {\r
4224           return (*moveType != IllegalMove);\r
4225         } else {\r
4226           return !(fromX == fromY && toX == toY);\r
4227         }\r
4228 \r
4229       case WhiteDrop:\r
4230       case BlackDrop:\r
4231         *fromX = *moveType == WhiteDrop ?\r
4232           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
4233           (int) CharToPiece(ToLower(currentMoveString[0]));\r
4234         *fromY = DROP_RANK;\r
4235         *toX = currentMoveString[2] - AAA;\r
4236         *toY = currentMoveString[3] - ONE;\r
4237         *promoChar = NULLCHAR;\r
4238         return TRUE;\r
4239 \r
4240       case AmbiguousMove:\r
4241       case ImpossibleMove:\r
4242       case (ChessMove) 0:       /* end of file */\r
4243       case ElapsedTime:\r
4244       case Comment:\r
4245       case PGNTag:\r
4246       case NAG:\r
4247       case WhiteWins:\r
4248       case BlackWins:\r
4249       case GameIsDrawn:\r
4250       default:\r
4251     if (appData.debugMode) {\r
4252         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);\r
4253     }\r
4254         /* bug? */\r
4255         *fromX = *fromY = *toX = *toY = 0;\r
4256         *promoChar = NULLCHAR;\r
4257         return FALSE;\r
4258     }\r
4259 }\r
4260 \r
4261 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.\r
4262 // All positions will have equal probability, but the current method will not provide a unique\r
4263 // numbering scheme for arrays that contain 3 or more pieces of the same kind.\r
4264 #define DARK 1\r
4265 #define LITE 2\r
4266 #define ANY 3\r
4267 \r
4268 int squaresLeft[4];\r
4269 int piecesLeft[(int)BlackPawn];\r
4270 int seed, nrOfShuffles;\r
4271 \r
4272 void GetPositionNumber()\r
4273 {       // sets global variable seed\r
4274         int i;\r
4275 \r
4276         seed = appData.defaultFrcPosition;\r
4277         if(seed < 0) { // randomize based on time for negative FRC position numbers\r
4278                 for(i=0; i<50; i++) seed += random();\r
4279                 seed = random() ^ random() >> 8 ^ random() << 8;\r
4280                 if(seed<0) seed = -seed;\r
4281         }\r
4282 }\r
4283 \r
4284 int put(Board board, int pieceType, int rank, int n, int shade)\r
4285 // put the piece on the (n-1)-th empty squares of the given shade\r
4286 {\r
4287         int i;\r
4288 \r
4289         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {\r
4290                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {\r
4291                         board[rank][i] = (ChessSquare) pieceType;\r
4292                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;\r
4293                         squaresLeft[ANY]--;\r
4294                         piecesLeft[pieceType]--; \r
4295                         return i;\r
4296                 }\r
4297         }\r
4298         return -1;\r
4299 }\r
4300 \r
4301 \r
4302 void AddOnePiece(Board board, int pieceType, int rank, int shade)\r
4303 // calculate where the next piece goes, (any empty square), and put it there\r
4304 {\r
4305         int i;\r
4306 \r
4307         i = seed % squaresLeft[shade];\r
4308         nrOfShuffles *= squaresLeft[shade];\r
4309         seed /= squaresLeft[shade];\r
4310         put(board, pieceType, rank, i, shade);\r
4311 }\r
4312 \r
4313 void AddTwoPieces(Board board, int pieceType, int rank)\r
4314 // calculate where the next 2 identical pieces go, (any empty square), and put it there\r
4315 {\r
4316         int i, n=squaresLeft[ANY], j=n-1, k;\r
4317 \r
4318         k = n*(n-1)/2; // nr of possibilities, not counting permutations\r
4319         i = seed % k;  // pick one\r
4320         nrOfShuffles *= k;\r
4321         seed /= k;\r
4322         while(i >= j) i -= j--;\r
4323         j = n - 1 - j; i += j;\r
4324         put(board, pieceType, rank, j, ANY);\r
4325         put(board, pieceType, rank, i, ANY);\r
4326 }\r
4327 \r
4328 void SetUpShuffle(Board board, int number)\r
4329 {\r
4330         int i, p, first=1;\r
4331 \r
4332         GetPositionNumber(); nrOfShuffles = 1;\r
4333 \r
4334         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;\r
4335         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;\r
4336         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];\r
4337 \r
4338         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;\r
4339 \r
4340         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board\r
4341             p = (int) board[0][i];\r
4342             if(p < (int) BlackPawn) piecesLeft[p] ++;\r
4343             board[0][i] = EmptySquare;\r
4344         }\r
4345 \r
4346         if(PosFlags(0) & F_ALL_CASTLE_OK) {\r
4347             // shuffles restricted to allow normal castling put KRR first\r
4348             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle\r
4349                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);\r
4350             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles\r
4351                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);\r
4352             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling\r
4353                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);\r
4354             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling\r
4355                 put(board, WhiteRook, 0, 0, ANY);\r
4356             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle\r
4357         }\r
4358 \r
4359         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)\r
4360             // only for even boards make effort to put pairs of colorbound pieces on opposite colors\r
4361             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {\r
4362                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;\r
4363                 while(piecesLeft[p] >= 2) {\r
4364                     AddOnePiece(board, p, 0, LITE);\r
4365                     AddOnePiece(board, p, 0, DARK);\r
4366                 }\r
4367                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)\r
4368             }\r
4369 \r
4370         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {\r
4371             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere\r
4372             // but we leave King and Rooks for last, to possibly obey FRC restriction\r
4373             if(p == (int)WhiteRook) continue;\r
4374             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations\r
4375             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece\r
4376         }\r
4377 \r
4378         // now everything is placed, except perhaps King (Unicorn) and Rooks\r
4379 \r
4380         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {\r
4381             // Last King gets castling rights\r
4382             while(piecesLeft[(int)WhiteUnicorn]) {\r
4383                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);\r
4384                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;\r
4385             }\r
4386 \r
4387             while(piecesLeft[(int)WhiteKing]) {\r
4388                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);\r
4389                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;\r
4390             }\r
4391 \r
4392 \r
4393         } else {\r
4394             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);\r
4395             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);\r
4396         }\r
4397 \r
4398         // Only Rooks can be left; simply place them all\r
4399         while(piecesLeft[(int)WhiteRook]) {\r
4400                 i = put(board, WhiteRook, 0, 0, ANY);\r
4401                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights\r
4402                         if(first) {\r
4403                                 first=0;\r
4404                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;\r
4405                         }\r
4406                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;\r
4407                 }\r
4408         }\r
4409         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white\r
4410             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;\r
4411         }\r
4412 \r
4413         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize\r
4414 }\r
4415 \r
4416 int SetCharTable( char *table, const char * map )\r
4417 /* [HGM] moved here from winboard.c because of its general usefulness */\r
4418 /*       Basically a safe strcpy that uses the last character as King */\r
4419 {\r
4420     int result = FALSE; int NrPieces;\r
4421 \r
4422     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare \r
4423                     && NrPieces >= 12 && !(NrPieces&1)) {\r
4424         int i; /* [HGM] Accept even length from 12 to 34 */\r
4425 \r
4426         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';\r
4427         for( i=0; i<NrPieces/2-1; i++ ) {\r
4428             table[i] = map[i];\r
4429             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];\r
4430         }\r
4431         table[(int) WhiteKing]  = map[NrPieces/2-1];\r
4432         table[(int) BlackKing]  = map[NrPieces-1];\r
4433 \r
4434         result = TRUE;\r
4435     }\r
4436 \r
4437     return result;\r
4438 }\r
4439 \r
4440 void Prelude(Board board)\r
4441 {       // [HGM] superchess: random selection of exo-pieces\r
4442         int i, j, k; ChessSquare p; \r
4443         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };\r
4444 \r
4445         GetPositionNumber(); // use FRC position number\r
4446 \r
4447         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table\r
4448             SetCharTable(pieceToChar, appData.pieceToCharTable);\r
4449             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) \r
4450                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;\r
4451         }\r
4452 \r
4453         j = seed%4;                 seed /= 4; \r
4454         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);\r
4455         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4456         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4457         j = seed%3 + (seed%3 >= j); seed /= 3; \r
4458         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);\r
4459         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4460         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4461         j = seed%3;                 seed /= 3; \r
4462         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);\r
4463         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4464         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4465         j = seed%2 + (seed%2 >= j); seed /= 2; \r
4466         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);\r
4467         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;\r
4468         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;\r
4469         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);\r
4470         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);\r
4471         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);\r
4472         put(board, exoPieces[0],    0, 0, ANY);\r
4473         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];\r
4474 }\r
4475 \r
4476 void\r
4477 InitPosition(redraw)\r
4478      int redraw;\r
4479 {\r
4480     ChessSquare (* pieces)[BOARD_SIZE];\r
4481     int i, j, pawnRow, overrule,\r
4482     oldx = gameInfo.boardWidth,\r
4483     oldy = gameInfo.boardHeight,\r
4484     oldh = gameInfo.holdingsWidth,\r
4485     oldv = gameInfo.variant;\r
4486 \r
4487     currentMove = forwardMostMove = backwardMostMove = 0;\r
4488     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request\r
4489 \r
4490     /* [AS] Initialize pv info list [HGM] and game status */\r
4491     {\r
4492         for( i=0; i<MAX_MOVES; i++ ) {\r
4493             pvInfoList[i].depth = 0;\r
4494             epStatus[i]=EP_NONE;\r
4495             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
4496         }\r
4497 \r
4498         initialRulePlies = 0; /* 50-move counter start */\r
4499 \r
4500         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;\r
4501         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;\r
4502     }\r
4503 \r
4504     \r
4505     /* [HGM] logic here is completely changed. In stead of full positions */\r
4506     /* the initialized data only consist of the two backranks. The switch */\r
4507     /* selects which one we will use, which is than copied to the Board   */\r
4508     /* initialPosition, which for the rest is initialized by Pawns and    */\r
4509     /* empty squares. This initial position is then copied to boards[0],  */\r
4510     /* possibly after shuffling, so that it remains available.            */\r
4511 \r
4512     gameInfo.holdingsWidth = 0; /* default board sizes */\r
4513     gameInfo.boardWidth    = 8;\r
4514     gameInfo.boardHeight   = 8;\r
4515     gameInfo.holdingsSize  = 0;\r
4516     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */\r
4517     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */\r
4518     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); \r
4519 \r
4520     switch (gameInfo.variant) {\r
4521     case VariantFischeRandom:\r
4522       shuffleOpenings = TRUE;\r
4523     default:\r
4524       pieces = FIDEArray;\r
4525       break;\r
4526     case VariantShatranj:\r
4527       pieces = ShatranjArray;\r
4528       nrCastlingRights = 0;\r
4529       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); \r
4530       break;\r
4531     case VariantTwoKings:\r
4532       pieces = twoKingsArray;\r
4533       break;\r
4534     case VariantCapaRandom:\r
4535       shuffleOpenings = TRUE;\r
4536     case VariantCapablanca:\r
4537       pieces = CapablancaArray;\r
4538       gameInfo.boardWidth = 10;\r
4539       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
4540       break;\r
4541     case VariantGothic:\r
4542       pieces = GothicArray;\r
4543       gameInfo.boardWidth = 10;\r
4544       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
4545       break;\r
4546     case VariantJanus:\r
4547       pieces = JanusArray;\r
4548       gameInfo.boardWidth = 10;\r
4549       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); \r
4550       nrCastlingRights = 6;\r
4551         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
4552         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
4553         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;\r
4554         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
4555         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4556         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;\r
4557       break;\r
4558     case VariantFalcon:\r
4559       pieces = FalconArray;\r
4560       gameInfo.boardWidth = 10;\r
4561       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); \r
4562       break;\r
4563     case VariantXiangqi:\r
4564       pieces = XiangqiArray;\r
4565       gameInfo.boardWidth  = 9;\r
4566       gameInfo.boardHeight = 10;\r
4567       nrCastlingRights = 0;\r
4568       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); \r
4569       break;\r
4570     case VariantShogi:\r
4571       pieces = ShogiArray;\r
4572       gameInfo.boardWidth  = 9;\r
4573       gameInfo.boardHeight = 9;\r
4574       gameInfo.holdingsSize = 7;\r
4575       nrCastlingRights = 0;\r
4576       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); \r
4577       break;\r
4578     case VariantCourier:\r
4579       pieces = CourierArray;\r
4580       gameInfo.boardWidth  = 12;\r
4581       nrCastlingRights = 0;\r
4582       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); \r
4583       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
4584       break;\r
4585     case VariantKnightmate:\r
4586       pieces = KnightmateArray;\r
4587       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); \r
4588       break;\r
4589     case VariantFairy:\r
4590       pieces = fairyArray;\r
4591       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); \r
4592       break;\r
4593     case VariantGreat:\r
4594       pieces = GreatArray;\r
4595       gameInfo.boardWidth = 10;\r
4596       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");\r
4597       gameInfo.holdingsSize = 8;\r
4598       break;\r
4599     case VariantSuper:\r
4600       pieces = FIDEArray;\r
4601       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");\r
4602       gameInfo.holdingsSize = 8;\r
4603       startedFromSetupPosition = TRUE;\r
4604       break;\r
4605     case VariantCrazyhouse:\r
4606     case VariantBughouse:\r
4607       pieces = FIDEArray;\r
4608       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); \r
4609       gameInfo.holdingsSize = 5;\r
4610       break;\r
4611     case VariantWildCastle:\r
4612       pieces = FIDEArray;\r
4613       /* !!?shuffle with kings guaranteed to be on d or e file */\r
4614       shuffleOpenings = 1;\r
4615       break;\r
4616     case VariantNoCastle:\r
4617       pieces = FIDEArray;\r
4618       nrCastlingRights = 0;\r
4619       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
4620       /* !!?unconstrained back-rank shuffle */\r
4621       shuffleOpenings = 1;\r
4622       break;\r
4623     }\r
4624 \r
4625     overrule = 0;\r
4626     if(appData.NrFiles >= 0) {\r
4627         if(gameInfo.boardWidth != appData.NrFiles) overrule++;\r
4628         gameInfo.boardWidth = appData.NrFiles;\r
4629     }\r
4630     if(appData.NrRanks >= 0) {\r
4631         gameInfo.boardHeight = appData.NrRanks;\r
4632     }\r
4633     if(appData.holdingsSize >= 0) {\r
4634         i = appData.holdingsSize;\r
4635         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;\r
4636         gameInfo.holdingsSize = i;\r
4637     }\r
4638     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;\r
4639     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)\r
4640         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);\r
4641 \r
4642     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */\r
4643     if(pawnRow < 1) pawnRow = 1;\r
4644 \r
4645     /* User pieceToChar list overrules defaults */\r
4646     if(appData.pieceToCharTable != NULL)\r
4647         SetCharTable(pieceToChar, appData.pieceToCharTable);\r
4648 \r
4649     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;\r
4650 \r
4651         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)\r
4652             s = (ChessSquare) 0; /* account holding counts in guard band */\r
4653         for( i=0; i<BOARD_HEIGHT; i++ )\r
4654             initialPosition[i][j] = s;\r
4655 \r
4656         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;\r
4657         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];\r
4658         initialPosition[pawnRow][j] = WhitePawn;\r
4659         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;\r
4660         if(gameInfo.variant == VariantXiangqi) {\r
4661             if(j&1) {\r
4662                 initialPosition[pawnRow][j] = \r
4663                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;\r
4664                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {\r
4665                    initialPosition[2][j] = WhiteCannon;\r
4666                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;\r
4667                 }\r
4668             }\r
4669         }\r
4670         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];\r
4671     }\r
4672     if( (gameInfo.variant == VariantShogi) && !overrule ) {\r
4673 \r
4674             j=BOARD_LEFT+1;\r
4675             initialPosition[1][j] = WhiteBishop;\r
4676             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;\r
4677             j=BOARD_RGHT-2;\r
4678             initialPosition[1][j] = WhiteRook;\r
4679             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;\r
4680     }\r
4681 \r
4682     if( nrCastlingRights == -1) {\r
4683         /* [HGM] Build normal castling rights (must be done after board sizing!) */\r
4684         /*       This sets default castling rights from none to normal corners   */\r
4685         /* Variants with other castling rights must set them themselves above    */\r
4686         nrCastlingRights = 6;\r
4687        \r
4688         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
4689         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
4690         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;\r
4691         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
4692         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4693         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;\r
4694      }\r
4695 \r
4696      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);\r
4697      if(gameInfo.variant == VariantGreat) { // promotion commoners\r
4698         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;\r
4699         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;\r
4700         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;\r
4701         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;\r
4702      }\r
4703 #if 0\r
4704     if(gameInfo.variant == VariantFischeRandom) {\r
4705       if( appData.defaultFrcPosition < 0 ) {\r
4706         ShuffleFRC( initialPosition );\r
4707       }\r
4708       else {\r
4709         SetupFRC( initialPosition, appData.defaultFrcPosition );\r
4710       }\r
4711       startedFromSetupPosition = TRUE;\r
4712     } else \r
4713 #else\r
4714   if (appData.debugMode) {\r
4715     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);\r
4716   }\r
4717     if(shuffleOpenings) {\r
4718         SetUpShuffle(initialPosition, appData.defaultFrcPosition);\r
4719         startedFromSetupPosition = TRUE;\r
4720     }\r
4721 #endif\r
4722     if(startedFromPositionFile) {\r
4723       /* [HGM] loadPos: use PositionFile for every new game */\r
4724       CopyBoard(initialPosition, filePosition);\r
4725       for(i=0; i<nrCastlingRights; i++)\r
4726           castlingRights[0][i] = initialRights[i] = fileRights[i];\r
4727       startedFromSetupPosition = TRUE;\r
4728     }\r
4729 \r
4730     CopyBoard(boards[0], initialPosition);\r
4731 \r
4732     if(oldx != gameInfo.boardWidth ||\r
4733        oldy != gameInfo.boardHeight ||\r
4734        oldh != gameInfo.holdingsWidth\r
4735 #ifdef GOTHIC\r
4736        || oldv == VariantGothic ||        // For licensing popups\r
4737        gameInfo.variant == VariantGothic\r
4738 #endif\r
4739 #ifdef FALCON\r
4740        || oldv == VariantFalcon ||\r
4741        gameInfo.variant == VariantFalcon\r
4742 #endif\r
4743                                          )\r
4744             InitDrawingSizes(-2 ,0);\r
4745 \r
4746     if (redraw)\r
4747       DrawPosition(TRUE, boards[currentMove]);\r
4748 }\r
4749 \r
4750 void\r
4751 SendBoard(cps, moveNum)\r
4752      ChessProgramState *cps;\r
4753      int moveNum;\r
4754 {\r
4755     char message[MSG_SIZ];\r
4756     \r
4757     if (cps->useSetboard) {\r
4758       char* fen = PositionToFEN(moveNum, cps->fenOverride);\r
4759       sprintf(message, "setboard %s\n", fen);\r
4760       SendToProgram(message, cps);\r
4761       free(fen);\r
4762 \r
4763     } else {\r
4764       ChessSquare *bp;\r
4765       int i, j;\r
4766       /* Kludge to set black to move, avoiding the troublesome and now\r
4767        * deprecated "black" command.\r
4768        */\r
4769       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);\r
4770 \r
4771       SendToProgram("edit\n", cps);\r
4772       SendToProgram("#\n", cps);\r
4773       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4774         bp = &boards[moveNum][i][BOARD_LEFT];\r
4775         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4776           if ((int) *bp < (int) BlackPawn) {\r
4777             sprintf(message, "%c%c%c\n", PieceToChar(*bp), \r
4778                     AAA + j, ONE + i);\r
4779             if(message[0] == '+' || message[0] == '~') {\r
4780                 sprintf(message, "%c%c%c+\n",\r
4781                         PieceToChar((ChessSquare)(DEMOTED *bp)),\r
4782                         AAA + j, ONE + i);\r
4783             }\r
4784             if(cps->alphaRank) { /* [HGM] shogi: translate coords */\r
4785                 message[1] = BOARD_RGHT   - 1 - j + '1';\r
4786                 message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
4787             }\r
4788             SendToProgram(message, cps);\r
4789           }\r
4790         }\r
4791       }\r
4792     \r
4793       SendToProgram("c\n", cps);\r
4794       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4795         bp = &boards[moveNum][i][BOARD_LEFT];\r
4796         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4797           if (((int) *bp != (int) EmptySquare)\r
4798               && ((int) *bp >= (int) BlackPawn)) {\r
4799             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),\r
4800                     AAA + j, ONE + i);\r
4801             if(message[0] == '+' || message[0] == '~') {\r
4802                 sprintf(message, "%c%c%c+\n",\r
4803                         PieceToChar((ChessSquare)(DEMOTED *bp)),\r
4804                         AAA + j, ONE + i);\r
4805             }\r
4806             if(cps->alphaRank) { /* [HGM] shogi: translate coords */\r
4807                 message[1] = BOARD_RGHT   - 1 - j + '1';\r
4808                 message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
4809             }\r
4810             SendToProgram(message, cps);\r
4811           }\r
4812         }\r
4813       }\r
4814     \r
4815       SendToProgram(".\n", cps);\r
4816     }\r
4817     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */\r
4818 }\r
4819 \r
4820 int\r
4821 IsPromotion(fromX, fromY, toX, toY)\r
4822      int fromX, fromY, toX, toY;\r
4823 {\r
4824     /* [HGM] add Shogi promotions */\r
4825     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;\r
4826     ChessSquare piece;\r
4827 \r
4828     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||\r
4829       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;\r
4830    /* [HGM] Note to self: line above also weeds out drops */\r
4831     piece = boards[currentMove][fromY][fromX];\r
4832     if(gameInfo.variant == VariantShogi) {\r
4833         promotionZoneSize = 3;\r
4834         highestPromotingPiece = (int)WhiteKing;\r
4835         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,\r
4836            and if in normal chess we then allow promotion to King, why not\r
4837            allow promotion of other piece in Shogi?                         */\r
4838     }\r
4839     if((int)piece >= BlackPawn) {\r
4840         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)\r
4841              return FALSE;\r
4842         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;\r
4843     } else {\r
4844         if(  toY < BOARD_HEIGHT - promotionZoneSize &&\r
4845            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;\r
4846     }\r
4847     return ( (int)piece <= highestPromotingPiece );\r
4848 }\r
4849 \r
4850 int\r
4851 InPalace(row, column)\r
4852      int row, column;\r
4853 {   /* [HGM] for Xiangqi */\r
4854     if( (row < 3 || row > BOARD_HEIGHT-4) &&\r
4855          column < (BOARD_WIDTH + 4)/2 &&\r
4856          column > (BOARD_WIDTH - 5)/2 ) return TRUE;\r
4857     return FALSE;\r
4858 }\r
4859 \r
4860 int\r
4861 PieceForSquare (x, y)\r
4862      int x;\r
4863      int y;\r
4864 {\r
4865   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)\r
4866      return -1;\r
4867   else\r
4868      return boards[currentMove][y][x];\r
4869 }\r
4870 \r
4871 int\r
4872 OKToStartUserMove(x, y)\r
4873      int x, y;\r
4874 {\r
4875     ChessSquare from_piece;\r
4876     int white_piece;\r
4877 \r
4878     if (matchMode) return FALSE;\r
4879     if (gameMode == EditPosition) return TRUE;\r
4880 \r
4881     if (x >= 0 && y >= 0)\r
4882       from_piece = boards[currentMove][y][x];\r
4883     else\r
4884       from_piece = EmptySquare;\r
4885 \r
4886     if (from_piece == EmptySquare) return FALSE;\r
4887 \r
4888     white_piece = (int)from_piece >= (int)WhitePawn &&\r
4889       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */\r
4890 \r
4891     switch (gameMode) {\r
4892       case PlayFromGameFile:\r
4893       case AnalyzeFile:\r
4894       case TwoMachinesPlay:\r
4895       case EndOfGame:\r
4896         return FALSE;\r
4897 \r
4898       case IcsObserving:\r
4899       case IcsIdle:\r
4900         return FALSE;\r
4901 \r
4902       case MachinePlaysWhite:\r
4903       case IcsPlayingBlack:\r
4904         if (appData.zippyPlay) return FALSE;\r
4905         if (white_piece) {\r
4906             DisplayMoveError(_("You are playing Black"));\r
4907             return FALSE;\r
4908         }\r
4909         break;\r
4910 \r
4911       case MachinePlaysBlack:\r
4912       case IcsPlayingWhite:\r
4913         if (appData.zippyPlay) return FALSE;\r
4914         if (!white_piece) {\r
4915             DisplayMoveError(_("You are playing White"));\r
4916             return FALSE;\r
4917         }\r
4918         break;\r
4919 \r
4920       case EditGame:\r
4921         if (!white_piece && WhiteOnMove(currentMove)) {\r
4922             DisplayMoveError(_("It is White's turn"));\r
4923             return FALSE;\r
4924         }           \r
4925         if (white_piece && !WhiteOnMove(currentMove)) {\r
4926             DisplayMoveError(_("It is Black's turn"));\r
4927             return FALSE;\r
4928         }           \r
4929         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {\r
4930             /* Editing correspondence game history */\r
4931             /* Could disallow this or prompt for confirmation */\r
4932             cmailOldMove = -1;\r
4933         }\r
4934         if (currentMove < forwardMostMove) {\r
4935             /* Discarding moves */\r
4936             /* Could prompt for confirmation here,\r
4937                but I don't think that's such a good idea */\r
4938             forwardMostMove = currentMove;\r
4939         }\r
4940         break;\r
4941 \r
4942       case BeginningOfGame:\r
4943         if (appData.icsActive) return FALSE;\r
4944         if (!appData.noChessProgram) {\r
4945             if (!white_piece) {\r
4946                 DisplayMoveError(_("You are playing White"));\r
4947                 return FALSE;\r
4948             }\r
4949         }\r
4950         break;\r
4951         \r
4952       case Training:\r
4953         if (!white_piece && WhiteOnMove(currentMove)) {\r
4954             DisplayMoveError(_("It is White's turn"));\r
4955             return FALSE;\r
4956         }           \r
4957         if (white_piece && !WhiteOnMove(currentMove)) {\r
4958             DisplayMoveError(_("It is Black's turn"));\r
4959             return FALSE;\r
4960         }           \r
4961         break;\r
4962 \r
4963       default:\r
4964       case IcsExamining:\r
4965         break;\r
4966     }\r
4967     if (currentMove != forwardMostMove && gameMode != AnalyzeMode\r
4968         && gameMode != AnalyzeFile && gameMode != Training) {\r
4969         DisplayMoveError(_("Displayed position is not current"));\r
4970         return FALSE;\r
4971     }\r
4972     return TRUE;\r
4973 }\r
4974 \r
4975 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;\r
4976 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;\r
4977 int lastLoadGameUseList = FALSE;\r
4978 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];\r
4979 ChessMove lastLoadGameStart = (ChessMove) 0;\r
4980 \r
4981 \r
4982 ChessMove\r
4983 UserMoveTest(fromX, fromY, toX, toY, promoChar)\r
4984      int fromX, fromY, toX, toY;\r
4985      int promoChar;\r
4986 {\r
4987     ChessMove moveType;\r
4988     ChessSquare pdown, pup;\r
4989 \r
4990     if (fromX < 0 || fromY < 0) return ImpossibleMove;\r
4991     if ((fromX == toX) && (fromY == toY)) {\r
4992         return ImpossibleMove;\r
4993     }\r
4994 \r
4995     /* [HGM] suppress all moves into holdings area and guard band */\r
4996     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )\r
4997             return ImpossibleMove;\r
4998 \r
4999     /* [HGM] <sameColor> moved to here from winboard.c */\r
5000     /* note: this code seems to exist for filtering out some obviously illegal premoves */\r
5001     pdown = boards[currentMove][fromY][fromX];\r
5002     pup = boards[currentMove][toY][toX];\r
5003     if (    gameMode != EditPosition &&\r
5004             (WhitePawn <= pdown && pdown < BlackPawn &&\r
5005              WhitePawn <= pup && pup < BlackPawn  ||\r
5006              BlackPawn <= pdown && pdown < EmptySquare &&\r
5007              BlackPawn <= pup && pup < EmptySquare \r
5008             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&\r
5009                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||\r
5010                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) \r
5011         )           )\r
5012          return ImpossibleMove;\r
5013 \r
5014     /* Check if the user is playing in turn.  This is complicated because we\r
5015        let the user "pick up" a piece before it is his turn.  So the piece he\r
5016        tried to pick up may have been captured by the time he puts it down!\r
5017        Therefore we use the color the user is supposed to be playing in this\r
5018        test, not the color of the piece that is currently on the starting\r
5019        square---except in EditGame mode, where the user is playing both\r
5020        sides; fortunately there the capture race can't happen.  (It can\r
5021        now happen in IcsExamining mode, but that's just too bad.  The user\r
5022        will get a somewhat confusing message in that case.)\r
5023        */\r
5024 \r
5025     switch (gameMode) {\r
5026       case PlayFromGameFile:\r
5027       case AnalyzeFile:\r
5028       case TwoMachinesPlay:\r
5029       case EndOfGame:\r
5030       case IcsObserving:\r
5031       case IcsIdle:\r
5032         /* We switched into a game mode where moves are not accepted,\r
5033            perhaps while the mouse button was down. */\r
5034         return ImpossibleMove;\r
5035 \r
5036       case MachinePlaysWhite:\r
5037         /* User is moving for Black */\r
5038         if (WhiteOnMove(currentMove)) {\r
5039             DisplayMoveError(_("It is White's turn"));\r
5040             return ImpossibleMove;\r
5041         }\r
5042         break;\r
5043 \r
5044       case MachinePlaysBlack:\r
5045         /* User is moving for White */\r
5046         if (!WhiteOnMove(currentMove)) {\r
5047             DisplayMoveError(_("It is Black's turn"));\r
5048             return ImpossibleMove;\r
5049         }\r
5050         break;\r
5051 \r
5052       case EditGame:\r
5053       case IcsExamining:\r
5054       case BeginningOfGame:\r
5055       case AnalyzeMode:\r
5056       case Training:\r
5057         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&\r
5058             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {\r
5059             /* User is moving for Black */\r
5060             if (WhiteOnMove(currentMove)) {\r
5061                 DisplayMoveError(_("It is White's turn"));\r
5062                 return ImpossibleMove;\r
5063             }\r
5064         } else {\r
5065             /* User is moving for White */\r
5066             if (!WhiteOnMove(currentMove)) {\r
5067                 DisplayMoveError(_("It is Black's turn"));\r
5068                 return ImpossibleMove;\r
5069             }\r
5070         }\r
5071         break;\r
5072 \r
5073       case IcsPlayingBlack:\r
5074         /* User is moving for Black */\r
5075         if (WhiteOnMove(currentMove)) {\r
5076             if (!appData.premove) {\r
5077                 DisplayMoveError(_("It is White's turn"));\r
5078             } else if (toX >= 0 && toY >= 0) {\r
5079                 premoveToX = toX;\r
5080                 premoveToY = toY;\r
5081                 premoveFromX = fromX;\r
5082                 premoveFromY = fromY;\r
5083                 premovePromoChar = promoChar;\r
5084                 gotPremove = 1;\r
5085                 if (appData.debugMode) \r
5086                     fprintf(debugFP, "Got premove: fromX %d,"\r
5087                             "fromY %d, toX %d, toY %d\n",\r
5088                             fromX, fromY, toX, toY);\r
5089             }\r
5090             return ImpossibleMove;\r
5091         }\r
5092         break;\r
5093 \r
5094       case IcsPlayingWhite:\r
5095         /* User is moving for White */\r
5096         if (!WhiteOnMove(currentMove)) {\r
5097             if (!appData.premove) {\r
5098                 DisplayMoveError(_("It is Black's turn"));\r
5099             } else if (toX >= 0 && toY >= 0) {\r
5100                 premoveToX = toX;\r
5101                 premoveToY = toY;\r
5102                 premoveFromX = fromX;\r
5103                 premoveFromY = fromY;\r
5104                 premovePromoChar = promoChar;\r
5105                 gotPremove = 1;\r
5106                 if (appData.debugMode) \r
5107                     fprintf(debugFP, "Got premove: fromX %d,"\r
5108                             "fromY %d, toX %d, toY %d\n",\r
5109                             fromX, fromY, toX, toY);\r
5110             }\r
5111             return ImpossibleMove;\r
5112         }\r
5113         break;\r
5114 \r
5115       default:\r
5116         break;\r
5117 \r
5118       case EditPosition:\r
5119         /* EditPosition, empty square, or different color piece;\r
5120            click-click move is possible */\r
5121         if (toX == -2 || toY == -2) {\r
5122             boards[0][fromY][fromX] = EmptySquare;\r
5123             return AmbiguousMove;\r
5124         } else if (toX >= 0 && toY >= 0) {\r
5125             boards[0][toY][toX] = boards[0][fromY][fromX];\r
5126             boards[0][fromY][fromX] = EmptySquare;\r
5127             return AmbiguousMove;\r
5128         }\r
5129         return ImpossibleMove;\r
5130     }\r
5131 \r
5132     /* [HGM] If move started in holdings, it means a drop */\r
5133     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { \r
5134          if( pup != EmptySquare ) return ImpossibleMove;\r
5135          if(appData.testLegality) {\r
5136              /* it would be more logical if LegalityTest() also figured out\r
5137               * which drops are legal. For now we forbid pawns on back rank.\r
5138               * Shogi is on its own here...\r
5139               */\r
5140              if( (pdown == WhitePawn || pdown == BlackPawn) &&\r
5141                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )\r
5142                  return(ImpossibleMove); /* no pawn drops on 1st/8th */\r
5143          }\r
5144          return WhiteDrop; /* Not needed to specify white or black yet */\r
5145     }\r
5146 \r
5147     userOfferedDraw = FALSE;\r
5148         \r
5149     /* [HGM] always test for legality, to get promotion info */\r
5150     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),\r
5151                           epStatus[currentMove], castlingRights[currentMove],\r
5152                                          fromY, fromX, toY, toX, promoChar);\r
5153 \r
5154     /* [HGM] but possibly ignore an IllegalMove result */\r
5155     if (appData.testLegality) {\r
5156         if (moveType == IllegalMove || moveType == ImpossibleMove) {\r
5157             DisplayMoveError(_("Illegal move"));\r
5158             return ImpossibleMove;\r
5159         }\r
5160     }\r
5161 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);\r
5162     return moveType;\r
5163     /* [HGM] <popupFix> in stead of calling FinishMove directly, this\r
5164        function is made into one that returns an OK move type if FinishMove\r
5165        should be called. This to give the calling driver routine the\r
5166        opportunity to finish the userMove input with a promotion popup,\r
5167        without bothering the user with this for invalid or illegal moves */\r
5168 \r
5169 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */\r
5170 }\r
5171 \r
5172 /* Common tail of UserMoveEvent and DropMenuEvent */\r
5173 int\r
5174 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)\r
5175      ChessMove moveType;\r
5176      int fromX, fromY, toX, toY;\r
5177      /*char*/int promoChar;\r
5178 {\r
5179     char *bookHit = 0;\r
5180 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);\r
5181     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { \r
5182         // [HGM] superchess: suppress promotions to non-available piece\r
5183         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));\r
5184         if(WhiteOnMove(currentMove)) {\r
5185             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;\r
5186         } else {\r
5187             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;\r
5188         }\r
5189     }\r
5190 \r
5191     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion\r
5192        move type in caller when we know the move is a legal promotion */\r
5193     if(moveType == NormalMove && promoChar)\r
5194         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);\r
5195 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);\r
5196     /* [HGM] convert drag-and-drop piece drops to standard form */\r
5197     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {\r
5198          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
5199          fromX = boards[currentMove][fromY][fromX];\r
5200          fromY = DROP_RANK;\r
5201     }\r
5202 \r
5203     /* [HGM] <popupFix> The following if has been moved here from\r
5204        UserMoveEvent(). Because it seemed to belon here (why not allow\r
5205        piece drops in training games?), and because it can only be\r
5206        performed after it is known to what we promote. */\r
5207     if (gameMode == Training) {\r
5208       /* compare the move played on the board to the next move in the\r
5209        * game. If they match, display the move and the opponent's response. \r
5210        * If they don't match, display an error message.\r
5211        */\r
5212       int saveAnimate;\r
5213       Board testBoard;\r
5214       CopyBoard(testBoard, boards[currentMove]);\r
5215       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);\r
5216 \r
5217       if (CompareBoards(testBoard, boards[currentMove+1])) {\r
5218         ForwardInner(currentMove+1);\r
5219 \r
5220         /* Autoplay the opponent's response.\r
5221          * if appData.animate was TRUE when Training mode was entered,\r
5222          * the response will be animated.\r
5223          */\r
5224         saveAnimate = appData.animate;\r
5225         appData.animate = animateTraining;\r
5226         ForwardInner(currentMove+1);\r
5227         appData.animate = saveAnimate;\r
5228 \r
5229         /* check for the end of the game */\r
5230         if (currentMove >= forwardMostMove) {\r
5231           gameMode = PlayFromGameFile;\r
5232           ModeHighlight();\r
5233           SetTrainingModeOff();\r
5234           DisplayInformation(_("End of game"));\r
5235         }\r
5236       } else {\r
5237         DisplayError(_("Incorrect move"), 0);\r
5238       }\r
5239       return 1;\r
5240     }\r
5241 \r
5242   /* Ok, now we know that the move is good, so we can kill\r
5243      the previous line in Analysis Mode */\r
5244   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {\r
5245     forwardMostMove = currentMove;\r
5246   }\r
5247 \r
5248   /* If we need the chess program but it's dead, restart it */\r
5249   ResurrectChessProgram();\r
5250 \r
5251   /* A user move restarts a paused game*/\r
5252   if (pausing)\r
5253     PauseEvent();\r
5254 \r
5255   thinkOutput[0] = NULLCHAR;\r
5256 \r
5257   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/\r
5258 \r
5259     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) \r
5260                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { \r
5261         // [HGM] superchess: take promotion piece out of holdings\r
5262         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));\r
5263         if(WhiteOnMove(forwardMostMove-1)) {\r
5264             if(!--boards[forwardMostMove][k][BOARD_WIDTH-2])\r
5265                 boards[forwardMostMove][k][BOARD_WIDTH-1] = EmptySquare;\r
5266         } else {\r
5267             if(!--boards[forwardMostMove][BOARD_HEIGHT-1-k][1])\r
5268                 boards[forwardMostMove][BOARD_HEIGHT-1-k][0] = EmptySquare;\r
5269         }\r
5270     }\r
5271 \r
5272   if (gameMode == BeginningOfGame) {\r
5273     if (appData.noChessProgram) {\r
5274       gameMode = EditGame;\r
5275       SetGameInfo();\r
5276     } else {\r
5277       char buf[MSG_SIZ];\r
5278       gameMode = MachinePlaysBlack;\r
5279       StartClocks();\r
5280       SetGameInfo();\r
5281       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
5282       DisplayTitle(buf);\r
5283       if (first.sendName) {\r
5284         sprintf(buf, "name %s\n", gameInfo.white);\r
5285         SendToProgram(buf, &first);\r
5286       }\r
5287       StartClocks();\r
5288     }\r
5289     ModeHighlight();\r
5290   }\r
5291 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);\r
5292   /* Relay move to ICS or chess engine */\r
5293   if (appData.icsActive) {\r
5294     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
5295         gameMode == IcsExamining) {\r
5296       SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
5297       ics_user_moved = 1;\r
5298     }\r
5299   } else {\r
5300     if (first.sendTime && (gameMode == BeginningOfGame ||\r
5301                            gameMode == MachinePlaysWhite ||\r
5302                            gameMode == MachinePlaysBlack)) {\r
5303       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);\r
5304     }\r
5305     if (gameMode != EditGame && gameMode != PlayFromGameFile) {\r
5306          // [HGM] book: if program might be playing, let it use book\r
5307         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);\r
5308         first.maybeThinking = TRUE;\r
5309     } else SendMoveToProgram(forwardMostMove-1, &first);\r
5310     if (currentMove == cmailOldMove + 1) {\r
5311       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
5312     }\r
5313   }\r
5314 \r
5315   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5316 \r
5317   switch (gameMode) {\r
5318   case EditGame:\r
5319     switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
5320                      EP_UNKNOWN, castlingRights[currentMove]) ) {\r
5321     case MT_NONE:\r
5322     case MT_CHECK:\r
5323       break;\r
5324     case MT_CHECKMATE:\r
5325       if (WhiteOnMove(currentMove)) {\r
5326         GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
5327       } else {\r
5328         GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
5329       }\r
5330       break;\r
5331     case MT_STALEMATE:\r
5332       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
5333       break;\r
5334     }\r
5335     break;\r
5336     \r
5337   case MachinePlaysBlack:\r
5338   case MachinePlaysWhite:\r
5339     /* disable certain menu options while machine is thinking */\r
5340     SetMachineThinkingEnables();\r
5341     break;\r
5342 \r
5343   default:\r
5344     break;\r
5345   }\r
5346 \r
5347   if(bookHit) { // [HGM] book: simulate book reply\r
5348         static char bookMove[MSG_SIZ]; // a bit generous?\r
5349 \r
5350         programStats.nodes = programStats.depth = programStats.time = \r
5351         programStats.score = programStats.got_only_move = 0;\r
5352         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
5353 \r
5354         strcpy(bookMove, "move ");\r
5355         strcat(bookMove, bookHit);\r
5356         HandleMachineMove(bookMove, &first);\r
5357   }\r
5358   return 1;\r
5359 }\r
5360 \r
5361 void\r
5362 UserMoveEvent(fromX, fromY, toX, toY, promoChar)\r
5363      int fromX, fromY, toX, toY;\r
5364      int promoChar;\r
5365 {\r
5366     /* [HGM] This routine was added to allow calling of its two logical\r
5367        parts from other modules in the old way. Before, UserMoveEvent()\r
5368        automatically called FinishMove() if the move was OK, and returned\r
5369        otherwise. I separated the two, in order to make it possible to\r
5370        slip a promotion popup in between. But that it always needs two\r
5371        calls, to the first part, (now called UserMoveTest() ), and to\r
5372        FinishMove if the first part succeeded. Calls that do not need\r
5373        to do anything in between, can call this routine the old way. \r
5374     */\r
5375     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);\r
5376 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);\r
5377     if(moveType != ImpossibleMove)\r
5378         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);\r
5379 }\r
5380 \r
5381 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )\r
5382 {\r
5383 //    char * hint = lastHint;\r
5384     FrontEndProgramStats stats;\r
5385 \r
5386     stats.which = cps == &first ? 0 : 1;\r
5387     stats.depth = cpstats->depth;\r
5388     stats.nodes = cpstats->nodes;\r
5389     stats.score = cpstats->score;\r
5390     stats.time = cpstats->time;\r
5391     stats.pv = cpstats->movelist;\r
5392     stats.hint = lastHint;\r
5393     stats.an_move_index = 0;\r
5394     stats.an_move_count = 0;\r
5395 \r
5396     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {\r
5397         stats.hint = cpstats->move_name;\r
5398         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;\r
5399         stats.an_move_count = cpstats->nr_moves;\r
5400     }\r
5401 \r
5402     SetProgramStats( &stats );\r
5403 }\r
5404 \r
5405 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)\r
5406 {   // [HGM] book: this routine intercepts moves to simulate book replies\r
5407     char *bookHit = NULL;\r
5408 \r
5409     //first determine if the incoming move brings opponent into his book\r
5410     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))\r
5411         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move\r
5412     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");\r
5413     if(bookHit != NULL && !cps->bookSuspend) {\r
5414         // make sure opponent is not going to reply after receiving move to book position\r
5415         SendToProgram("force\n", cps);\r
5416         cps->bookSuspend = TRUE; // flag indicating it has to be restarted\r
5417     }\r
5418     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move\r
5419     // now arrange restart after book miss\r
5420     if(bookHit) {\r
5421         // after a book hit we never send 'go', and the code after the call to this routine\r
5422         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').\r
5423         char buf[MSG_SIZ];\r
5424         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(\r
5425         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it\r
5426         SendToProgram(buf, cps);\r
5427         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'\r
5428     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine\r
5429         SendToProgram("go\n", cps);\r
5430         cps->bookSuspend = FALSE; // after a 'go' we are never suspended\r
5431     } else { // 'go' might be sent based on 'firstMove' after this routine returns\r
5432         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return\r
5433             SendToProgram("go\n", cps); \r
5434         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss\r
5435     }\r
5436     return bookHit; // notify caller of hit, so it can take action to send move to opponent\r
5437 }\r
5438 \r
5439 char *savedMessage;\r
5440 ChessProgramState *savedState;\r
5441 void DeferredBookMove(void)\r
5442 {\r
5443         if(savedState->lastPing != savedState->lastPong)\r
5444                     ScheduleDelayedEvent(DeferredBookMove, 10);\r
5445         else\r
5446         HandleMachineMove(savedMessage, savedState);\r
5447 }\r
5448 \r
5449 void\r
5450 HandleMachineMove(message, cps)\r
5451      char *message;\r
5452      ChessProgramState *cps;\r
5453 {\r
5454     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];\r
5455     char realname[MSG_SIZ];\r
5456     int fromX, fromY, toX, toY;\r
5457     ChessMove moveType;\r
5458     char promoChar;\r
5459     char *p;\r
5460     int machineWhite;\r
5461     char *bookHit;\r
5462 \r
5463 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit\r
5464     /*\r
5465      * Kludge to ignore BEL characters\r
5466      */\r
5467     while (*message == '\007') message++;\r
5468 \r
5469     /*\r
5470      * [HGM] engine debug message: ignore lines starting with '#' character\r
5471      */\r
5472     if(cps->debug && *message == '#') return;\r
5473 \r
5474     /*\r
5475      * Look for book output\r
5476      */\r
5477     if (cps == &first && bookRequested) {\r
5478         if (message[0] == '\t' || message[0] == ' ') {\r
5479             /* Part of the book output is here; append it */\r
5480             strcat(bookOutput, message);\r
5481             strcat(bookOutput, "  \n");\r
5482             return;\r
5483         } else if (bookOutput[0] != NULLCHAR) {\r
5484             /* All of book output has arrived; display it */\r
5485             char *p = bookOutput;\r
5486             while (*p != NULLCHAR) {\r
5487                 if (*p == '\t') *p = ' ';\r
5488                 p++;\r
5489             }\r
5490             DisplayInformation(bookOutput);\r
5491             bookRequested = FALSE;\r
5492             /* Fall through to parse the current output */\r
5493         }\r
5494     }\r
5495 \r
5496     /*\r
5497      * Look for machine move.\r
5498      */\r
5499     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||\r
5500         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) \r
5501     {\r
5502         /* This method is only useful on engines that support ping */\r
5503         if (cps->lastPing != cps->lastPong) {\r
5504           if (gameMode == BeginningOfGame) {\r
5505             /* Extra move from before last new; ignore */\r
5506             if (appData.debugMode) {\r
5507                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
5508             }\r
5509           } else {\r
5510             if (appData.debugMode) {\r
5511                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
5512                         cps->which, gameMode);\r
5513             }\r
5514 \r
5515             SendToProgram("undo\n", cps);\r
5516           }\r
5517           return;\r
5518         }\r
5519 \r
5520         switch (gameMode) {\r
5521           case BeginningOfGame:\r
5522             /* Extra move from before last reset; ignore */\r
5523             if (appData.debugMode) {\r
5524                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
5525             }\r
5526             return;\r
5527 \r
5528           case EndOfGame:\r
5529           case IcsIdle:\r
5530           default:\r
5531             /* Extra move after we tried to stop.  The mode test is\r
5532                not a reliable way of detecting this problem, but it's\r
5533                the best we can do on engines that don't support ping.\r
5534             */\r
5535             if (appData.debugMode) {\r
5536                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
5537                         cps->which, gameMode);\r
5538             }\r
5539             SendToProgram("undo\n", cps);\r
5540             return;\r
5541 \r
5542           case MachinePlaysWhite:\r
5543           case IcsPlayingWhite:\r
5544             machineWhite = TRUE;\r
5545             break;\r
5546 \r
5547           case MachinePlaysBlack:\r
5548           case IcsPlayingBlack:\r
5549             machineWhite = FALSE;\r
5550             break;\r
5551 \r
5552           case TwoMachinesPlay:\r
5553             machineWhite = (cps->twoMachinesColor[0] == 'w');\r
5554             break;\r
5555         }\r
5556         if (WhiteOnMove(forwardMostMove) != machineWhite) {\r
5557             if (appData.debugMode) {\r
5558                 fprintf(debugFP,\r
5559                         "Ignoring move out of turn by %s, gameMode %d"\r
5560                         ", forwardMost %d\n",\r
5561                         cps->which, gameMode, forwardMostMove);\r
5562             }\r
5563             return;\r
5564         }\r
5565 \r
5566     if (appData.debugMode) { int f = forwardMostMove;\r
5567         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,\r
5568                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
5569     }\r
5570         if(cps->alphaRank) AlphaRank(machineMove, 4);\r
5571         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,\r
5572                               &fromX, &fromY, &toX, &toY, &promoChar)) {\r
5573             /* Machine move could not be parsed; ignore it. */\r
5574             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),\r
5575                     machineMove, cps->which);\r
5576             DisplayError(buf1, 0);\r
5577             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",\r
5578                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);\r
5579             if (gameMode == TwoMachinesPlay) {\r
5580               GameEnds(machineWhite ? BlackWins : WhiteWins,\r
5581                        buf1, GE_XBOARD);\r
5582             }\r
5583             return;\r
5584         }\r
5585 \r
5586         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */\r
5587         /* So we have to redo legality test with true e.p. status here,  */\r
5588         /* to make sure an illegal e.p. capture does not slip through,   */\r
5589         /* to cause a forfeit on a justified illegal-move complaint      */\r
5590         /* of the opponent.                                              */\r
5591         if( gameMode==TwoMachinesPlay && appData.testLegality\r
5592             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */\r
5593                                                               ) {\r
5594            ChessMove moveType;\r
5595            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
5596                         epStatus[forwardMostMove], castlingRights[forwardMostMove],\r
5597                              fromY, fromX, toY, toX, promoChar);\r
5598             if (appData.debugMode) {\r
5599                 int i;\r
5600                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",\r
5601                     castlingRights[forwardMostMove][i], castlingRank[i]);\r
5602                 fprintf(debugFP, "castling rights\n");\r
5603             }\r
5604             if(moveType == IllegalMove) {\r
5605                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",\r
5606                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);\r
5607                 GameEnds(machineWhite ? BlackWins : WhiteWins,\r
5608                            buf1, GE_XBOARD);\r
5609                 return;\r
5610            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)\r
5611            /* [HGM] Kludge to handle engines that send FRC-style castling\r
5612               when they shouldn't (like TSCP-Gothic) */\r
5613            switch(moveType) {\r
5614              case WhiteASideCastleFR:\r
5615              case BlackASideCastleFR:\r
5616                toX+=2;\r
5617                currentMoveString[2]++;\r
5618                break;\r
5619              case WhiteHSideCastleFR:\r
5620              case BlackHSideCastleFR:\r
5621                toX--;\r
5622                currentMoveString[2]--;\r
5623                break;\r
5624              default: ; // nothing to do, but suppresses warning of pedantic compilers\r
5625            }\r
5626         }\r
5627         hintRequested = FALSE;\r
5628         lastHint[0] = NULLCHAR;\r
5629         bookRequested = FALSE;\r
5630         /* Program may be pondering now */\r
5631         cps->maybeThinking = TRUE;\r
5632         if (cps->sendTime == 2) cps->sendTime = 1;\r
5633         if (cps->offeredDraw) cps->offeredDraw--;\r
5634 \r
5635 #if ZIPPY\r
5636         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&\r
5637             first.initDone) {\r
5638           SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
5639           ics_user_moved = 1;\r
5640           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */\r
5641                 char buf[3*MSG_SIZ];\r
5642 \r
5643                 sprintf(buf, "kibitz %d/%+.2f (%.2f sec, %.0f nodes, %1.0f knps) PV = %s\n",\r
5644                         programStats.depth,\r
5645                         programStats.score / 100.,\r
5646                         programStats.time / 100.,\r
5647                         u64ToDouble(programStats.nodes),\r
5648                         u64ToDouble(programStats.nodes) / (10*abs(programStats.time) + 1.),\r
5649                         programStats.movelist);\r
5650                 SendToICS(buf);\r
5651           }\r
5652         }\r
5653 #endif\r
5654         /* currentMoveString is set as a side-effect of ParseOneMove */\r
5655         strcpy(machineMove, currentMoveString);\r
5656         strcat(machineMove, "\n");\r
5657         strcpy(moveList[forwardMostMove], machineMove);\r
5658 \r
5659         /* [AS] Save move info and clear stats for next move */\r
5660         pvInfoList[ forwardMostMove ].score = programStats.score;\r
5661         pvInfoList[ forwardMostMove ].depth = programStats.depth;\r
5662         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats\r
5663         ClearProgramStats();\r
5664         thinkOutput[0] = NULLCHAR;\r
5665         hiddenThinkOutputState = 0;\r
5666 \r
5667         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/\r
5668 \r
5669         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */\r
5670         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {\r
5671             int count = 0;\r
5672 \r
5673             while( count < adjudicateLossPlies ) {\r
5674                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;\r
5675 \r
5676                 if( count & 1 ) {\r
5677                     score = -score; /* Flip score for winning side */\r
5678                 }\r
5679 \r
5680                 if( score > adjudicateLossThreshold ) {\r
5681                     break;\r
5682                 }\r
5683 \r
5684                 count++;\r
5685             }\r
5686 \r
5687             if( count >= adjudicateLossPlies ) {\r
5688                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5689 \r
5690                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5691                     "Xboard adjudication", \r
5692                     GE_XBOARD );\r
5693 \r
5694                 return;\r
5695             }\r
5696         }\r
5697 \r
5698         if( gameMode == TwoMachinesPlay ) {\r
5699           // [HGM] some adjudications useful with buggy engines\r
5700             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;\r
5701           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {\r
5702 \r
5703             if( appData.testLegality )\r
5704             {   /* [HGM] Some more adjudications for obstinate engines */\r
5705                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,\r
5706                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,\r
5707                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;\r
5708                 static int moveCount = 6;\r
5709 \r
5710                 /* Count what is on board. */\r
5711                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
5712                 {   ChessSquare p = boards[forwardMostMove][i][j];\r
5713                     int m=i;\r
5714 \r
5715                     switch((int) p)\r
5716                     {   /* count B,N,R and other of each side */\r
5717                         case WhiteKing:\r
5718                         case BlackKing:\r
5719                              NrK++; break; // [HGM] atomic: count Kings\r
5720                         case WhiteKnight:\r
5721                              NrWN++; break;\r
5722                         case WhiteBishop:\r
5723                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj\r
5724                              bishopsColor |= 1 << ((i^j)&1);\r
5725                              NrWB++; break;\r
5726                         case BlackKnight:\r
5727                              NrBN++; break;\r
5728                         case BlackBishop:\r
5729                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj\r
5730                              bishopsColor |= 1 << ((i^j)&1);\r
5731                              NrBB++; break;\r
5732                         case WhiteRook:\r
5733                              NrWR++; break;\r
5734                         case BlackRook:\r
5735                              NrBR++; break;\r
5736                         case WhiteQueen:\r
5737                              NrWQ++; break;\r
5738                         case BlackQueen:\r
5739                              NrBQ++; break;\r
5740                         case EmptySquare: \r
5741                              break;\r
5742                         case BlackPawn:\r
5743                              m = 7-i;\r
5744                         case WhitePawn:\r
5745                              PawnAdvance += m; NrPawns++;\r
5746                     }\r
5747                     NrPieces += (p != EmptySquare);\r
5748                     NrW += ((int)p < (int)BlackPawn);\r
5749                     if(gameInfo.variant == VariantXiangqi && \r
5750                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {\r
5751                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces\r
5752                         NrW -= ((int)p < (int)BlackPawn);\r
5753                     }\r
5754                 }\r
5755 \r
5756                 /* Some material-based adjudications that have to be made before stalemate test */\r
5757                 if(gameInfo.variant == VariantAtomic && NrK < 2) {\r
5758                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal\r
5759                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated\r
5760                      if(appData.checkMates) {\r
5761                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move\r
5762                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5763                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, \r
5764                                                         "Xboard adjudication: King destroyed", GE_XBOARD );\r
5765                          return;\r
5766                      }\r
5767                 }\r
5768 \r
5769                 /* Bare King in Shatranj (loses) or Losers (wins) */\r
5770                 if( NrW == 1 || NrPieces - NrW == 1) {\r
5771                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)\r
5772                      epStatus[forwardMostMove] = EP_STALEMATE; // kludge to make position claimable as win\r
5773                      if(appData.checkMates) {\r
5774                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5775                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5776                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5777                                                         "Xboard adjudication: Bare king", GE_XBOARD );\r
5778                          return;\r
5779                      }\r
5780                   } else\r
5781                   if( gameInfo.variant == VariantShatranj && --bare < 0)\r
5782                   {    /* bare King */\r
5783                         epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as win for stm\r
5784                         if(appData.checkMates) {\r
5785                             /* but only adjudicate if adjudication enabled */\r
5786                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move\r
5787                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5788                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, \r
5789                                                         "Xboard adjudication: Bare king", GE_XBOARD );\r
5790                             return;\r
5791                         }\r
5792                   }\r
5793                 } else bare = 1;\r
5794 \r
5795 \r
5796             // don't wait for engine to announce game end if we can judge ourselves\r
5797             switch (MateTest(boards[forwardMostMove],\r
5798                                  PosFlags(forwardMostMove), epFile,\r
5799                                        castlingRights[forwardMostMove]) ) {\r
5800               case MT_NONE:\r
5801               case MT_CHECK:\r
5802               default:\r
5803                 break;\r
5804               case MT_STALEMATE:\r
5805                 if(epStatus[forwardMostMove] != EP_CHECKMATE) // [HGM] spare win through baring or K-capt\r
5806                     epStatus[forwardMostMove] = EP_STALEMATE;\r
5807                 if(appData.checkMates) {\r
5808                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5809                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5810                     if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantSuicide\r
5811                                                          || gameInfo.variant == VariantGiveaway) // [HGM] losers:\r
5812                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, // stalemated side wins!\r
5813                                 "Xboard adjudication: Stalemate", GE_XBOARD );\r
5814                     else\r
5815                         GameEnds( GameIsDrawn, "Xboard adjudication: Stalemate", GE_XBOARD );\r
5816                     return;\r
5817                 }\r
5818                 break;\r
5819               case MT_CHECKMATE:\r
5820                 epStatus[forwardMostMove] = EP_CHECKMATE;\r
5821                 if(appData.checkMates) {\r
5822                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5823                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5824                     GameEnds( WhiteOnMove(forwardMostMove) != (gameInfo.variant == VariantLosers) // [HGM] losers:\r
5825                              ? BlackWins : WhiteWins,            // reverse the result ( A!=1 is !A for a boolean)\r
5826                              "Xboard adjudication: Checkmate", GE_XBOARD );\r
5827                     return;\r
5828                 }\r
5829                 break;\r
5830             }\r
5831 \r
5832                 /* Next absolutely insufficient mating material. */\r
5833                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && \r
5834                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible\r
5835                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||\r
5836                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color\r
5837                 {    /* KBK, KNK, KK of KBKB with like Bishops */\r
5838 \r
5839                      /* always flag draws, for judging claims */\r
5840                      epStatus[forwardMostMove] = EP_INSUF_DRAW;\r
5841 \r
5842                      if(appData.materialDraws) {\r
5843                          /* but only adjudicate them if adjudication enabled */\r
5844                          SendToProgram("force\n", cps->other); // suppress reply\r
5845                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */\r
5846                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5847                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );\r
5848                          return;\r
5849                      }\r
5850                 }\r
5851 \r
5852                 /* Then some trivial draws (only adjudicate, cannot be claimed) */\r
5853                 if(NrPieces == 4 && \r
5854                    (   NrWR == 1 && NrBR == 1 /* KRKR */\r
5855                    || NrWQ==1 && NrBQ==1     /* KQKQ */\r
5856                    || NrWN==2 || NrBN==2     /* KNNK */\r
5857                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */\r
5858                   ) ) {\r
5859                      if(--moveCount < 0 && appData.trivialDraws)\r
5860                      {    /* if the first 3 moves do not show a tactical win, declare draw */\r
5861                           SendToProgram("force\n", cps->other); // suppress reply\r
5862                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5863                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5864                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );\r
5865                           return;\r
5866                      }\r
5867                 } else moveCount = 6;\r
5868             }\r
5869           }\r
5870 #if 1\r
5871     if (appData.debugMode) { int i;\r
5872       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",\r
5873               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],\r
5874               appData.drawRepeats);\r
5875       for( i=forwardMostMove; i>=backwardMostMove; i-- )\r
5876            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);\r
5877 \r
5878     }\r
5879 #endif\r
5880                 /* Check for rep-draws */\r
5881                 count = 0;\r
5882                 for(k = forwardMostMove-2;\r
5883                     k>=backwardMostMove && k>=forwardMostMove-100 &&\r
5884                         epStatus[k] < EP_UNKNOWN &&\r
5885                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;\r
5886                     k-=2)\r
5887                 {   int rights=0;\r
5888 #if 0\r
5889     if (appData.debugMode) {\r
5890       fprintf(debugFP, " loop\n");\r
5891     }\r
5892 #endif\r
5893                     if(CompareBoards(boards[k], boards[forwardMostMove])) {\r
5894 #if 0\r
5895     if (appData.debugMode) {\r
5896       fprintf(debugFP, "match\n");\r
5897     }\r
5898 #endif\r
5899                         /* compare castling rights */\r
5900                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&\r
5901                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )\r
5902                                 rights++; /* King lost rights, while rook still had them */\r
5903                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */\r
5904                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||\r
5905                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )\r
5906                                    rights++; /* but at least one rook lost them */\r
5907                         }\r
5908                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&\r
5909                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )\r
5910                                 rights++; \r
5911                         if( castlingRights[forwardMostMove][5] >= 0 ) {\r
5912                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||\r
5913                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )\r
5914                                    rights++;\r
5915                         }\r
5916 #if 0\r
5917     if (appData.debugMode) {\r
5918       for(i=0; i<nrCastlingRights; i++)\r
5919       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);\r
5920     }\r
5921 \r
5922     if (appData.debugMode) {\r
5923       fprintf(debugFP, " %d %d\n", rights, k);\r
5924     }\r
5925 #endif\r
5926                         if( rights == 0 && ++count > appData.drawRepeats-2\r
5927                             && appData.drawRepeats > 1) {\r
5928                              /* adjudicate after user-specified nr of repeats */\r
5929                              SendToProgram("force\n", cps->other); // suppress reply\r
5930                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5931                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5932                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { \r
5933                                 // [HGM] xiangqi: check for forbidden perpetuals\r
5934                                 int m, ourPerpetual = 1, hisPerpetual = 1;\r
5935                                 for(m=forwardMostMove; m>k; m-=2) {\r
5936                                     if(MateTest(boards[m], PosFlags(m), \r
5937                                                         EP_NONE, castlingRights[m]) != MT_CHECK)\r
5938                                         ourPerpetual = 0; // the current mover did not always check\r
5939                                     if(MateTest(boards[m-1], PosFlags(m-1), \r
5940                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)\r
5941                                         hisPerpetual = 0; // the opponent did not always check\r
5942                                 }\r
5943                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",\r
5944                                                                         ourPerpetual, hisPerpetual);\r
5945                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit\r
5946                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5947                                            "Xboard adjudication: perpetual checking", GE_XBOARD );\r
5948                                     return;\r
5949                                 }\r
5950                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet\r
5951                                     break; // (or we would have caught him before). Abort repetition-checking loop.\r
5952                                 // Now check for perpetual chases\r
5953                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase\r
5954                                     hisPerpetual = PerpetualChase(k, forwardMostMove);\r
5955                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);\r
5956                                     if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit\r
5957                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5958                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );\r
5959                                         return;\r
5960                                     }\r
5961                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet\r
5962                                         break; // Abort repetition-checking loop.\r
5963                                 }\r
5964                                 // if neither of us is checking or chasing all the time, or both are, it is draw\r
5965                              }\r
5966                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );\r
5967                              return;\r
5968                         }\r
5969                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */\r
5970                              epStatus[forwardMostMove] = EP_REP_DRAW;\r
5971                     }\r
5972                 }\r
5973 \r
5974                 /* Now we test for 50-move draws. Determine ply count */\r
5975                 count = forwardMostMove;\r
5976                 /* look for last irreversble move */\r
5977                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )\r
5978                     count--;\r
5979                 /* if we hit starting position, add initial plies */\r
5980                 if( count == backwardMostMove )\r
5981                     count -= initialRulePlies;\r
5982                 count = forwardMostMove - count; \r
5983                 if( count >= 100)\r
5984                          epStatus[forwardMostMove] = EP_RULE_DRAW;\r
5985                          /* this is used to judge if draw claims are legal */\r
5986                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {\r
5987                          SendToProgram("force\n", cps->other); // suppress reply\r
5988                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
5989                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5990                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );\r
5991                          return;\r
5992                 }\r
5993 \r
5994                 /* if draw offer is pending, treat it as a draw claim\r
5995                  * when draw condition present, to allow engines a way to\r
5996                  * claim draws before making their move to avoid a race\r
5997                  * condition occurring after their move\r
5998                  */\r
5999                 if( cps->other->offeredDraw || cps->offeredDraw ) {\r
6000                          char *p = NULL;\r
6001                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)\r
6002                              p = "Draw claim: 50-move rule";\r
6003                          if(epStatus[forwardMostMove] == EP_REP_DRAW)\r
6004                              p = "Draw claim: 3-fold repetition";\r
6005                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)\r
6006                              p = "Draw claim: insufficient mating material";\r
6007                          if( p != NULL ) {\r
6008                              SendToProgram("force\n", cps->other); // suppress reply\r
6009                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6010                              GameEnds( GameIsDrawn, p, GE_XBOARD );\r
6011                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6012                              return;\r
6013                          }\r
6014                 }\r
6015 \r
6016 \r
6017                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {\r
6018                     SendToProgram("force\n", cps->other); // suppress reply\r
6019                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
6020                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6021 \r
6022                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );\r
6023 \r
6024                     return;\r
6025                 }\r
6026         }\r
6027 \r
6028         bookHit = NULL;\r
6029         if (gameMode == TwoMachinesPlay) {\r
6030             /* [HGM] relaying draw offers moved to after reception of move */\r
6031             /* and interpreting offer as claim if it brings draw condition */\r
6032             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {\r
6033                 SendToProgram("draw\n", cps->other);\r
6034             }\r
6035             if (cps->other->sendTime) {\r
6036                 SendTimeRemaining(cps->other,\r
6037                                   cps->other->twoMachinesColor[0] == 'w');\r
6038             }\r
6039             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);\r
6040             if (firstMove && !bookHit) {\r
6041                 firstMove = FALSE;\r
6042                 if (cps->other->useColors) {\r
6043                   SendToProgram(cps->other->twoMachinesColor, cps->other);\r
6044                 }\r
6045                 SendToProgram("go\n", cps->other);\r
6046             }\r
6047             cps->other->maybeThinking = TRUE;\r
6048         }\r
6049 \r
6050         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
6051         \r
6052         if (!pausing && appData.ringBellAfterMoves) {\r
6053             RingBell();\r
6054         }\r
6055 \r
6056         /* \r
6057          * Reenable menu items that were disabled while\r
6058          * machine was thinking\r
6059          */\r
6060         if (gameMode != TwoMachinesPlay)\r
6061             SetUserThinkingEnables();\r
6062 \r
6063         // [HGM] book: after book hit opponent has received move and is now in force mode\r
6064         // force the book reply into it, and then fake that it outputted this move by jumping\r
6065         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move\r
6066         if(bookHit) {\r
6067                 static char bookMove[MSG_SIZ]; // a bit generous?\r
6068 \r
6069                 strcpy(bookMove, "move ");\r
6070                 strcat(bookMove, bookHit);\r
6071                 message = bookMove;\r
6072                 cps = cps->other;\r
6073                 programStats.nodes = programStats.depth = programStats.time = \r
6074                 programStats.score = programStats.got_only_move = 0;\r
6075                 sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
6076 \r
6077                 if(cps->lastPing != cps->lastPong) {\r
6078                     savedMessage = message; // args for deferred call\r
6079                     savedState = cps;\r
6080                     ScheduleDelayedEvent(DeferredBookMove, 10);\r
6081                     return;\r
6082                 }\r
6083                 goto FakeBookMove;\r
6084         }\r
6085 \r
6086         return;\r
6087     }\r
6088 \r
6089     /* Set special modes for chess engines.  Later something general\r
6090      *  could be added here; for now there is just one kludge feature,\r
6091      *  needed because Crafty 15.10 and earlier don't ignore SIGINT\r
6092      *  when "xboard" is given as an interactive command.\r
6093      */\r
6094     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {\r
6095         cps->useSigint = FALSE;\r
6096         cps->useSigterm = FALSE;\r
6097     }\r
6098 \r
6099     /* [HGM] Allow engine to set up a position. Don't ask me why one would\r
6100      * want this, I was asked to put it in, and obliged.\r
6101      */\r
6102     if (!strncmp(message, "setboard ", 9)) {\r
6103         Board initial_position; int i;\r
6104 \r
6105         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);\r
6106 \r
6107         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {\r
6108             DisplayError(_("Bad FEN received from engine"), 0);\r
6109             return ;\r
6110         } else {\r
6111            Reset(FALSE, FALSE);\r
6112            CopyBoard(boards[0], initial_position);\r
6113            initialRulePlies = FENrulePlies;\r
6114            epStatus[0] = FENepStatus;\r
6115            for( i=0; i<nrCastlingRights; i++ )\r
6116                 castlingRights[0][i] = FENcastlingRights[i];\r
6117            if(blackPlaysFirst) gameMode = MachinePlaysWhite;\r
6118            else gameMode = MachinePlaysBlack;                 \r
6119            DrawPosition(FALSE, boards[currentMove]);\r
6120         }\r
6121         return;\r
6122     }\r
6123 \r
6124     /*\r
6125      * Look for communication commands\r
6126      */\r
6127     if (!strncmp(message, "telluser ", 9)) {\r
6128         DisplayNote(message + 9);\r
6129         return;\r
6130     }\r
6131     if (!strncmp(message, "tellusererror ", 14)) {\r
6132         DisplayError(message + 14, 0);\r
6133         return;\r
6134     }\r
6135     if (!strncmp(message, "tellopponent ", 13)) {\r
6136       if (appData.icsActive) {\r
6137         if (loggedOn) {\r
6138           sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);\r
6139           SendToICS(buf1);\r
6140         }\r
6141       } else {\r
6142         DisplayNote(message + 13);\r
6143       }\r
6144       return;\r
6145     }\r
6146     if (!strncmp(message, "tellothers ", 11)) {\r
6147       if (appData.icsActive) {\r
6148         if (loggedOn) {\r
6149           sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);\r
6150           SendToICS(buf1);\r
6151         }\r
6152       }\r
6153       return;\r
6154     }\r
6155     if (!strncmp(message, "tellall ", 8)) {\r
6156       if (appData.icsActive) {\r
6157         if (loggedOn) {\r
6158           sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);\r
6159           SendToICS(buf1);\r
6160         }\r
6161       } else {\r
6162         DisplayNote(message + 8);\r
6163       }\r
6164       return;\r
6165     }\r
6166     if (strncmp(message, "warning", 7) == 0) {\r
6167         /* Undocumented feature, use tellusererror in new code */\r
6168         DisplayError(message, 0);\r
6169         return;\r
6170     }\r
6171     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {\r
6172         strcpy(realname, cps->tidy);\r
6173         strcat(realname, " query");\r
6174         AskQuestion(realname, buf2, buf1, cps->pr);\r
6175         return;\r
6176     }\r
6177     /* Commands from the engine directly to ICS.  We don't allow these to be \r
6178      *  sent until we are logged on. Crafty kibitzes have been known to \r
6179      *  interfere with the login process.\r
6180      */\r
6181     if (loggedOn) {\r
6182         if (!strncmp(message, "tellics ", 8)) {\r
6183             SendToICS(message + 8);\r
6184             SendToICS("\n");\r
6185             return;\r
6186         }\r
6187         if (!strncmp(message, "tellicsnoalias ", 15)) {\r
6188             SendToICS(ics_prefix);\r
6189             SendToICS(message + 15);\r
6190             SendToICS("\n");\r
6191             return;\r
6192         }\r
6193         /* The following are for backward compatibility only */\r
6194         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||\r
6195             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {\r
6196             SendToICS(ics_prefix);\r
6197             SendToICS(message);\r
6198             SendToICS("\n");\r
6199             return;\r
6200         }\r
6201     }\r
6202     if (strncmp(message, "feature ", 8) == 0) {\r
6203       ParseFeatures(message+8, cps);\r
6204     }\r
6205     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {\r
6206         return;\r
6207     }\r
6208     /*\r
6209      * If the move is illegal, cancel it and redraw the board.\r
6210      * Also deal with other error cases.  Matching is rather loose\r
6211      * here to accommodate engines written before the spec.\r
6212      */\r
6213     if (strncmp(message + 1, "llegal move", 11) == 0 ||\r
6214         strncmp(message, "Error", 5) == 0) {\r
6215         if (StrStr(message, "name") || \r
6216             StrStr(message, "rating") || StrStr(message, "?") ||\r
6217             StrStr(message, "result") || StrStr(message, "board") ||\r
6218             StrStr(message, "bk") || StrStr(message, "computer") ||\r
6219             StrStr(message, "variant") || StrStr(message, "hint") ||\r
6220             StrStr(message, "random") || StrStr(message, "depth") ||\r
6221             StrStr(message, "accepted")) {\r
6222             return;\r
6223         }\r
6224         if (StrStr(message, "protover")) {\r
6225           /* Program is responding to input, so it's apparently done\r
6226              initializing, and this error message indicates it is\r
6227              protocol version 1.  So we don't need to wait any longer\r
6228              for it to initialize and send feature commands. */\r
6229           FeatureDone(cps, 1);\r
6230           cps->protocolVersion = 1;\r
6231           return;\r
6232         }\r
6233         cps->maybeThinking = FALSE;\r
6234 \r
6235         if (StrStr(message, "draw")) {\r
6236             /* Program doesn't have "draw" command */\r
6237             cps->sendDrawOffers = 0;\r
6238             return;\r
6239         }\r
6240         if (cps->sendTime != 1 &&\r
6241             (StrStr(message, "time") || StrStr(message, "otim"))) {\r
6242           /* Program apparently doesn't have "time" or "otim" command */\r
6243           cps->sendTime = 0;\r
6244           return;\r
6245         }\r
6246         if (StrStr(message, "analyze")) {\r
6247             cps->analysisSupport = FALSE;\r
6248             cps->analyzing = FALSE;\r
6249             Reset(FALSE, TRUE);\r
6250             sprintf(buf2, _("%s does not support analysis"), cps->tidy);\r
6251             DisplayError(buf2, 0);\r
6252             return;\r
6253         }\r
6254         if (StrStr(message, "(no matching move)st")) {\r
6255           /* Special kludge for GNU Chess 4 only */\r
6256           cps->stKludge = TRUE;\r
6257           SendTimeControl(cps, movesPerSession, timeControl,\r
6258                           timeIncrement, appData.searchDepth,\r
6259                           searchTime);\r
6260           return;\r
6261         }\r
6262         if (StrStr(message, "(no matching move)sd")) {\r
6263           /* Special kludge for GNU Chess 4 only */\r
6264           cps->sdKludge = TRUE;\r
6265           SendTimeControl(cps, movesPerSession, timeControl,\r
6266                           timeIncrement, appData.searchDepth,\r
6267                           searchTime);\r
6268           return;\r
6269         }\r
6270         if (!StrStr(message, "llegal")) {\r
6271             return;\r
6272         }\r
6273         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
6274             gameMode == IcsIdle) return;\r
6275         if (forwardMostMove <= backwardMostMove) return;\r
6276 #if 0\r
6277         /* Following removed: it caused a bug where a real illegal move\r
6278            message in analyze mored would be ignored. */\r
6279         if (cps == &first && programStats.ok_to_send == 0) {\r
6280             /* Bogus message from Crafty responding to "."  This filtering\r
6281                can miss some of the bad messages, but fortunately the bug \r
6282                is fixed in current Crafty versions, so it doesn't matter. */\r
6283             return;\r
6284         }\r
6285 #endif\r
6286         if (pausing) PauseEvent();\r
6287         if (gameMode == PlayFromGameFile) {\r
6288             /* Stop reading this game file */\r
6289             gameMode = EditGame;\r
6290             ModeHighlight();\r
6291         }\r
6292         currentMove = --forwardMostMove;\r
6293         DisplayMove(currentMove-1); /* before DisplayMoveError */\r
6294         SwitchClocks();\r
6295         DisplayBothClocks();\r
6296         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),\r
6297                 parseList[currentMove], cps->which);\r
6298         DisplayMoveError(buf1);\r
6299         DrawPosition(FALSE, boards[currentMove]);\r
6300 \r
6301         /* [HGM] illegal-move claim should forfeit game when Xboard */\r
6302         /* only passes fully legal moves                            */\r
6303         if( appData.testLegality && gameMode == TwoMachinesPlay ) {\r
6304             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,\r
6305                                 "False illegal-move claim", GE_XBOARD );\r
6306         }\r
6307         return;\r
6308     }\r
6309     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {\r
6310         /* Program has a broken "time" command that\r
6311            outputs a string not ending in newline.\r
6312            Don't use it. */\r
6313         cps->sendTime = 0;\r
6314     }\r
6315     \r
6316     /*\r
6317      * If chess program startup fails, exit with an error message.\r
6318      * Attempts to recover here are futile.\r
6319      */\r
6320     if ((StrStr(message, "unknown host") != NULL)\r
6321         || (StrStr(message, "No remote directory") != NULL)\r
6322         || (StrStr(message, "not found") != NULL)\r
6323         || (StrStr(message, "No such file") != NULL)\r
6324         || (StrStr(message, "can't alloc") != NULL)\r
6325         || (StrStr(message, "Permission denied") != NULL)) {\r
6326 \r
6327         cps->maybeThinking = FALSE;\r
6328         sprintf(buf1, _("Failed to start %s chess program %s on %s: %s\n"),\r
6329                 cps->which, cps->program, cps->host, message);\r
6330         RemoveInputSource(cps->isr);\r
6331         DisplayFatalError(buf1, 0, 1);\r
6332         return;\r
6333     }\r
6334     \r
6335     /* \r
6336      * Look for hint output\r
6337      */\r
6338     if (sscanf(message, "Hint: %s", buf1) == 1) {\r
6339         if (cps == &first && hintRequested) {\r
6340             hintRequested = FALSE;\r
6341             if (ParseOneMove(buf1, forwardMostMove, &moveType,\r
6342                                  &fromX, &fromY, &toX, &toY, &promoChar)) {\r
6343                 (void) CoordsToAlgebraic(boards[forwardMostMove],\r
6344                                     PosFlags(forwardMostMove), EP_UNKNOWN,\r
6345                                     fromY, fromX, toY, toX, promoChar, buf1);\r
6346                 sprintf(buf2, _("Hint: %s"), buf1);\r
6347                 DisplayInformation(buf2);\r
6348             } else {\r
6349                 /* Hint move could not be parsed!? */\r
6350                 sprintf(buf2,\r
6351                         _("Illegal hint move \"%s\"\nfrom %s chess program"),\r
6352                         buf1, cps->which);\r
6353                 DisplayError(buf2, 0);\r
6354             }\r
6355         } else {\r
6356             strcpy(lastHint, buf1);\r
6357         }\r
6358         return;\r
6359     }\r
6360 \r
6361     /*\r
6362      * Ignore other messages if game is not in progress\r
6363      */\r
6364     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
6365         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;\r
6366 \r
6367     /*\r
6368      * look for win, lose, draw, or draw offer\r
6369      */\r
6370     if (strncmp(message, "1-0", 3) == 0) {\r
6371         char *p, *q, *r = "";\r
6372         p = strchr(message, '{');\r
6373         if (p) {\r
6374             q = strchr(p, '}');\r
6375             if (q) {\r
6376                 *q = NULLCHAR;\r
6377                 r = p + 1;\r
6378             }\r
6379         }\r
6380         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */\r
6381         return;\r
6382     } else if (strncmp(message, "0-1", 3) == 0) {\r
6383         char *p, *q, *r = "";\r
6384         p = strchr(message, '{');\r
6385         if (p) {\r
6386             q = strchr(p, '}');\r
6387             if (q) {\r
6388                 *q = NULLCHAR;\r
6389                 r = p + 1;\r
6390             }\r
6391         }\r
6392         /* Kludge for Arasan 4.1 bug */\r
6393         if (strcmp(r, "Black resigns") == 0) {\r
6394             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));\r
6395             return;\r
6396         }\r
6397         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));\r
6398         return;\r
6399     } else if (strncmp(message, "1/2", 3) == 0) {\r
6400         char *p, *q, *r = "";\r
6401         p = strchr(message, '{');\r
6402         if (p) {\r
6403             q = strchr(p, '}');\r
6404             if (q) {\r
6405                 *q = NULLCHAR;\r
6406                 r = p + 1;\r
6407             }\r
6408         }\r
6409             \r
6410         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));\r
6411         return;\r
6412 \r
6413     } else if (strncmp(message, "White resign", 12) == 0) {\r
6414         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
6415         return;\r
6416     } else if (strncmp(message, "Black resign", 12) == 0) {\r
6417         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
6418         return;\r
6419     } else if (strncmp(message, "White matches", 13) == 0 ||\r
6420                strncmp(message, "Black matches", 13) == 0   ) {\r
6421         /* [HGM] ignore GNUShogi noises */\r
6422         return;\r
6423     } else if (strncmp(message, "White", 5) == 0 &&\r
6424                message[5] != '(' &&\r
6425                StrStr(message, "Black") == NULL) {\r
6426         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6427         return;\r
6428     } else if (strncmp(message, "Black", 5) == 0 &&\r
6429                message[5] != '(') {\r
6430         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6431         return;\r
6432     } else if (strcmp(message, "resign") == 0 ||\r
6433                strcmp(message, "computer resigns") == 0) {\r
6434         switch (gameMode) {\r
6435           case MachinePlaysBlack:\r
6436           case IcsPlayingBlack:\r
6437             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);\r
6438             break;\r
6439           case MachinePlaysWhite:\r
6440           case IcsPlayingWhite:\r
6441             GameEnds(BlackWins, "White resigns", GE_ENGINE);\r
6442             break;\r
6443           case TwoMachinesPlay:\r
6444             if (cps->twoMachinesColor[0] == 'w')\r
6445               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
6446             else\r
6447               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
6448             break;\r
6449           default:\r
6450             /* can't happen */\r
6451             break;\r
6452         }\r
6453         return;\r
6454     } else if (strncmp(message, "opponent mates", 14) == 0) {\r
6455         switch (gameMode) {\r
6456           case MachinePlaysBlack:\r
6457           case IcsPlayingBlack:\r
6458             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
6459             break;\r
6460           case MachinePlaysWhite:\r
6461           case IcsPlayingWhite:\r
6462             GameEnds(BlackWins, "Black mates", GE_ENGINE);\r
6463             break;\r
6464           case TwoMachinesPlay:\r
6465             if (cps->twoMachinesColor[0] == 'w')\r
6466               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6467             else\r
6468               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6469             break;\r
6470           default:\r
6471             /* can't happen */\r
6472             break;\r
6473         }\r
6474         return;\r
6475     } else if (strncmp(message, "computer mates", 14) == 0) {\r
6476         switch (gameMode) {\r
6477           case MachinePlaysBlack:\r
6478           case IcsPlayingBlack:\r
6479             GameEnds(BlackWins, "Black mates", GE_ENGINE1);\r
6480             break;\r
6481           case MachinePlaysWhite:\r
6482           case IcsPlayingWhite:\r
6483             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
6484             break;\r
6485           case TwoMachinesPlay:\r
6486             if (cps->twoMachinesColor[0] == 'w')\r
6487               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6488             else\r
6489               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6490             break;\r
6491           default:\r
6492             /* can't happen */\r
6493             break;\r
6494         }\r
6495         return;\r
6496     } else if (strncmp(message, "checkmate", 9) == 0) {\r
6497         if (WhiteOnMove(forwardMostMove)) {\r
6498             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6499         } else {\r
6500             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6501         }\r
6502         return;\r
6503     } else if (strstr(message, "Draw") != NULL ||\r
6504                strstr(message, "game is a draw") != NULL) {\r
6505         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));\r
6506         return;\r
6507     } else if (strstr(message, "offer") != NULL &&\r
6508                strstr(message, "draw") != NULL) {\r
6509 #if ZIPPY\r
6510         if (appData.zippyPlay && first.initDone) {\r
6511             /* Relay offer to ICS */\r
6512             SendToICS(ics_prefix);\r
6513             SendToICS("draw\n");\r
6514         }\r
6515 #endif\r
6516         cps->offeredDraw = 2; /* valid until this engine moves twice */\r
6517         if (gameMode == TwoMachinesPlay) {\r
6518             if (cps->other->offeredDraw) {\r
6519                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
6520             /* [HGM] in two-machine mode we delay relaying draw offer      */\r
6521             /* until after we also have move, to see if it is really claim */\r
6522             }\r
6523 #if 0\r
6524               else {\r
6525                 if (cps->other->sendDrawOffers) {\r
6526                     SendToProgram("draw\n", cps->other);\r
6527                 }\r
6528             }\r
6529 #endif\r
6530         } else if (gameMode == MachinePlaysWhite ||\r
6531                    gameMode == MachinePlaysBlack) {\r
6532           if (userOfferedDraw) {\r
6533             DisplayInformation(_("Machine accepts your draw offer"));\r
6534             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
6535           } else {\r
6536             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));\r
6537           }\r
6538         }\r
6539     }\r
6540 \r
6541     \r
6542     /*\r
6543      * Look for thinking output\r
6544      */\r
6545     if ( appData.showThinking // [HGM] thinking: test all options that cause this output\r
6546           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()\r
6547                                 ) {\r
6548         int plylev, mvleft, mvtot, curscore, time;\r
6549         char mvname[MOVE_LEN];\r
6550         u64 nodes; // [DM]\r
6551         char plyext;\r
6552         int ignore = FALSE;\r
6553         int prefixHint = FALSE;\r
6554         mvname[0] = NULLCHAR;\r
6555 \r
6556         switch (gameMode) {\r
6557           case MachinePlaysBlack:\r
6558           case IcsPlayingBlack:\r
6559             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
6560             break;\r
6561           case MachinePlaysWhite:\r
6562           case IcsPlayingWhite:\r
6563             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
6564             break;\r
6565           case AnalyzeMode:\r
6566           case AnalyzeFile:\r
6567             break;\r
6568           case IcsObserving: /* [DM] icsEngineAnalyze */\r
6569             if (!appData.icsEngineAnalyze) ignore = TRUE;\r
6570             break;\r
6571           case TwoMachinesPlay:\r
6572             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {\r
6573                 ignore = TRUE;\r
6574             }\r
6575             break;\r
6576           default:\r
6577             ignore = TRUE;\r
6578             break;\r
6579         }\r
6580 \r
6581         if (!ignore) {\r
6582             buf1[0] = NULLCHAR;\r
6583             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",\r
6584                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {\r
6585 \r
6586                 if (plyext != ' ' && plyext != '\t') {\r
6587                     time *= 100;\r
6588                 }\r
6589 \r
6590                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
6591                 if( cps->scoreIsAbsolute && \r
6592                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )\r
6593                 {\r
6594                     curscore = -curscore;\r
6595                 }\r
6596 \r
6597 \r
6598                 programStats.depth = plylev;\r
6599                 programStats.nodes = nodes;\r
6600                 programStats.time = time;\r
6601                 programStats.score = curscore;\r
6602                 programStats.got_only_move = 0;\r
6603 \r
6604                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */\r
6605                         int ticklen;\r
6606 \r
6607                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time\r
6608                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time\r
6609                         if(WhiteOnMove(forwardMostMove)) \r
6610                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;\r
6611                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;\r
6612                 }\r
6613 \r
6614                 /* Buffer overflow protection */\r
6615                 if (buf1[0] != NULLCHAR) {\r
6616                     if (strlen(buf1) >= sizeof(programStats.movelist)\r
6617                         && appData.debugMode) {\r
6618                         fprintf(debugFP,\r
6619                                 "PV is too long; using the first %d bytes.\n",\r
6620                                 sizeof(programStats.movelist) - 1);\r
6621                     }\r
6622 \r
6623                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );\r
6624                 } else {\r
6625                     sprintf(programStats.movelist, " no PV\n");\r
6626                 }\r
6627 \r
6628                 if (programStats.seen_stat) {\r
6629                     programStats.ok_to_send = 1;\r
6630                 }\r
6631 \r
6632                 if (strchr(programStats.movelist, '(') != NULL) {\r
6633                     programStats.line_is_book = 1;\r
6634                     programStats.nr_moves = 0;\r
6635                     programStats.moves_left = 0;\r
6636                 } else {\r
6637                     programStats.line_is_book = 0;\r
6638                 }\r
6639 \r
6640                 SendProgramStatsToFrontend( cps, &programStats );\r
6641 \r
6642                 /* \r
6643                     [AS] Protect the thinkOutput buffer from overflow... this\r
6644                     is only useful if buf1 hasn't overflowed first!\r
6645                 */\r
6646                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",\r
6647                         plylev, \r
6648                         (gameMode == TwoMachinesPlay ?\r
6649                          ToUpper(cps->twoMachinesColor[0]) : ' '),\r
6650                         ((double) curscore) / 100.0,\r
6651                         prefixHint ? lastHint : "",\r
6652                         prefixHint ? " " : "" );\r
6653 \r
6654                 if( buf1[0] != NULLCHAR ) {\r
6655                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;\r
6656 \r
6657                     if( strlen(buf1) > max_len ) {\r
6658                         if( appData.debugMode) {\r
6659                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");\r
6660                         }\r
6661                         buf1[max_len+1] = '\0';\r
6662                     }\r
6663 \r
6664                     strcat( thinkOutput, buf1 );\r
6665                 }\r
6666 \r
6667                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode\r
6668                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6669                     DisplayMove(currentMove - 1);\r
6670                     DisplayAnalysis();\r
6671                 }\r
6672                 return;\r
6673 \r
6674             } else if ((p=StrStr(message, "(only move)")) != NULL) {\r
6675                 /* crafty (9.25+) says "(only move) <move>"\r
6676                  * if there is only 1 legal move\r
6677                  */\r
6678                 sscanf(p, "(only move) %s", buf1);\r
6679                 sprintf(thinkOutput, "%s (only move)", buf1);\r
6680                 sprintf(programStats.movelist, "%s (only move)", buf1);\r
6681                 programStats.depth = 1;\r
6682                 programStats.nr_moves = 1;\r
6683                 programStats.moves_left = 1;\r
6684                 programStats.nodes = 1;\r
6685                 programStats.time = 1;\r
6686                 programStats.got_only_move = 1;\r
6687 \r
6688                 /* Not really, but we also use this member to\r
6689                    mean "line isn't going to change" (Crafty\r
6690                    isn't searching, so stats won't change) */\r
6691                 programStats.line_is_book = 1;\r
6692 \r
6693                 SendProgramStatsToFrontend( cps, &programStats );\r
6694                 \r
6695                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || \r
6696                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6697                     DisplayMove(currentMove - 1);\r
6698                     DisplayAnalysis();\r
6699                 }\r
6700                 return;\r
6701             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",\r
6702                               &time, &nodes, &plylev, &mvleft,\r
6703                               &mvtot, mvname) >= 5) {\r
6704                 /* The stat01: line is from Crafty (9.29+) in response\r
6705                    to the "." command */\r
6706                 programStats.seen_stat = 1;\r
6707                 cps->maybeThinking = TRUE;\r
6708 \r
6709                 if (programStats.got_only_move || !appData.periodicUpdates)\r
6710                   return;\r
6711 \r
6712                 programStats.depth = plylev;\r
6713                 programStats.time = time;\r
6714                 programStats.nodes = nodes;\r
6715                 programStats.moves_left = mvleft;\r
6716                 programStats.nr_moves = mvtot;\r
6717                 strcpy(programStats.move_name, mvname);\r
6718                 programStats.ok_to_send = 1;\r
6719                 programStats.movelist[0] = '\0';\r
6720 \r
6721                 SendProgramStatsToFrontend( cps, &programStats );\r
6722 \r
6723                 DisplayAnalysis();\r
6724                 return;\r
6725 \r
6726             } else if (strncmp(message,"++",2) == 0) {\r
6727                 /* Crafty 9.29+ outputs this */\r
6728                 programStats.got_fail = 2;\r
6729                 return;\r
6730 \r
6731             } else if (strncmp(message,"--",2) == 0) {\r
6732                 /* Crafty 9.29+ outputs this */\r
6733                 programStats.got_fail = 1;\r
6734                 return;\r
6735 \r
6736             } else if (thinkOutput[0] != NULLCHAR &&\r
6737                        strncmp(message, "    ", 4) == 0) {\r
6738                 unsigned message_len;\r
6739 \r
6740                 p = message;\r
6741                 while (*p && *p == ' ') p++;\r
6742 \r
6743                 message_len = strlen( p );\r
6744 \r
6745                 /* [AS] Avoid buffer overflow */\r
6746                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {\r
6747                     strcat(thinkOutput, " ");\r
6748                     strcat(thinkOutput, p);\r
6749                 }\r
6750 \r
6751                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {\r
6752                     strcat(programStats.movelist, " ");\r
6753                     strcat(programStats.movelist, p);\r
6754                 }\r
6755 \r
6756                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||\r
6757                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
6758                     DisplayMove(currentMove - 1);\r
6759                     DisplayAnalysis();\r
6760                 }\r
6761                 return;\r
6762             }\r
6763         }\r
6764         else {\r
6765             buf1[0] = NULLCHAR;\r
6766 \r
6767             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",\r
6768                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) \r
6769             {\r
6770                 ChessProgramStats cpstats;\r
6771 \r
6772                 if (plyext != ' ' && plyext != '\t') {\r
6773                     time *= 100;\r
6774                 }\r
6775 \r
6776                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
6777                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {\r
6778                     curscore = -curscore;\r
6779                 }\r
6780 \r
6781                 cpstats.depth = plylev;\r
6782                 cpstats.nodes = nodes;\r
6783                 cpstats.time = time;\r
6784                 cpstats.score = curscore;\r
6785                 cpstats.got_only_move = 0;\r
6786                 cpstats.movelist[0] = '\0';\r
6787 \r
6788                 if (buf1[0] != NULLCHAR) {\r
6789                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );\r
6790                 }\r
6791 \r
6792                 cpstats.ok_to_send = 0;\r
6793                 cpstats.line_is_book = 0;\r
6794                 cpstats.nr_moves = 0;\r
6795                 cpstats.moves_left = 0;\r
6796 \r
6797                 SendProgramStatsToFrontend( cps, &cpstats );\r
6798             }\r
6799         }\r
6800     }\r
6801 }\r
6802 \r
6803 \r
6804 /* Parse a game score from the character string "game", and\r
6805    record it as the history of the current game.  The game\r
6806    score is NOT assumed to start from the standard position. \r
6807    The display is not updated in any way.\r
6808    */\r
6809 void\r
6810 ParseGameHistory(game)\r
6811      char *game;\r
6812 {\r
6813     ChessMove moveType;\r
6814     int fromX, fromY, toX, toY, boardIndex;\r
6815     char promoChar;\r
6816     char *p, *q;\r
6817     char buf[MSG_SIZ];\r
6818 \r
6819     if (appData.debugMode)\r
6820       fprintf(debugFP, "Parsing game history: %s\n", game);\r
6821 \r
6822     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");\r
6823     gameInfo.site = StrSave(appData.icsHost);\r
6824     gameInfo.date = PGNDate();\r
6825     gameInfo.round = StrSave("-");\r
6826 \r
6827     /* Parse out names of players */\r
6828     while (*game == ' ') game++;\r
6829     p = buf;\r
6830     while (*game != ' ') *p++ = *game++;\r
6831     *p = NULLCHAR;\r
6832     gameInfo.white = StrSave(buf);\r
6833     while (*game == ' ') game++;\r
6834     p = buf;\r
6835     while (*game != ' ' && *game != '\n') *p++ = *game++;\r
6836     *p = NULLCHAR;\r
6837     gameInfo.black = StrSave(buf);\r
6838 \r
6839     /* Parse moves */\r
6840     boardIndex = blackPlaysFirst ? 1 : 0;\r
6841     yynewstr(game);\r
6842     for (;;) {\r
6843         yyboardindex = boardIndex;\r
6844         moveType = (ChessMove) yylex();\r
6845         switch (moveType) {\r
6846           case IllegalMove:             /* maybe suicide chess, etc. */\r
6847   if (appData.debugMode) {\r
6848     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);\r
6849     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6850     setbuf(debugFP, NULL);\r
6851   }\r
6852           case WhitePromotionChancellor:\r
6853           case BlackPromotionChancellor:\r
6854           case WhitePromotionArchbishop:\r
6855           case BlackPromotionArchbishop:\r
6856           case WhitePromotionQueen:\r
6857           case BlackPromotionQueen:\r
6858           case WhitePromotionRook:\r
6859           case BlackPromotionRook:\r
6860           case WhitePromotionBishop:\r
6861           case BlackPromotionBishop:\r
6862           case WhitePromotionKnight:\r
6863           case BlackPromotionKnight:\r
6864           case WhitePromotionKing:\r
6865           case BlackPromotionKing:\r
6866           case NormalMove:\r
6867           case WhiteCapturesEnPassant:\r
6868           case BlackCapturesEnPassant:\r
6869           case WhiteKingSideCastle:\r
6870           case WhiteQueenSideCastle:\r
6871           case BlackKingSideCastle:\r
6872           case BlackQueenSideCastle:\r
6873           case WhiteKingSideCastleWild:\r
6874           case WhiteQueenSideCastleWild:\r
6875           case BlackKingSideCastleWild:\r
6876           case BlackQueenSideCastleWild:\r
6877           /* PUSH Fabien */\r
6878           case WhiteHSideCastleFR:\r
6879           case WhiteASideCastleFR:\r
6880           case BlackHSideCastleFR:\r
6881           case BlackASideCastleFR:\r
6882           /* POP Fabien */\r
6883             fromX = currentMoveString[0] - AAA;\r
6884             fromY = currentMoveString[1] - ONE;\r
6885             toX = currentMoveString[2] - AAA;\r
6886             toY = currentMoveString[3] - ONE;\r
6887             promoChar = currentMoveString[4];\r
6888             break;\r
6889           case WhiteDrop:\r
6890           case BlackDrop:\r
6891             fromX = moveType == WhiteDrop ?\r
6892               (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
6893             (int) CharToPiece(ToLower(currentMoveString[0]));\r
6894             fromY = DROP_RANK;\r
6895             toX = currentMoveString[2] - AAA;\r
6896             toY = currentMoveString[3] - ONE;\r
6897             promoChar = NULLCHAR;\r
6898             break;\r
6899           case AmbiguousMove:\r
6900             /* bug? */\r
6901             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);\r
6902   if (appData.debugMode) {\r
6903     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);\r
6904     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6905     setbuf(debugFP, NULL);\r
6906   }\r
6907             DisplayError(buf, 0);\r
6908             return;\r
6909           case ImpossibleMove:\r
6910             /* bug? */\r
6911             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);\r
6912   if (appData.debugMode) {\r
6913     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);\r
6914     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6915     setbuf(debugFP, NULL);\r
6916   }\r
6917             DisplayError(buf, 0);\r
6918             return;\r
6919           case (ChessMove) 0:   /* end of file */\r
6920             if (boardIndex < backwardMostMove) {\r
6921                 /* Oops, gap.  How did that happen? */\r
6922                 DisplayError(_("Gap in move list"), 0);\r
6923                 return;\r
6924             }\r
6925             backwardMostMove =  blackPlaysFirst ? 1 : 0;\r
6926             if (boardIndex > forwardMostMove) {\r
6927                 forwardMostMove = boardIndex;\r
6928             }\r
6929             return;\r
6930           case ElapsedTime:\r
6931             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {\r
6932                 strcat(parseList[boardIndex-1], " ");\r
6933                 strcat(parseList[boardIndex-1], yy_text);\r
6934             }\r
6935             continue;\r
6936           case Comment:\r
6937           case PGNTag:\r
6938           case NAG:\r
6939           default:\r
6940             /* ignore */\r
6941             continue;\r
6942           case WhiteWins:\r
6943           case BlackWins:\r
6944           case GameIsDrawn:\r
6945           case GameUnfinished:\r
6946             if (gameMode == IcsExamining) {\r
6947                 if (boardIndex < backwardMostMove) {\r
6948                     /* Oops, gap.  How did that happen? */\r
6949                     return;\r
6950                 }\r
6951                 backwardMostMove = blackPlaysFirst ? 1 : 0;\r
6952                 return;\r
6953             }\r
6954             gameInfo.result = moveType;\r
6955             p = strchr(yy_text, '{');\r
6956             if (p == NULL) p = strchr(yy_text, '(');\r
6957             if (p == NULL) {\r
6958                 p = yy_text;\r
6959                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
6960             } else {\r
6961                 q = strchr(p, *p == '{' ? '}' : ')');\r
6962                 if (q != NULL) *q = NULLCHAR;\r
6963                 p++;\r
6964             }\r
6965             gameInfo.resultDetails = StrSave(p);\r
6966             continue;\r
6967         }\r
6968         if (boardIndex >= forwardMostMove &&\r
6969             !(gameMode == IcsObserving && ics_gamenum == -1)) {\r
6970             backwardMostMove = blackPlaysFirst ? 1 : 0;\r
6971             return;\r
6972         }\r
6973         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),\r
6974                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,\r
6975                                  parseList[boardIndex]);\r
6976         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);\r
6977         /* currentMoveString is set as a side-effect of yylex */\r
6978         strcpy(moveList[boardIndex], currentMoveString);\r
6979         strcat(moveList[boardIndex], "\n");\r
6980         boardIndex++;\r
6981         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);\r
6982         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),\r
6983                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {\r
6984           case MT_NONE:\r
6985           case MT_STALEMATE:\r
6986           default:\r
6987             break;\r
6988           case MT_CHECK:\r
6989             if(gameInfo.variant != VariantShogi)\r
6990                 strcat(parseList[boardIndex - 1], "+");\r
6991             break;\r
6992           case MT_CHECKMATE:\r
6993             strcat(parseList[boardIndex - 1], "#");\r
6994             break;\r
6995         }\r
6996     }\r
6997 }\r
6998 \r
6999 \r
7000 /* Apply a move to the given board  */\r
7001 void\r
7002 ApplyMove(fromX, fromY, toX, toY, promoChar, board)\r
7003      int fromX, fromY, toX, toY;\r
7004      int promoChar;\r
7005      Board board;\r
7006 {\r
7007   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;\r
7008 \r
7009     /* [HGM] compute & store e.p. status and castling rights for new position */\r
7010     /* if we are updating a board for which those exist (i.e. in boards[])    */\r
7011     if((p = ((int)board - (int)boards[0])/((int)boards[1]-(int)boards[0])) < MAX_MOVES && p > 0)\r
7012     { int i;\r
7013 \r
7014       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;\r
7015       oldEP = epStatus[p-1];\r
7016       epStatus[p] = EP_NONE;\r
7017 \r
7018       if( board[toY][toX] != EmptySquare ) \r
7019            epStatus[p] = EP_CAPTURE;  \r
7020 \r
7021       if( board[fromY][fromX] == WhitePawn ) {\r
7022            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers\r
7023                epStatus[p] = EP_PAWN_MOVE;\r
7024            if( toY-fromY==2) {\r
7025                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&\r
7026                         gameInfo.variant != VariantBerolina || toX < fromX)\r
7027                       epStatus[p] = toX | berolina;\r
7028                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&\r
7029                         gameInfo.variant != VariantBerolina || toX > fromX) \r
7030                       epStatus[p] = toX;\r
7031            }\r
7032       } else \r
7033       if( board[fromY][fromX] == BlackPawn ) {\r
7034            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers\r
7035                epStatus[p] = EP_PAWN_MOVE; \r
7036            if( toY-fromY== -2) {\r
7037                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&\r
7038                         gameInfo.variant != VariantBerolina || toX < fromX)\r
7039                       epStatus[p] = toX | berolina;\r
7040                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&\r
7041                         gameInfo.variant != VariantBerolina || toX > fromX) \r
7042                       epStatus[p] = toX;\r
7043            }\r
7044        }\r
7045 \r
7046        for(i=0; i<nrCastlingRights; i++) {\r
7047            castlingRights[p][i] = castlingRights[p-1][i];\r
7048            if(castlingRights[p][i] == fromX && castlingRank[i] == fromY ||\r
7049               castlingRights[p][i] == toX   && castlingRank[i] == toY   \r
7050              ) castlingRights[p][i] = -1; // revoke for moved or captured piece\r
7051        }\r
7052 \r
7053     }\r
7054 \r
7055   /* [HGM] In Shatranj and Courier all promotions are to Ferz */\r
7056   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)\r
7057        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);\r
7058          \r
7059   if (fromX == toX && fromY == toY) return;\r
7060 \r
7061   if (fromY == DROP_RANK) {\r
7062         /* must be first */\r
7063         piece = board[toY][toX] = (ChessSquare) fromX;\r
7064   } else {\r
7065      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */\r
7066      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */\r
7067      if(gameInfo.variant == VariantKnightmate)\r
7068          king += (int) WhiteUnicorn - (int) WhiteKing;\r
7069 \r
7070     /* Code added by Tord: */\r
7071     /* FRC castling assumed when king captures friendly rook. */\r
7072     if (board[fromY][fromX] == WhiteKing &&\r
7073              board[toY][toX] == WhiteRook) {\r
7074       board[fromY][fromX] = EmptySquare;\r
7075       board[toY][toX] = EmptySquare;\r
7076       if(toX > fromX) {\r
7077         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;\r
7078       } else {\r
7079         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;\r
7080       }\r
7081     } else if (board[fromY][fromX] == BlackKing &&\r
7082                board[toY][toX] == BlackRook) {\r
7083       board[fromY][fromX] = EmptySquare;\r
7084       board[toY][toX] = EmptySquare;\r
7085       if(toX > fromX) {\r
7086         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;\r
7087       } else {\r
7088         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;\r
7089       }\r
7090     /* End of code added by Tord */\r
7091 \r
7092     } else if (board[fromY][fromX] == king\r
7093         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7094         && toY == fromY && toX > fromX+1) {\r
7095         board[fromY][fromX] = EmptySquare;\r
7096         board[toY][toX] = king;\r
7097         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
7098         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
7099     } else if (board[fromY][fromX] == king\r
7100         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7101                && toY == fromY && toX < fromX-1) {\r
7102         board[fromY][fromX] = EmptySquare;\r
7103         board[toY][toX] = king;\r
7104         board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
7105         board[fromY][BOARD_LEFT] = EmptySquare;\r
7106     } else if (board[fromY][fromX] == WhitePawn\r
7107                && toY == BOARD_HEIGHT-1\r
7108                && gameInfo.variant != VariantXiangqi\r
7109                ) {\r
7110         /* white pawn promotion */\r
7111         board[toY][toX] = CharToPiece(ToUpper(promoChar));\r
7112         if (board[toY][toX] == EmptySquare) {\r
7113             board[toY][toX] = WhiteQueen;\r
7114         }\r
7115         if(gameInfo.variant==VariantBughouse ||\r
7116            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
7117             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
7118         board[fromY][fromX] = EmptySquare;\r
7119     } else if ((fromY == BOARD_HEIGHT-4)\r
7120                && (toX != fromX)\r
7121                && gameInfo.variant != VariantXiangqi\r
7122                && gameInfo.variant != VariantBerolina\r
7123                && (board[fromY][fromX] == WhitePawn)\r
7124                && (board[toY][toX] == EmptySquare)) {\r
7125         board[fromY][fromX] = EmptySquare;\r
7126         board[toY][toX] = WhitePawn;\r
7127         captured = board[toY - 1][toX];\r
7128         board[toY - 1][toX] = EmptySquare;\r
7129     } else if ((fromY == BOARD_HEIGHT-4)\r
7130                && (toX == fromX)\r
7131                && gameInfo.variant == VariantBerolina\r
7132                && (board[fromY][fromX] == WhitePawn)\r
7133                && (board[toY][toX] == EmptySquare)) {\r
7134         board[fromY][fromX] = EmptySquare;\r
7135         board[toY][toX] = WhitePawn;\r
7136         if(oldEP & EP_BEROLIN_A) {\r
7137                 captured = board[fromY][fromX-1];\r
7138                 board[fromY][fromX-1] = EmptySquare;\r
7139         }else{  captured = board[fromY][fromX+1];\r
7140                 board[fromY][fromX+1] = EmptySquare;\r
7141         }\r
7142     } else if (board[fromY][fromX] == king\r
7143         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7144                && toY == fromY && toX > fromX+1) {\r
7145         board[fromY][fromX] = EmptySquare;\r
7146         board[toY][toX] = king;\r
7147         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
7148         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
7149     } else if (board[fromY][fromX] == king\r
7150         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
7151                && toY == fromY && toX < fromX-1) {\r
7152         board[fromY][fromX] = EmptySquare;\r
7153         board[toY][toX] = king;\r
7154         board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
7155         board[fromY][BOARD_LEFT] = EmptySquare;\r
7156     } else if (fromY == 7 && fromX == 3\r
7157                && board[fromY][fromX] == BlackKing\r
7158                && toY == 7 && toX == 5) {\r
7159         board[fromY][fromX] = EmptySquare;\r
7160         board[toY][toX] = BlackKing;\r
7161         board[fromY][7] = EmptySquare;\r
7162         board[toY][4] = BlackRook;\r
7163     } else if (fromY == 7 && fromX == 3\r
7164                && board[fromY][fromX] == BlackKing\r
7165                && toY == 7 && toX == 1) {\r
7166         board[fromY][fromX] = EmptySquare;\r
7167         board[toY][toX] = BlackKing;\r
7168         board[fromY][0] = EmptySquare;\r
7169         board[toY][2] = BlackRook;\r
7170     } else if (board[fromY][fromX] == BlackPawn\r
7171                && toY == 0\r
7172                && gameInfo.variant != VariantXiangqi\r
7173                ) {\r
7174         /* black pawn promotion */\r
7175         board[0][toX] = CharToPiece(ToLower(promoChar));\r
7176         if (board[0][toX] == EmptySquare) {\r
7177             board[0][toX] = BlackQueen;\r
7178         }\r
7179         if(gameInfo.variant==VariantBughouse ||\r
7180            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
7181             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
7182         board[fromY][fromX] = EmptySquare;\r
7183     } else if ((fromY == 3)\r
7184                && (toX != fromX)\r
7185                && gameInfo.variant != VariantXiangqi\r
7186                && gameInfo.variant != VariantBerolina\r
7187                && (board[fromY][fromX] == BlackPawn)\r
7188                && (board[toY][toX] == EmptySquare)) {\r
7189         board[fromY][fromX] = EmptySquare;\r
7190         board[toY][toX] = BlackPawn;\r
7191         captured = board[toY + 1][toX];\r
7192         board[toY + 1][toX] = EmptySquare;\r
7193     } else if ((fromY == 3)\r
7194                && (toX == fromX)\r
7195                && gameInfo.variant == VariantBerolina\r
7196                && (board[fromY][fromX] == BlackPawn)\r
7197                && (board[toY][toX] == EmptySquare)) {\r
7198         board[fromY][fromX] = EmptySquare;\r
7199         board[toY][toX] = BlackPawn;\r
7200         if(oldEP & EP_BEROLIN_A) {\r
7201                 captured = board[fromY][fromX-1];\r
7202                 board[fromY][fromX-1] = EmptySquare;\r
7203         }else{  captured = board[fromY][fromX+1];\r
7204                 board[fromY][fromX+1] = EmptySquare;\r
7205         }\r
7206     } else {\r
7207         board[toY][toX] = board[fromY][fromX];\r
7208         board[fromY][fromX] = EmptySquare;\r
7209     }\r
7210 \r
7211     /* [HGM] now we promote for Shogi, if needed */\r
7212     if(gameInfo.variant == VariantShogi && promoChar == 'q')\r
7213         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
7214   }\r
7215 \r
7216     if (gameInfo.holdingsWidth != 0) {\r
7217 \r
7218       /* !!A lot more code needs to be written to support holdings  */\r
7219       /* [HGM] OK, so I have written it. Holdings are stored in the */\r
7220       /* penultimate board files, so they are automaticlly stored   */\r
7221       /* in the game history.                                       */\r
7222       if (fromY == DROP_RANK) {\r
7223         /* Delete from holdings, by decreasing count */\r
7224         /* and erasing image if necessary            */\r
7225         p = (int) fromX;\r
7226         if(p < (int) BlackPawn) { /* white drop */\r
7227              p -= (int)WhitePawn;\r
7228              if(p >= gameInfo.holdingsSize) p = 0;\r
7229              if(--board[p][BOARD_WIDTH-2] == 0)\r
7230                   board[p][BOARD_WIDTH-1] = EmptySquare;\r
7231         } else {                  /* black drop */\r
7232              p -= (int)BlackPawn;\r
7233              if(p >= gameInfo.holdingsSize) p = 0;\r
7234              if(--board[BOARD_HEIGHT-1-p][1] == 0)\r
7235                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;\r
7236         }\r
7237       }\r
7238       if (captured != EmptySquare && gameInfo.holdingsSize > 0\r
7239           && gameInfo.variant != VariantBughouse        ) {\r
7240         /* [HGM] holdings: Add to holdings, if holdings exist */\r
7241         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { \r
7242                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip\r
7243                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;\r
7244         }\r
7245         p = (int) captured;\r
7246         if (p >= (int) BlackPawn) {\r
7247           p -= (int)BlackPawn;\r
7248           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
7249                   /* in Shogi restore piece to its original  first */\r
7250                   captured = (ChessSquare) (DEMOTED captured);\r
7251                   p = DEMOTED p;\r
7252           }\r
7253           p = PieceToNumber((ChessSquare)p);\r
7254           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }\r
7255           board[p][BOARD_WIDTH-2]++;\r
7256           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;\r
7257         } else {\r
7258           p -= (int)WhitePawn;\r
7259           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
7260                   captured = (ChessSquare) (DEMOTED captured);\r
7261                   p = DEMOTED p;\r
7262           }\r
7263           p = PieceToNumber((ChessSquare)p);\r
7264           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }\r
7265           board[BOARD_HEIGHT-1-p][1]++;\r
7266           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;\r
7267         }\r
7268       }\r
7269 \r
7270     } else if (gameInfo.variant == VariantAtomic) {\r
7271       if (captured != EmptySquare) {\r
7272         int y, x;\r
7273         for (y = toY-1; y <= toY+1; y++) {\r
7274           for (x = toX-1; x <= toX+1; x++) {\r
7275             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&\r
7276                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {\r
7277               board[y][x] = EmptySquare;\r
7278             }\r
7279           }\r
7280         }\r
7281         board[toY][toX] = EmptySquare;\r
7282       }\r
7283     }\r
7284     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {\r
7285         /* [HGM] Shogi promotions */\r
7286         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
7287     }\r
7288 \r
7289 }\r
7290 \r
7291 /* Updates forwardMostMove */\r
7292 void\r
7293 MakeMove(fromX, fromY, toX, toY, promoChar)\r
7294      int fromX, fromY, toX, toY;\r
7295      int promoChar;\r
7296 {\r
7297 //    forwardMostMove++; // [HGM] bare: moved downstream\r
7298 \r
7299     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */\r
7300         int timeLeft; static int lastLoadFlag=0; int king, piece;\r
7301         piece = boards[forwardMostMove][fromY][fromX];\r
7302         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;\r
7303         if(gameInfo.variant == VariantKnightmate)\r
7304             king += (int) WhiteUnicorn - (int) WhiteKing;\r
7305         if(forwardMostMove == 0) {\r
7306             if(blackPlaysFirst) \r
7307                 fprintf(serverMoves, "%s;", second.tidy);\r
7308             fprintf(serverMoves, "%s;", first.tidy);\r
7309             if(!blackPlaysFirst) \r
7310                 fprintf(serverMoves, "%s;", second.tidy);\r
7311         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");\r
7312         lastLoadFlag = loadFlag;\r
7313         // print base move\r
7314         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);\r
7315         // print castling suffix\r
7316         if( toY == fromY && piece == king ) {\r
7317             if(toX-fromX > 1)\r
7318                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);\r
7319             if(fromX-toX >1)\r
7320                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);\r
7321         }\r
7322         // e.p. suffix\r
7323         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||\r
7324              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&\r
7325              boards[forwardMostMove][toY][toX] == EmptySquare\r
7326              && fromX != toX )\r
7327                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);\r
7328         // promotion suffix\r
7329         if(promoChar != NULLCHAR)\r
7330                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);\r
7331         if(!loadFlag) {\r
7332             fprintf(serverMoves, "/%d/%d",\r
7333                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);\r
7334             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;\r
7335             else                      timeLeft = blackTimeRemaining/1000;\r
7336             fprintf(serverMoves, "/%d", timeLeft);\r
7337         }\r
7338         fflush(serverMoves);\r
7339     }\r
7340 \r
7341     if (forwardMostMove+1 >= MAX_MOVES) {\r
7342       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),\r
7343                         0, 1);\r
7344       return;\r
7345     }\r
7346     SwitchClocks();\r
7347     timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;\r
7348     timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;\r
7349     if (commentList[forwardMostMove+1] != NULL) {\r
7350         free(commentList[forwardMostMove+1]);\r
7351         commentList[forwardMostMove+1] = NULL;\r
7352     }\r
7353     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);\r
7354     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);\r
7355     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board\r
7356     gameInfo.result = GameUnfinished;\r
7357     if (gameInfo.resultDetails != NULL) {\r
7358         free(gameInfo.resultDetails);\r
7359         gameInfo.resultDetails = NULL;\r
7360     }\r
7361     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,\r
7362                               moveList[forwardMostMove - 1]);\r
7363     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],\r
7364                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,\r
7365                              fromY, fromX, toY, toX, promoChar,\r
7366                              parseList[forwardMostMove - 1]);\r
7367     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
7368                        epStatus[forwardMostMove], /* [HGM] use true e.p. */\r
7369                             castlingRights[forwardMostMove]) ) {\r
7370       case MT_NONE:\r
7371       case MT_STALEMATE:\r
7372       default:\r
7373         break;\r
7374       case MT_CHECK:\r
7375         if(gameInfo.variant != VariantShogi)\r
7376             strcat(parseList[forwardMostMove - 1], "+");\r
7377         break;\r
7378       case MT_CHECKMATE:\r
7379         strcat(parseList[forwardMostMove - 1], "#");\r
7380         break;\r
7381     }\r
7382     if (appData.debugMode) {\r
7383         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);\r
7384     }\r
7385 \r
7386 }\r
7387 \r
7388 /* Updates currentMove if not pausing */\r
7389 void\r
7390 ShowMove(fromX, fromY, toX, toY)\r
7391 {\r
7392     int instant = (gameMode == PlayFromGameFile) ?\r
7393         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;\r
7394     if(appData.noGUI) return;\r
7395     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
7396         if (!instant) {\r
7397             if (forwardMostMove == currentMove + 1) {\r
7398                 AnimateMove(boards[forwardMostMove - 1],\r
7399                             fromX, fromY, toX, toY);\r
7400             }\r
7401             if (appData.highlightLastMove) {\r
7402                 SetHighlights(fromX, fromY, toX, toY);\r
7403             }\r
7404         }\r
7405         currentMove = forwardMostMove;\r
7406     }\r
7407 \r
7408     if (instant) return;\r
7409 \r
7410     DisplayMove(currentMove - 1);\r
7411     DrawPosition(FALSE, boards[currentMove]);\r
7412     DisplayBothClocks();\r
7413     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
7414 }\r
7415 \r
7416 void SendEgtPath(ChessProgramState *cps)\r
7417 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */\r
7418         char buf[MSG_SIZ], name[MSG_SIZ], *p;\r
7419 \r
7420         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;\r
7421 \r
7422         while(*p) {\r
7423             char c, *q = name+1, *r, *s;\r
7424 \r
7425             name[0] = ','; // extract next format name from feature and copy with prefixed ','\r
7426             while(*p && *p != ',') *q++ = *p++;\r
7427             *q++ = ':'; *q = 0;\r
7428             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && \r
7429                 strcmp(name, ",nalimov:") == 0 ) {\r
7430                 // take nalimov path from the menu-changeable option first, if it is defined\r
7431                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);\r
7432                 SendToProgram(buf,cps);     // send egtbpath command for nalimov\r
7433             } else\r
7434             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||\r
7435                 (s = StrStr(appData.egtFormats, name)) != NULL) {\r
7436                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma\r
7437                 s = r = StrStr(s, ":") + 1; // beginning of path info\r
7438                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string\r
7439                 c = *r; *r = 0;             // temporarily null-terminate path info\r
7440                     *--q = 0;               // strip of trailig ':' from name\r
7441                     sprintf(buf, "egtbpath %s %s\n", name+1, s);\r
7442                 *r = c;\r
7443                 SendToProgram(buf,cps);     // send egtbpath command for this format\r
7444             }\r
7445             if(*p == ',') p++; // read away comma to position for next format name\r
7446         }\r
7447 }\r
7448 \r
7449 void\r
7450 InitChessProgram(cps, setup)\r
7451      ChessProgramState *cps;\r
7452      int setup; /* [HGM] needed to setup FRC opening position */\r
7453 {\r
7454     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;\r
7455     if (appData.noChessProgram) return;\r
7456     hintRequested = FALSE;\r
7457     bookRequested = FALSE;\r
7458 \r
7459     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */\r
7460     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */\r
7461     if(cps->memSize) { /* [HGM] memory */\r
7462         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);\r
7463         SendToProgram(buf, cps);\r
7464     }\r
7465     SendEgtPath(cps); /* [HGM] EGT */\r
7466     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */\r
7467         sprintf(buf, "cores %d\n", appData.smpCores);\r
7468         SendToProgram(buf, cps);\r
7469     }\r
7470 \r
7471     SendToProgram(cps->initString, cps);\r
7472     if (gameInfo.variant != VariantNormal &&\r
7473         gameInfo.variant != VariantLoadable\r
7474         /* [HGM] also send variant if board size non-standard */\r
7475         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0\r
7476                                             ) {\r
7477       char *v = VariantName(gameInfo.variant);\r
7478       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {\r
7479         /* [HGM] in protocol 1 we have to assume all variants valid */\r
7480         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);\r
7481         DisplayFatalError(buf, 0, 1);\r
7482         return;\r
7483       }\r
7484 \r
7485       /* [HGM] make prefix for non-standard board size. Awkward testing... */\r
7486       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7487       if( gameInfo.variant == VariantXiangqi )\r
7488            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;\r
7489       if( gameInfo.variant == VariantShogi )\r
7490            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;\r
7491       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )\r
7492            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;\r
7493       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || \r
7494                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )\r
7495            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7496       if( gameInfo.variant == VariantCourier )\r
7497            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7498       if( gameInfo.variant == VariantSuper )\r
7499            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;\r
7500       if( gameInfo.variant == VariantGreat )\r
7501            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;\r
7502 \r
7503       if(overruled) {\r
7504            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, \r
7505                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name\r
7506            /* [HGM] varsize: try first if this defiant size variant is specifically known */\r
7507            if(StrStr(cps->variants, b) == NULL) { \r
7508                // specific sized variant not known, check if general sizing allowed\r
7509                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best\r
7510                    if(StrStr(cps->variants, "boardsize") == NULL) {\r
7511                        sprintf(buf, "Board size %dx%d+%d not supported by %s",\r
7512                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);\r
7513                        DisplayFatalError(buf, 0, 1);\r
7514                        return;\r
7515                    }\r
7516                    /* [HGM] here we really should compare with the maximum supported board size */\r
7517                }\r
7518            }\r
7519       } else sprintf(b, "%s", VariantName(gameInfo.variant));\r
7520       sprintf(buf, "variant %s\n", b);\r
7521       SendToProgram(buf, cps);\r
7522     }\r
7523     currentlyInitializedVariant = gameInfo.variant;\r
7524 \r
7525     /* [HGM] send opening position in FRC to first engine */\r
7526     if(setup) {\r
7527           SendToProgram("force\n", cps);\r
7528           SendBoard(cps, 0);\r
7529           /* engine is now in force mode! Set flag to wake it up after first move. */\r
7530           setboardSpoiledMachineBlack = 1;\r
7531     }\r
7532 \r
7533     if (cps->sendICS) {\r
7534       sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");\r
7535       SendToProgram(buf, cps);\r
7536     }\r
7537     cps->maybeThinking = FALSE;\r
7538     cps->offeredDraw = 0;\r
7539     if (!appData.icsActive) {\r
7540         SendTimeControl(cps, movesPerSession, timeControl,\r
7541                         timeIncrement, appData.searchDepth,\r
7542                         searchTime);\r
7543     }\r
7544     if (appData.showThinking \r
7545         // [HGM] thinking: four options require thinking output to be sent\r
7546         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()\r
7547                                 ) {\r
7548         SendToProgram("post\n", cps);\r
7549     }\r
7550     SendToProgram("hard\n", cps);\r
7551     if (!appData.ponderNextMove) {\r
7552         /* Warning: "easy" is a toggle in GNU Chess, so don't send\r
7553            it without being sure what state we are in first.  "hard"\r
7554            is not a toggle, so that one is OK.\r
7555          */\r
7556         SendToProgram("easy\n", cps);\r
7557     }\r
7558     if (cps->usePing) {\r
7559       sprintf(buf, "ping %d\n", ++cps->lastPing);\r
7560       SendToProgram(buf, cps);\r
7561     }\r
7562     cps->initDone = TRUE;\r
7563 }   \r
7564 \r
7565 \r
7566 void\r
7567 StartChessProgram(cps)\r
7568      ChessProgramState *cps;\r
7569 {\r
7570     char buf[MSG_SIZ];\r
7571     int err;\r
7572 \r
7573     if (appData.noChessProgram) return;\r
7574     cps->initDone = FALSE;\r
7575 \r
7576     if (strcmp(cps->host, "localhost") == 0) {\r
7577         err = StartChildProcess(cps->program, cps->dir, &cps->pr);\r
7578     } else if (*appData.remoteShell == NULLCHAR) {\r
7579         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);\r
7580     } else {\r
7581         if (*appData.remoteUser == NULLCHAR) {\r
7582             sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,\r
7583                     cps->program);\r
7584         } else {\r
7585             sprintf(buf, "%s %s -l %s %s", appData.remoteShell,\r
7586                     cps->host, appData.remoteUser, cps->program);\r
7587         }\r
7588         err = StartChildProcess(buf, "", &cps->pr);\r
7589     }\r
7590     \r
7591     if (err != 0) {\r
7592         sprintf(buf, _("Startup failure on '%s'"), cps->program);\r
7593         DisplayFatalError(buf, err, 1);\r
7594         cps->pr = NoProc;\r
7595         cps->isr = NULL;\r
7596         return;\r
7597     }\r
7598     \r
7599     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);\r
7600     if (cps->protocolVersion > 1) {\r
7601       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);\r
7602       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options\r
7603       cps->comboCnt = 0;  //                and values of combo boxes\r
7604       SendToProgram(buf, cps);\r
7605     } else {\r
7606       SendToProgram("xboard\n", cps);\r
7607     }\r
7608 }\r
7609 \r
7610 \r
7611 void\r
7612 TwoMachinesEventIfReady P((void))\r
7613 {\r
7614   if (first.lastPing != first.lastPong) {\r
7615     DisplayMessage("", _("Waiting for first chess program"));\r
7616     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000\r
7617     return;\r
7618   }\r
7619   if (second.lastPing != second.lastPong) {\r
7620     DisplayMessage("", _("Waiting for second chess program"));\r
7621     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000\r
7622     return;\r
7623   }\r
7624   ThawUI();\r
7625   TwoMachinesEvent();\r
7626 }\r
7627 \r
7628 void\r
7629 NextMatchGame P((void))\r
7630 {\r
7631     int index; /* [HGM] autoinc: step lod index during match */\r
7632     Reset(FALSE, TRUE);\r
7633     if (*appData.loadGameFile != NULLCHAR) {\r
7634         index = appData.loadGameIndex;\r
7635         if(index < 0) { // [HGM] autoinc\r
7636             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;\r
7637             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;\r
7638         } \r
7639         LoadGameFromFile(appData.loadGameFile,\r
7640                          index,\r
7641                          appData.loadGameFile, FALSE);\r
7642     } else if (*appData.loadPositionFile != NULLCHAR) {\r
7643         index = appData.loadPositionIndex;\r
7644         if(index < 0) { // [HGM] autoinc\r
7645             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;\r
7646             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;\r
7647         } \r
7648         LoadPositionFromFile(appData.loadPositionFile,\r
7649                              index,\r
7650                              appData.loadPositionFile);\r
7651     }\r
7652     TwoMachinesEventIfReady();\r
7653 }\r
7654 \r
7655 void UserAdjudicationEvent( int result )\r
7656 {\r
7657     ChessMove gameResult = GameIsDrawn;\r
7658 \r
7659     if( result > 0 ) {\r
7660         gameResult = WhiteWins;\r
7661     }\r
7662     else if( result < 0 ) {\r
7663         gameResult = BlackWins;\r
7664     }\r
7665 \r
7666     if( gameMode == TwoMachinesPlay ) {\r
7667         GameEnds( gameResult, "User adjudication", GE_XBOARD );\r
7668     }\r
7669 }\r
7670 \r
7671 \r
7672 void\r
7673 GameEnds(result, resultDetails, whosays)\r
7674      ChessMove result;\r
7675      char *resultDetails;\r
7676      int whosays;\r
7677 {\r
7678     GameMode nextGameMode;\r
7679     int isIcsGame;\r
7680     char buf[MSG_SIZ];\r
7681 \r
7682     if(endingGame) return; /* [HGM] crash: forbid recursion */\r
7683     endingGame = 1;\r
7684 \r
7685     if (appData.debugMode) {\r
7686       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",\r
7687               result, resultDetails ? resultDetails : "(null)", whosays);\r
7688     }\r
7689 \r
7690     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {\r
7691         /* If we are playing on ICS, the server decides when the\r
7692            game is over, but the engine can offer to draw, claim \r
7693            a draw, or resign. \r
7694          */\r
7695 #if ZIPPY\r
7696         if (appData.zippyPlay && first.initDone) {\r
7697             if (result == GameIsDrawn) {\r
7698                 /* In case draw still needs to be claimed */\r
7699                 SendToICS(ics_prefix);\r
7700                 SendToICS("draw\n");\r
7701             } else if (StrCaseStr(resultDetails, "resign")) {\r
7702                 SendToICS(ics_prefix);\r
7703                 SendToICS("resign\n");\r
7704             }\r
7705         }\r
7706 #endif\r
7707         endingGame = 0; /* [HGM] crash */\r
7708         return;\r
7709     }\r
7710 \r
7711     /* If we're loading the game from a file, stop */\r
7712     if (whosays == GE_FILE) {\r
7713       (void) StopLoadGameTimer();\r
7714       gameFileFP = NULL;\r
7715     }\r
7716 \r
7717     /* Cancel draw offers */\r
7718     first.offeredDraw = second.offeredDraw = 0;\r
7719 \r
7720     /* If this is an ICS game, only ICS can really say it's done;\r
7721        if not, anyone can. */\r
7722     isIcsGame = (gameMode == IcsPlayingWhite || \r
7723                  gameMode == IcsPlayingBlack || \r
7724                  gameMode == IcsObserving    || \r
7725                  gameMode == IcsExamining);\r
7726 \r
7727     if (!isIcsGame || whosays == GE_ICS) {\r
7728         /* OK -- not an ICS game, or ICS said it was done */\r
7729         StopClocks();\r
7730         if (!isIcsGame && !appData.noChessProgram) \r
7731           SetUserThinkingEnables();\r
7732     \r
7733         /* [HGM] if a machine claims the game end we verify this claim */\r
7734         if(gameMode == TwoMachinesPlay && appData.testClaims) {\r
7735             if(appData.testLegality && whosays >= GE_ENGINE1 ) {\r
7736                 char claimer;\r
7737                 ChessMove trueResult = (ChessMove) -1;\r
7738 \r
7739                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */\r
7740                                             first.twoMachinesColor[0] :\r
7741                                             second.twoMachinesColor[0] ;\r
7742 \r
7743                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first\r
7744                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {\r
7745                     /* [HGM] verify: engine mate claims accepted if they were flagged */\r
7746                     trueResult = WhiteOnMove(forwardMostMove) != (gameInfo.variant == VariantLosers)\r
7747                         ? BlackWins : WhiteWins; // [HGM] losers: reverse the result in VariantLosers!\r
7748                 } else\r
7749                 if(epStatus[forwardMostMove] == EP_STALEMATE) {\r
7750                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE\r
7751                     if(gameInfo.variant == VariantGiveaway || gameInfo.variant == VariantSuicide || \r
7752                        gameInfo.variant == VariantLosers)  // [HGM] losers: in giveaway variants stalemate wins\r
7753                         trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;\r
7754                 }\r
7755 \r
7756                 // now verify win claims, but not in drop games, as we don't understand those yet\r
7757                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper\r
7758                                                  || gameInfo.variant == VariantGreat) &&\r
7759                     (result == WhiteWins && claimer == 'w' ||\r
7760                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win\r
7761                       if (appData.debugMode) {\r
7762                         fprintf(debugFP, "result=%d sp=%d move=%d\n",\r
7763                                 result, epStatus[forwardMostMove], forwardMostMove);\r
7764                       }\r
7765                       if(result != trueResult) {\r
7766                               sprintf(buf, "False win claim: '%s'", resultDetails);\r
7767                               result = claimer == 'w' ? BlackWins : WhiteWins;\r
7768                               resultDetails = buf;\r
7769                       }\r
7770                 } else\r
7771                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS\r
7772                     && (forwardMostMove <= backwardMostMove ||\r
7773                         epStatus[forwardMostMove-1] > EP_DRAWS ||\r
7774                         (claimer=='b')==(forwardMostMove&1))\r
7775                                                                                   ) {\r
7776                       /* [HGM] verify: draws that were not flagged are false claims */\r
7777                       sprintf(buf, "False draw claim: '%s'", resultDetails);\r
7778                       result = claimer == 'w' ? BlackWins : WhiteWins;\r
7779                       resultDetails = buf;\r
7780                 }\r
7781                 /* (Claiming a loss is accepted no questions asked!) */\r
7782             }\r
7783             /* [HGM] bare: don't allow bare King to win */\r
7784             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)\r
7785                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway \r
7786                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...\r
7787                && result != GameIsDrawn)\r
7788             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);\r
7789                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {\r
7790                         int p = (int)boards[forwardMostMove][i][j] - color;\r
7791                         if(p >= 0 && p <= (int)WhiteKing) k++;\r
7792                 }\r
7793                 if (appData.debugMode) {\r
7794                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",\r
7795                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);\r
7796                 }\r
7797                 if(k <= 1) {\r
7798                         result = GameIsDrawn;\r
7799                         sprintf(buf, "%s but bare king", resultDetails);\r
7800                         resultDetails = buf;\r
7801                 }\r
7802             }\r
7803         }\r
7804 \r
7805 \r
7806         if(serverMoves != NULL && !loadFlag) { char c = '=';\r
7807             if(result==WhiteWins) c = '+';\r
7808             if(result==BlackWins) c = '-';\r
7809             if(resultDetails != NULL)\r
7810                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);\r
7811         }\r
7812         if (resultDetails != NULL) {\r
7813             gameInfo.result = result;\r
7814             gameInfo.resultDetails = StrSave(resultDetails);\r
7815 \r
7816             /* display last move only if game was not loaded from file */\r
7817             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))\r
7818                 DisplayMove(currentMove - 1);\r
7819     \r
7820             if (forwardMostMove != 0) {\r
7821                 if (gameMode != PlayFromGameFile && gameMode != EditGame) {\r
7822                     if (*appData.saveGameFile != NULLCHAR) {\r
7823                         SaveGameToFile(appData.saveGameFile, TRUE);\r
7824                     } else if (appData.autoSaveGames) {\r
7825                         AutoSaveGame();\r
7826                     }\r
7827                     if (*appData.savePositionFile != NULLCHAR) {\r
7828                         SavePositionToFile(appData.savePositionFile);\r
7829                     }\r
7830                 }\r
7831             }\r
7832 \r
7833             /* Tell program how game ended in case it is learning */\r
7834             /* [HGM] Moved this to after saving the PGN, just in case */\r
7835             /* engine died and we got here through time loss. In that */\r
7836             /* case we will get a fatal error writing the pipe, which */\r
7837             /* would otherwise lose us the PGN.                       */\r
7838             /* [HGM] crash: not needed anymore, but doesn't hurt;     */\r
7839             /* output during GameEnds should never be fatal anymore   */\r
7840             if (gameMode == MachinePlaysWhite ||\r
7841                 gameMode == MachinePlaysBlack ||\r
7842                 gameMode == TwoMachinesPlay ||\r
7843                 gameMode == IcsPlayingWhite ||\r
7844                 gameMode == IcsPlayingBlack ||\r
7845                 gameMode == BeginningOfGame) {\r
7846                 char buf[MSG_SIZ];\r
7847                 sprintf(buf, "result %s {%s}\n", PGNResult(result),\r
7848                         resultDetails);\r
7849                 if (first.pr != NoProc) {\r
7850                     SendToProgram(buf, &first);\r
7851                 }\r
7852                 if (second.pr != NoProc &&\r
7853                     gameMode == TwoMachinesPlay) {\r
7854                     SendToProgram(buf, &second);\r
7855                 }\r
7856             }\r
7857         }\r
7858 \r
7859         if (appData.icsActive) {\r
7860             if (appData.quietPlay &&\r
7861                 (gameMode == IcsPlayingWhite ||\r
7862                  gameMode == IcsPlayingBlack)) {\r
7863                 SendToICS(ics_prefix);\r
7864                 SendToICS("set shout 1\n");\r
7865             }\r
7866             nextGameMode = IcsIdle;\r
7867             ics_user_moved = FALSE;\r
7868             /* clean up premove.  It's ugly when the game has ended and the\r
7869              * premove highlights are still on the board.\r
7870              */\r
7871             if (gotPremove) {\r
7872               gotPremove = FALSE;\r
7873               ClearPremoveHighlights();\r
7874               DrawPosition(FALSE, boards[currentMove]);\r
7875             }\r
7876             if (whosays == GE_ICS) {\r
7877                 switch (result) {\r
7878                 case WhiteWins:\r
7879                     if (gameMode == IcsPlayingWhite)\r
7880                         PlayIcsWinSound();\r
7881                     else if(gameMode == IcsPlayingBlack)\r
7882                         PlayIcsLossSound();\r
7883                     break;\r
7884                 case BlackWins:\r
7885                     if (gameMode == IcsPlayingBlack)\r
7886                         PlayIcsWinSound();\r
7887                     else if(gameMode == IcsPlayingWhite)\r
7888                         PlayIcsLossSound();\r
7889                     break;\r
7890                 case GameIsDrawn:\r
7891                     PlayIcsDrawSound();\r
7892                     break;\r
7893                 default:\r
7894                     PlayIcsUnfinishedSound();\r
7895                 }\r
7896             }\r
7897         } else if (gameMode == EditGame ||\r
7898                    gameMode == PlayFromGameFile || \r
7899                    gameMode == AnalyzeMode || \r
7900                    gameMode == AnalyzeFile) {\r
7901             nextGameMode = gameMode;\r
7902         } else {\r
7903             nextGameMode = EndOfGame;\r
7904         }\r
7905         pausing = FALSE;\r
7906         ModeHighlight();\r
7907     } else {\r
7908         nextGameMode = gameMode;\r
7909     }\r
7910 \r
7911     if (appData.noChessProgram) {\r
7912         gameMode = nextGameMode;\r
7913         ModeHighlight();\r
7914         endingGame = 0; /* [HGM] crash */\r
7915         return;\r
7916     }\r
7917 \r
7918     if (first.reuse) {\r
7919         /* Put first chess program into idle state */\r
7920         if (first.pr != NoProc &&\r
7921             (gameMode == MachinePlaysWhite ||\r
7922              gameMode == MachinePlaysBlack ||\r
7923              gameMode == TwoMachinesPlay ||\r
7924              gameMode == IcsPlayingWhite ||\r
7925              gameMode == IcsPlayingBlack ||\r
7926              gameMode == BeginningOfGame)) {\r
7927             SendToProgram("force\n", &first);\r
7928             if (first.usePing) {\r
7929               char buf[MSG_SIZ];\r
7930               sprintf(buf, "ping %d\n", ++first.lastPing);\r
7931               SendToProgram(buf, &first);\r
7932             }\r
7933         }\r
7934     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
7935         /* Kill off first chess program */\r
7936         if (first.isr != NULL)\r
7937           RemoveInputSource(first.isr);\r
7938         first.isr = NULL;\r
7939     \r
7940         if (first.pr != NoProc) {\r
7941             ExitAnalyzeMode();\r
7942             DoSleep( appData.delayBeforeQuit );\r
7943             SendToProgram("quit\n", &first);\r
7944             DoSleep( appData.delayAfterQuit );\r
7945             DestroyChildProcess(first.pr, first.useSigterm);\r
7946         }\r
7947         first.pr = NoProc;\r
7948     }\r
7949     if (second.reuse) {\r
7950         /* Put second chess program into idle state */\r
7951         if (second.pr != NoProc &&\r
7952             gameMode == TwoMachinesPlay) {\r
7953             SendToProgram("force\n", &second);\r
7954             if (second.usePing) {\r
7955               char buf[MSG_SIZ];\r
7956               sprintf(buf, "ping %d\n", ++second.lastPing);\r
7957               SendToProgram(buf, &second);\r
7958             }\r
7959         }\r
7960     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
7961         /* Kill off second chess program */\r
7962         if (second.isr != NULL)\r
7963           RemoveInputSource(second.isr);\r
7964         second.isr = NULL;\r
7965     \r
7966         if (second.pr != NoProc) {\r
7967             DoSleep( appData.delayBeforeQuit );\r
7968             SendToProgram("quit\n", &second);\r
7969             DoSleep( appData.delayAfterQuit );\r
7970             DestroyChildProcess(second.pr, second.useSigterm);\r
7971         }\r
7972         second.pr = NoProc;\r
7973     }\r
7974 \r
7975     if (matchMode && gameMode == TwoMachinesPlay) {\r
7976         switch (result) {\r
7977         case WhiteWins:\r
7978           if (first.twoMachinesColor[0] == 'w') {\r
7979             first.matchWins++;\r
7980           } else {\r
7981             second.matchWins++;\r
7982           }\r
7983           break;\r
7984         case BlackWins:\r
7985           if (first.twoMachinesColor[0] == 'b') {\r
7986             first.matchWins++;\r
7987           } else {\r
7988             second.matchWins++;\r
7989           }\r
7990           break;\r
7991         default:\r
7992           break;\r
7993         }\r
7994         if (matchGame < appData.matchGames) {\r
7995             char *tmp;\r
7996             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */\r
7997                 tmp = first.twoMachinesColor;\r
7998                 first.twoMachinesColor = second.twoMachinesColor;\r
7999                 second.twoMachinesColor = tmp;\r
8000             }\r
8001             gameMode = nextGameMode;\r
8002             matchGame++;\r
8003             if(appData.matchPause>10000 || appData.matchPause<10)\r
8004                 appData.matchPause = 10000; /* [HGM] make pause adjustable */\r
8005             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);\r
8006             endingGame = 0; /* [HGM] crash */\r
8007             return;\r
8008         } else {\r
8009             char buf[MSG_SIZ];\r
8010             gameMode = nextGameMode;\r
8011             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),\r
8012                     first.tidy, second.tidy,\r
8013                     first.matchWins, second.matchWins,\r
8014                     appData.matchGames - (first.matchWins + second.matchWins));\r
8015             DisplayFatalError(buf, 0, 0);\r
8016         }\r
8017     }\r
8018     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&\r
8019         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))\r
8020       ExitAnalyzeMode();\r
8021     gameMode = nextGameMode;\r
8022     ModeHighlight();\r
8023     endingGame = 0;  /* [HGM] crash */\r
8024 }\r
8025 \r
8026 /* Assumes program was just initialized (initString sent).\r
8027    Leaves program in force mode. */\r
8028 void\r
8029 FeedMovesToProgram(cps, upto) \r
8030      ChessProgramState *cps;\r
8031      int upto;\r
8032 {\r
8033     int i;\r
8034     \r
8035     if (appData.debugMode)\r
8036       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",\r
8037               startedFromSetupPosition ? "position and " : "",\r
8038               backwardMostMove, upto, cps->which);\r
8039     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];\r
8040         // [HGM] variantswitch: make engine aware of new variant\r
8041         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)\r
8042                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!\r
8043         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));\r
8044         SendToProgram(buf, cps);\r
8045         currentlyInitializedVariant = gameInfo.variant;\r
8046     }\r
8047     SendToProgram("force\n", cps);\r
8048     if (startedFromSetupPosition) {\r
8049         SendBoard(cps, backwardMostMove);\r
8050     if (appData.debugMode) {\r
8051         fprintf(debugFP, "feedMoves\n");\r
8052     }\r
8053     }\r
8054     for (i = backwardMostMove; i < upto; i++) {\r
8055         SendMoveToProgram(i, cps);\r
8056     }\r
8057 }\r
8058 \r
8059 \r
8060 void\r
8061 ResurrectChessProgram()\r
8062 {\r
8063      /* The chess program may have exited.\r
8064         If so, restart it and feed it all the moves made so far. */\r
8065 \r
8066     if (appData.noChessProgram || first.pr != NoProc) return;\r
8067     \r
8068     StartChessProgram(&first);\r
8069     InitChessProgram(&first, FALSE);\r
8070     FeedMovesToProgram(&first, currentMove);\r
8071 \r
8072     if (!first.sendTime) {\r
8073         /* can't tell gnuchess what its clock should read,\r
8074            so we bow to its notion. */\r
8075         ResetClocks();\r
8076         timeRemaining[0][currentMove] = whiteTimeRemaining;\r
8077         timeRemaining[1][currentMove] = blackTimeRemaining;\r
8078     }\r
8079 \r
8080     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||\r
8081                 appData.icsEngineAnalyze) && first.analysisSupport) {\r
8082       SendToProgram("analyze\n", &first);\r
8083       first.analyzing = TRUE;\r
8084     }\r
8085 }\r
8086 \r
8087 /*\r
8088  * Button procedures\r
8089  */\r
8090 void\r
8091 Reset(redraw, init)\r
8092      int redraw, init;\r
8093 {\r
8094     int i;\r
8095 \r
8096     if (appData.debugMode) {\r
8097         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",\r
8098                 redraw, init, gameMode);\r
8099     }\r
8100     pausing = pauseExamInvalid = FALSE;\r
8101     startedFromSetupPosition = blackPlaysFirst = FALSE;\r
8102     firstMove = TRUE;\r
8103     whiteFlag = blackFlag = FALSE;\r
8104     userOfferedDraw = FALSE;\r
8105     hintRequested = bookRequested = FALSE;\r
8106     first.maybeThinking = FALSE;\r
8107     second.maybeThinking = FALSE;\r
8108     first.bookSuspend = FALSE; // [HGM] book\r
8109     second.bookSuspend = FALSE;\r
8110     thinkOutput[0] = NULLCHAR;\r
8111     lastHint[0] = NULLCHAR;\r
8112     ClearGameInfo(&gameInfo);\r
8113     gameInfo.variant = StringToVariant(appData.variant);\r
8114     ics_user_moved = ics_clock_paused = FALSE;\r
8115     ics_getting_history = H_FALSE;\r
8116     ics_gamenum = -1;\r
8117     white_holding[0] = black_holding[0] = NULLCHAR;\r
8118     ClearProgramStats();\r
8119     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode\r
8120     \r
8121     ResetFrontEnd();\r
8122     ClearHighlights();\r
8123     flipView = appData.flipView;\r
8124     ClearPremoveHighlights();\r
8125     gotPremove = FALSE;\r
8126     alarmSounded = FALSE;\r
8127 \r
8128     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
8129     if(appData.serverMovesName != NULL) {\r
8130         /* [HGM] prepare to make moves file for broadcasting */\r
8131         clock_t t = clock();\r
8132         if(serverMoves != NULL) fclose(serverMoves);\r
8133         serverMoves = fopen(appData.serverMovesName, "r");\r
8134         if(serverMoves != NULL) {\r
8135             fclose(serverMoves);\r
8136             /* delay 15 sec before overwriting, so all clients can see end */\r
8137             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);\r
8138         }\r
8139         serverMoves = fopen(appData.serverMovesName, "w");\r
8140     }\r
8141 \r
8142     ExitAnalyzeMode();\r
8143     gameMode = BeginningOfGame;\r
8144     ModeHighlight();\r
8145     if(appData.icsActive) gameInfo.variant = VariantNormal;\r
8146     InitPosition(redraw);\r
8147     for (i = 0; i < MAX_MOVES; i++) {\r
8148         if (commentList[i] != NULL) {\r
8149             free(commentList[i]);\r
8150             commentList[i] = NULL;\r
8151         }\r
8152     }\r
8153     ResetClocks();\r
8154     timeRemaining[0][0] = whiteTimeRemaining;\r
8155     timeRemaining[1][0] = blackTimeRemaining;\r
8156     if (first.pr == NULL) {\r
8157         StartChessProgram(&first);\r
8158     }\r
8159     if (init) {\r
8160             InitChessProgram(&first, startedFromSetupPosition);\r
8161     }\r
8162     DisplayTitle("");\r
8163     DisplayMessage("", "");\r
8164     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
8165 }\r
8166 \r
8167 void\r
8168 AutoPlayGameLoop()\r
8169 {\r
8170     for (;;) {\r
8171         if (!AutoPlayOneMove())\r
8172           return;\r
8173         if (matchMode || appData.timeDelay == 0)\r
8174           continue;\r
8175         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)\r
8176           return;\r
8177         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));\r
8178         break;\r
8179     }\r
8180 }\r
8181 \r
8182 \r
8183 int\r
8184 AutoPlayOneMove()\r
8185 {\r
8186     int fromX, fromY, toX, toY;\r
8187 \r
8188     if (appData.debugMode) {\r
8189       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);\r
8190     }\r
8191 \r
8192     if (gameMode != PlayFromGameFile)\r
8193       return FALSE;\r
8194 \r
8195     if (currentMove >= forwardMostMove) {\r
8196       gameMode = EditGame;\r
8197       ModeHighlight();\r
8198 \r
8199       /* [AS] Clear current move marker at the end of a game */\r
8200       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */\r
8201 \r
8202       return FALSE;\r
8203     }\r
8204     \r
8205     toX = moveList[currentMove][2] - AAA;\r
8206     toY = moveList[currentMove][3] - ONE;\r
8207 \r
8208     if (moveList[currentMove][1] == '@') {\r
8209         if (appData.highlightLastMove) {\r
8210             SetHighlights(-1, -1, toX, toY);\r
8211         }\r
8212     } else {\r
8213         fromX = moveList[currentMove][0] - AAA;\r
8214         fromY = moveList[currentMove][1] - ONE;\r
8215 \r
8216         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */\r
8217 \r
8218         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
8219 \r
8220         if (appData.highlightLastMove) {\r
8221             SetHighlights(fromX, fromY, toX, toY);\r
8222         }\r
8223     }\r
8224     DisplayMove(currentMove);\r
8225     SendMoveToProgram(currentMove++, &first);\r
8226     DisplayBothClocks();\r
8227     DrawPosition(FALSE, boards[currentMove]);\r
8228     // [HGM] PV info: always display, routine tests if empty\r
8229     DisplayComment(currentMove - 1, commentList[currentMove]);\r
8230     return TRUE;\r
8231 }\r
8232 \r
8233 \r
8234 int\r
8235 LoadGameOneMove(readAhead)\r
8236      ChessMove readAhead;\r
8237 {\r
8238     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;\r
8239     char promoChar = NULLCHAR;\r
8240     ChessMove moveType;\r
8241     char move[MSG_SIZ];\r
8242     char *p, *q;\r
8243     \r
8244     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && \r
8245         gameMode != AnalyzeMode && gameMode != Training) {\r
8246         gameFileFP = NULL;\r
8247         return FALSE;\r
8248     }\r
8249     \r
8250     yyboardindex = forwardMostMove;\r
8251     if (readAhead != (ChessMove)0) {\r
8252       moveType = readAhead;\r
8253     } else {\r
8254       if (gameFileFP == NULL)\r
8255           return FALSE;\r
8256       moveType = (ChessMove) yylex();\r
8257     }\r
8258     \r
8259     done = FALSE;\r
8260     switch (moveType) {\r
8261       case Comment:\r
8262         if (appData.debugMode) \r
8263           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
8264         p = yy_text;\r
8265         if (*p == '{' || *p == '[' || *p == '(') {\r
8266             p[strlen(p) - 1] = NULLCHAR;\r
8267             p++;\r
8268         }\r
8269 \r
8270         /* append the comment but don't display it */\r
8271         while (*p == '\n') p++;\r
8272         AppendComment(currentMove, p);\r
8273         return TRUE;\r
8274 \r
8275       case WhiteCapturesEnPassant:\r
8276       case BlackCapturesEnPassant:\r
8277       case WhitePromotionChancellor:\r
8278       case BlackPromotionChancellor:\r
8279       case WhitePromotionArchbishop:\r
8280       case BlackPromotionArchbishop:\r
8281       case WhitePromotionCentaur:\r
8282       case BlackPromotionCentaur:\r
8283       case WhitePromotionQueen:\r
8284       case BlackPromotionQueen:\r
8285       case WhitePromotionRook:\r
8286       case BlackPromotionRook:\r
8287       case WhitePromotionBishop:\r
8288       case BlackPromotionBishop:\r
8289       case WhitePromotionKnight:\r
8290       case BlackPromotionKnight:\r
8291       case WhitePromotionKing:\r
8292       case BlackPromotionKing:\r
8293       case NormalMove:\r
8294       case WhiteKingSideCastle:\r
8295       case WhiteQueenSideCastle:\r
8296       case BlackKingSideCastle:\r
8297       case BlackQueenSideCastle:\r
8298       case WhiteKingSideCastleWild:\r
8299       case WhiteQueenSideCastleWild:\r
8300       case BlackKingSideCastleWild:\r
8301       case BlackQueenSideCastleWild:\r
8302       /* PUSH Fabien */\r
8303       case WhiteHSideCastleFR:\r
8304       case WhiteASideCastleFR:\r
8305       case BlackHSideCastleFR:\r
8306       case BlackASideCastleFR:\r
8307       /* POP Fabien */\r
8308         if (appData.debugMode)\r
8309           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
8310         fromX = currentMoveString[0] - AAA;\r
8311         fromY = currentMoveString[1] - ONE;\r
8312         toX = currentMoveString[2] - AAA;\r
8313         toY = currentMoveString[3] - ONE;\r
8314         promoChar = currentMoveString[4];\r
8315         break;\r
8316 \r
8317       case WhiteDrop:\r
8318       case BlackDrop:\r
8319         if (appData.debugMode)\r
8320           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
8321         fromX = moveType == WhiteDrop ?\r
8322           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
8323         (int) CharToPiece(ToLower(currentMoveString[0]));\r
8324         fromY = DROP_RANK;\r
8325         toX = currentMoveString[2] - AAA;\r
8326         toY = currentMoveString[3] - ONE;\r
8327         break;\r
8328 \r
8329       case WhiteWins:\r
8330       case BlackWins:\r
8331       case GameIsDrawn:\r
8332       case GameUnfinished:\r
8333         if (appData.debugMode)\r
8334           fprintf(debugFP, "Parsed game end: %s\n", yy_text);\r
8335         p = strchr(yy_text, '{');\r
8336         if (p == NULL) p = strchr(yy_text, '(');\r
8337         if (p == NULL) {\r
8338             p = yy_text;\r
8339             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
8340         } else {\r
8341             q = strchr(p, *p == '{' ? '}' : ')');\r
8342             if (q != NULL) *q = NULLCHAR;\r
8343             p++;\r
8344         }\r
8345         GameEnds(moveType, p, GE_FILE);\r
8346         done = TRUE;\r
8347         if (cmailMsgLoaded) {\r
8348             ClearHighlights();\r
8349             flipView = WhiteOnMove(currentMove);\r
8350             if (moveType == GameUnfinished) flipView = !flipView;\r
8351             if (appData.debugMode)\r
8352               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;\r
8353         }\r
8354         break;\r
8355 \r
8356       case (ChessMove) 0:       /* end of file */\r
8357         if (appData.debugMode)\r
8358           fprintf(debugFP, "Parser hit end of file\n");\r
8359         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8360                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8361           case MT_NONE:\r
8362           case MT_CHECK:\r
8363             break;\r
8364           case MT_CHECKMATE:\r
8365             if (WhiteOnMove(currentMove)) {\r
8366                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
8367             } else {\r
8368                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
8369             }\r
8370             break;\r
8371           case MT_STALEMATE:\r
8372             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
8373             break;\r
8374         }\r
8375         done = TRUE;\r
8376         break;\r
8377 \r
8378       case MoveNumberOne:\r
8379         if (lastLoadGameStart == GNUChessGame) {\r
8380             /* GNUChessGames have numbers, but they aren't move numbers */\r
8381             if (appData.debugMode)\r
8382               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
8383                       yy_text, (int) moveType);\r
8384             return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
8385         }\r
8386         /* else fall thru */\r
8387 \r
8388       case XBoardGame:\r
8389       case GNUChessGame:\r
8390       case PGNTag:\r
8391         /* Reached start of next game in file */\r
8392         if (appData.debugMode)\r
8393           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);\r
8394         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8395                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8396           case MT_NONE:\r
8397           case MT_CHECK:\r
8398             break;\r
8399           case MT_CHECKMATE:\r
8400             if (WhiteOnMove(currentMove)) {\r
8401                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
8402             } else {\r
8403                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
8404             }\r
8405             break;\r
8406           case MT_STALEMATE:\r
8407             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
8408             break;\r
8409         }\r
8410         done = TRUE;\r
8411         break;\r
8412 \r
8413       case PositionDiagram:     /* should not happen; ignore */\r
8414       case ElapsedTime:         /* ignore */\r
8415       case NAG:                 /* ignore */\r
8416         if (appData.debugMode)\r
8417           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
8418                   yy_text, (int) moveType);\r
8419         return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
8420 \r
8421       case IllegalMove:\r
8422         if (appData.testLegality) {\r
8423             if (appData.debugMode)\r
8424               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);\r
8425             sprintf(move, _("Illegal move: %d.%s%s"),\r
8426                     (forwardMostMove / 2) + 1,\r
8427                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8428             DisplayError(move, 0);\r
8429             done = TRUE;\r
8430         } else {\r
8431             if (appData.debugMode)\r
8432               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",\r
8433                       yy_text, currentMoveString);\r
8434             fromX = currentMoveString[0] - AAA;\r
8435             fromY = currentMoveString[1] - ONE;\r
8436             toX = currentMoveString[2] - AAA;\r
8437             toY = currentMoveString[3] - ONE;\r
8438             promoChar = currentMoveString[4];\r
8439         }\r
8440         break;\r
8441 \r
8442       case AmbiguousMove:\r
8443         if (appData.debugMode)\r
8444           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);\r
8445         sprintf(move, _("Ambiguous move: %d.%s%s"),\r
8446                 (forwardMostMove / 2) + 1,\r
8447                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8448         DisplayError(move, 0);\r
8449         done = TRUE;\r
8450         break;\r
8451 \r
8452       default:\r
8453       case ImpossibleMove:\r
8454         if (appData.debugMode)\r
8455           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);\r
8456         sprintf(move, _("Illegal move: %d.%s%s"),\r
8457                 (forwardMostMove / 2) + 1,\r
8458                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
8459         DisplayError(move, 0);\r
8460         done = TRUE;\r
8461         break;\r
8462     }\r
8463 \r
8464     if (done) {\r
8465         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {\r
8466             DrawPosition(FALSE, boards[currentMove]);\r
8467             DisplayBothClocks();\r
8468             if (!appData.matchMode) // [HGM] PV info: routine tests if empty\r
8469               DisplayComment(currentMove - 1, commentList[currentMove]);\r
8470         }\r
8471         (void) StopLoadGameTimer();\r
8472         gameFileFP = NULL;\r
8473         cmailOldMove = forwardMostMove;\r
8474         return FALSE;\r
8475     } else {\r
8476         /* currentMoveString is set as a side-effect of yylex */\r
8477         strcat(currentMoveString, "\n");\r
8478         strcpy(moveList[forwardMostMove], currentMoveString);\r
8479         \r
8480         thinkOutput[0] = NULLCHAR;\r
8481         MakeMove(fromX, fromY, toX, toY, promoChar);\r
8482         currentMove = forwardMostMove;\r
8483         return TRUE;\r
8484     }\r
8485 }\r
8486 \r
8487 /* Load the nth game from the given file */\r
8488 int\r
8489 LoadGameFromFile(filename, n, title, useList)\r
8490      char *filename;\r
8491      int n;\r
8492      char *title;\r
8493      /*Boolean*/ int useList;\r
8494 {\r
8495     FILE *f;\r
8496     char buf[MSG_SIZ];\r
8497 \r
8498     if (strcmp(filename, "-") == 0) {\r
8499         f = stdin;\r
8500         title = "stdin";\r
8501     } else {\r
8502         f = fopen(filename, "rb");\r
8503         if (f == NULL) {\r
8504             sprintf(buf, _("Can't open \"%s\""), filename);\r
8505             DisplayError(buf, errno);\r
8506             return FALSE;\r
8507         }\r
8508     }\r
8509     if (fseek(f, 0, 0) == -1) {\r
8510         /* f is not seekable; probably a pipe */\r
8511         useList = FALSE;\r
8512     }\r
8513     if (useList && n == 0) {\r
8514         int error = GameListBuild(f);\r
8515         if (error) {\r
8516             DisplayError(_("Cannot build game list"), error);\r
8517         } else if (!ListEmpty(&gameList) &&\r
8518                    ((ListGame *) gameList.tailPred)->number > 1) {\r
8519             GameListPopUp(f, title);\r
8520             return TRUE;\r
8521         }\r
8522         GameListDestroy();\r
8523         n = 1;\r
8524     }\r
8525     if (n == 0) n = 1;\r
8526     return LoadGame(f, n, title, FALSE);\r
8527 }\r
8528 \r
8529 \r
8530 void\r
8531 MakeRegisteredMove()\r
8532 {\r
8533     int fromX, fromY, toX, toY;\r
8534     char promoChar;\r
8535     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8536         switch (cmailMoveType[lastLoadGameNumber - 1]) {\r
8537           case CMAIL_MOVE:\r
8538           case CMAIL_DRAW:\r
8539             if (appData.debugMode)\r
8540               fprintf(debugFP, "Restoring %s for game %d\n",\r
8541                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
8542     \r
8543             thinkOutput[0] = NULLCHAR;\r
8544             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);\r
8545             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;\r
8546             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;\r
8547             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;\r
8548             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;\r
8549             promoChar = cmailMove[lastLoadGameNumber - 1][4];\r
8550             MakeMove(fromX, fromY, toX, toY, promoChar);\r
8551             ShowMove(fromX, fromY, toX, toY);\r
8552               \r
8553             switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8554                              EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8555               case MT_NONE:\r
8556               case MT_CHECK:\r
8557                 break;\r
8558                 \r
8559               case MT_CHECKMATE:\r
8560                 if (WhiteOnMove(currentMove)) {\r
8561                     GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
8562                 } else {\r
8563                     GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
8564                 }\r
8565                 break;\r
8566                 \r
8567               case MT_STALEMATE:\r
8568                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
8569                 break;\r
8570             }\r
8571 \r
8572             break;\r
8573             \r
8574           case CMAIL_RESIGN:\r
8575             if (WhiteOnMove(currentMove)) {\r
8576                 GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
8577             } else {\r
8578                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
8579             }\r
8580             break;\r
8581             \r
8582           case CMAIL_ACCEPT:\r
8583             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
8584             break;\r
8585               \r
8586           default:\r
8587             break;\r
8588         }\r
8589     }\r
8590 \r
8591     return;\r
8592 }\r
8593 \r
8594 /* Wrapper around LoadGame for use when a Cmail message is loaded */\r
8595 int\r
8596 CmailLoadGame(f, gameNumber, title, useList)\r
8597      FILE *f;\r
8598      int gameNumber;\r
8599      char *title;\r
8600      int useList;\r
8601 {\r
8602     int retVal;\r
8603 \r
8604     if (gameNumber > nCmailGames) {\r
8605         DisplayError(_("No more games in this message"), 0);\r
8606         return FALSE;\r
8607     }\r
8608     if (f == lastLoadGameFP) {\r
8609         int offset = gameNumber - lastLoadGameNumber;\r
8610         if (offset == 0) {\r
8611             cmailMsg[0] = NULLCHAR;\r
8612             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8613                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
8614                 nCmailMovesRegistered--;\r
8615             }\r
8616             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
8617             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {\r
8618                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;\r
8619             }\r
8620         } else {\r
8621             if (! RegisterMove()) return FALSE;\r
8622         }\r
8623     }\r
8624 \r
8625     retVal = LoadGame(f, gameNumber, title, useList);\r
8626 \r
8627     /* Make move registered during previous look at this game, if any */\r
8628     MakeRegisteredMove();\r
8629 \r
8630     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {\r
8631         commentList[currentMove]\r
8632           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);\r
8633         DisplayComment(currentMove - 1, commentList[currentMove]);\r
8634     }\r
8635 \r
8636     return retVal;\r
8637 }\r
8638 \r
8639 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */\r
8640 int\r
8641 ReloadGame(offset)\r
8642      int offset;\r
8643 {\r
8644     int gameNumber = lastLoadGameNumber + offset;\r
8645     if (lastLoadGameFP == NULL) {\r
8646         DisplayError(_("No game has been loaded yet"), 0);\r
8647         return FALSE;\r
8648     }\r
8649     if (gameNumber <= 0) {\r
8650         DisplayError(_("Can't back up any further"), 0);\r
8651         return FALSE;\r
8652     }\r
8653     if (cmailMsgLoaded) {\r
8654         return CmailLoadGame(lastLoadGameFP, gameNumber,\r
8655                              lastLoadGameTitle, lastLoadGameUseList);\r
8656     } else {\r
8657         return LoadGame(lastLoadGameFP, gameNumber,\r
8658                         lastLoadGameTitle, lastLoadGameUseList);\r
8659     }\r
8660 }\r
8661 \r
8662 \r
8663 \r
8664 /* Load the nth game from open file f */\r
8665 int\r
8666 LoadGame(f, gameNumber, title, useList)\r
8667      FILE *f;\r
8668      int gameNumber;\r
8669      char *title;\r
8670      int useList;\r
8671 {\r
8672     ChessMove cm;\r
8673     char buf[MSG_SIZ];\r
8674     int gn = gameNumber;\r
8675     ListGame *lg = NULL;\r
8676     int numPGNTags = 0;\r
8677     int err;\r
8678     GameMode oldGameMode;\r
8679     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */\r
8680 \r
8681     if (appData.debugMode) \r
8682         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);\r
8683 \r
8684     if (gameMode == Training )\r
8685         SetTrainingModeOff();\r
8686 \r
8687     oldGameMode = gameMode;\r
8688     if (gameMode != BeginningOfGame) {\r
8689       Reset(FALSE, TRUE);\r
8690     }\r
8691 \r
8692     gameFileFP = f;\r
8693     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {\r
8694         fclose(lastLoadGameFP);\r
8695     }\r
8696 \r
8697     if (useList) {\r
8698         lg = (ListGame *) ListElem(&gameList, gameNumber-1);\r
8699         \r
8700         if (lg) {\r
8701             fseek(f, lg->offset, 0);\r
8702             GameListHighlight(gameNumber);\r
8703             gn = 1;\r
8704         }\r
8705         else {\r
8706             DisplayError(_("Game number out of range"), 0);\r
8707             return FALSE;\r
8708         }\r
8709     } else {\r
8710         GameListDestroy();\r
8711         if (fseek(f, 0, 0) == -1) {\r
8712             if (f == lastLoadGameFP ?\r
8713                 gameNumber == lastLoadGameNumber + 1 :\r
8714                 gameNumber == 1) {\r
8715                 gn = 1;\r
8716             } else {\r
8717                 DisplayError(_("Can't seek on game file"), 0);\r
8718                 return FALSE;\r
8719             }\r
8720         }\r
8721     }\r
8722     lastLoadGameFP = f;\r
8723     lastLoadGameNumber = gameNumber;\r
8724     strcpy(lastLoadGameTitle, title);\r
8725     lastLoadGameUseList = useList;\r
8726 \r
8727     yynewfile(f);\r
8728 \r
8729     if (lg && lg->gameInfo.white && lg->gameInfo.black) {\r
8730         sprintf(buf, "%s vs. %s", lg->gameInfo.white,\r
8731                 lg->gameInfo.black);\r
8732             DisplayTitle(buf);\r
8733     } else if (*title != NULLCHAR) {\r
8734         if (gameNumber > 1) {\r
8735             sprintf(buf, "%s %d", title, gameNumber);\r
8736             DisplayTitle(buf);\r
8737         } else {\r
8738             DisplayTitle(title);\r
8739         }\r
8740     }\r
8741 \r
8742     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {\r
8743         gameMode = PlayFromGameFile;\r
8744         ModeHighlight();\r
8745     }\r
8746 \r
8747     currentMove = forwardMostMove = backwardMostMove = 0;\r
8748     CopyBoard(boards[0], initialPosition);\r
8749     StopClocks();\r
8750 \r
8751     /*\r
8752      * Skip the first gn-1 games in the file.\r
8753      * Also skip over anything that precedes an identifiable \r
8754      * start of game marker, to avoid being confused by \r
8755      * garbage at the start of the file.  Currently \r
8756      * recognized start of game markers are the move number "1",\r
8757      * the pattern "gnuchess .* game", the pattern\r
8758      * "^[#;%] [^ ]* game file", and a PGN tag block.  \r
8759      * A game that starts with one of the latter two patterns\r
8760      * will also have a move number 1, possibly\r
8761      * following a position diagram.\r
8762      * 5-4-02: Let's try being more lenient and allowing a game to\r
8763      * start with an unnumbered move.  Does that break anything?\r
8764      */\r
8765     cm = lastLoadGameStart = (ChessMove) 0;\r
8766     while (gn > 0) {\r
8767         yyboardindex = forwardMostMove;\r
8768         cm = (ChessMove) yylex();\r
8769         switch (cm) {\r
8770           case (ChessMove) 0:\r
8771             if (cmailMsgLoaded) {\r
8772                 nCmailGames = CMAIL_MAX_GAMES - gn;\r
8773             } else {\r
8774                 Reset(TRUE, TRUE);\r
8775                 DisplayError(_("Game not found in file"), 0);\r
8776             }\r
8777             return FALSE;\r
8778 \r
8779           case GNUChessGame:\r
8780           case XBoardGame:\r
8781             gn--;\r
8782             lastLoadGameStart = cm;\r
8783             break;\r
8784             \r
8785           case MoveNumberOne:\r
8786             switch (lastLoadGameStart) {\r
8787               case GNUChessGame:\r
8788               case XBoardGame:\r
8789               case PGNTag:\r
8790                 break;\r
8791               case MoveNumberOne:\r
8792               case (ChessMove) 0:\r
8793                 gn--;           /* count this game */\r
8794                 lastLoadGameStart = cm;\r
8795                 break;\r
8796               default:\r
8797                 /* impossible */\r
8798                 break;\r
8799             }\r
8800             break;\r
8801 \r
8802           case PGNTag:\r
8803             switch (lastLoadGameStart) {\r
8804               case GNUChessGame:\r
8805               case PGNTag:\r
8806               case MoveNumberOne:\r
8807               case (ChessMove) 0:\r
8808                 gn--;           /* count this game */\r
8809                 lastLoadGameStart = cm;\r
8810                 break;\r
8811               case XBoardGame:\r
8812                 lastLoadGameStart = cm; /* game counted already */\r
8813                 break;\r
8814               default:\r
8815                 /* impossible */\r
8816                 break;\r
8817             }\r
8818             if (gn > 0) {\r
8819                 do {\r
8820                     yyboardindex = forwardMostMove;\r
8821                     cm = (ChessMove) yylex();\r
8822                 } while (cm == PGNTag || cm == Comment);\r
8823             }\r
8824             break;\r
8825 \r
8826           case WhiteWins:\r
8827           case BlackWins:\r
8828           case GameIsDrawn:\r
8829             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {\r
8830                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]\r
8831                     != CMAIL_OLD_RESULT) {\r
8832                     nCmailResults ++ ;\r
8833                     cmailResult[  CMAIL_MAX_GAMES\r
8834                                 - gn - 1] = CMAIL_OLD_RESULT;\r
8835                 }\r
8836             }\r
8837             break;\r
8838 \r
8839           case NormalMove:\r
8840             /* Only a NormalMove can be at the start of a game\r
8841              * without a position diagram. */\r
8842             if (lastLoadGameStart == (ChessMove) 0) {\r
8843               gn--;\r
8844               lastLoadGameStart = MoveNumberOne;\r
8845             }\r
8846             break;\r
8847 \r
8848           default:\r
8849             break;\r
8850         }\r
8851     }\r
8852     \r
8853     if (appData.debugMode)\r
8854       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);\r
8855 \r
8856     if (cm == XBoardGame) {\r
8857         /* Skip any header junk before position diagram and/or move 1 */\r
8858         for (;;) {\r
8859             yyboardindex = forwardMostMove;\r
8860             cm = (ChessMove) yylex();\r
8861 \r
8862             if (cm == (ChessMove) 0 ||\r
8863                 cm == GNUChessGame || cm == XBoardGame) {\r
8864                 /* Empty game; pretend end-of-file and handle later */\r
8865                 cm = (ChessMove) 0;\r
8866                 break;\r
8867             }\r
8868 \r
8869             if (cm == MoveNumberOne || cm == PositionDiagram ||\r
8870                 cm == PGNTag || cm == Comment)\r
8871               break;\r
8872         }\r
8873     } else if (cm == GNUChessGame) {\r
8874         if (gameInfo.event != NULL) {\r
8875             free(gameInfo.event);\r
8876         }\r
8877         gameInfo.event = StrSave(yy_text);\r
8878     }   \r
8879 \r
8880     startedFromSetupPosition = FALSE;\r
8881     while (cm == PGNTag) {\r
8882         if (appData.debugMode) \r
8883           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);\r
8884         err = ParsePGNTag(yy_text, &gameInfo);\r
8885         if (!err) numPGNTags++;\r
8886 \r
8887         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */\r
8888         if(gameInfo.variant != oldVariant) {\r
8889             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */\r
8890             InitPosition(TRUE);\r
8891             oldVariant = gameInfo.variant;\r
8892             if (appData.debugMode) \r
8893               fprintf(debugFP, "New variant %d\n", (int) oldVariant);\r
8894         }\r
8895 \r
8896 \r
8897         if (gameInfo.fen != NULL) {\r
8898           Board initial_position;\r
8899           startedFromSetupPosition = TRUE;\r
8900           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {\r
8901             Reset(TRUE, TRUE);\r
8902             DisplayError(_("Bad FEN position in file"), 0);\r
8903             return FALSE;\r
8904           }\r
8905           CopyBoard(boards[0], initial_position);\r
8906           if (blackPlaysFirst) {\r
8907             currentMove = forwardMostMove = backwardMostMove = 1;\r
8908             CopyBoard(boards[1], initial_position);\r
8909             strcpy(moveList[0], "");\r
8910             strcpy(parseList[0], "");\r
8911             timeRemaining[0][1] = whiteTimeRemaining;\r
8912             timeRemaining[1][1] = blackTimeRemaining;\r
8913             if (commentList[0] != NULL) {\r
8914               commentList[1] = commentList[0];\r
8915               commentList[0] = NULL;\r
8916             }\r
8917           } else {\r
8918             currentMove = forwardMostMove = backwardMostMove = 0;\r
8919           }\r
8920           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */\r
8921           {   int i;\r
8922               initialRulePlies = FENrulePlies;\r
8923               epStatus[forwardMostMove] = FENepStatus;\r
8924               for( i=0; i< nrCastlingRights; i++ )\r
8925                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];\r
8926           }\r
8927           yyboardindex = forwardMostMove;\r
8928           free(gameInfo.fen);\r
8929           gameInfo.fen = NULL;\r
8930         }\r
8931 \r
8932         yyboardindex = forwardMostMove;\r
8933         cm = (ChessMove) yylex();\r
8934 \r
8935         /* Handle comments interspersed among the tags */\r
8936         while (cm == Comment) {\r
8937             char *p;\r
8938             if (appData.debugMode) \r
8939               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
8940             p = yy_text;\r
8941             if (*p == '{' || *p == '[' || *p == '(') {\r
8942                 p[strlen(p) - 1] = NULLCHAR;\r
8943                 p++;\r
8944             }\r
8945             while (*p == '\n') p++;\r
8946             AppendComment(currentMove, p);\r
8947             yyboardindex = forwardMostMove;\r
8948             cm = (ChessMove) yylex();\r
8949         }\r
8950     }\r
8951 \r
8952     /* don't rely on existence of Event tag since if game was\r
8953      * pasted from clipboard the Event tag may not exist\r
8954      */\r
8955     if (numPGNTags > 0){\r
8956         char *tags;\r
8957         if (gameInfo.variant == VariantNormal) {\r
8958           gameInfo.variant = StringToVariant(gameInfo.event);\r
8959         }\r
8960         if (!matchMode) {\r
8961           if( appData.autoDisplayTags ) {\r
8962             tags = PGNTags(&gameInfo);\r
8963             TagsPopUp(tags, CmailMsg());\r
8964             free(tags);\r
8965           }\r
8966         }\r
8967     } else {\r
8968         /* Make something up, but don't display it now */\r
8969         SetGameInfo();\r
8970         TagsPopDown();\r
8971     }\r
8972 \r
8973     if (cm == PositionDiagram) {\r
8974         int i, j;\r
8975         char *p;\r
8976         Board initial_position;\r
8977 \r
8978         if (appData.debugMode)\r
8979           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);\r
8980 \r
8981         if (!startedFromSetupPosition) {\r
8982             p = yy_text;\r
8983             for (i = BOARD_HEIGHT - 1; i >= 0; i--)\r
8984               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)\r
8985                 switch (*p) {\r
8986                   case '[':\r
8987                   case '-':\r
8988                   case ' ':\r
8989                   case '\t':\r
8990                   case '\n':\r
8991                   case '\r':\r
8992                     break;\r
8993                   default:\r
8994                     initial_position[i][j++] = CharToPiece(*p);\r
8995                     break;\r
8996                 }\r
8997             while (*p == ' ' || *p == '\t' ||\r
8998                    *p == '\n' || *p == '\r') p++;\r
8999         \r
9000             if (strncmp(p, "black", strlen("black"))==0)\r
9001               blackPlaysFirst = TRUE;\r
9002             else\r
9003               blackPlaysFirst = FALSE;\r
9004             startedFromSetupPosition = TRUE;\r
9005         \r
9006             CopyBoard(boards[0], initial_position);\r
9007             if (blackPlaysFirst) {\r
9008                 currentMove = forwardMostMove = backwardMostMove = 1;\r
9009                 CopyBoard(boards[1], initial_position);\r
9010                 strcpy(moveList[0], "");\r
9011                 strcpy(parseList[0], "");\r
9012                 timeRemaining[0][1] = whiteTimeRemaining;\r
9013                 timeRemaining[1][1] = blackTimeRemaining;\r
9014                 if (commentList[0] != NULL) {\r
9015                     commentList[1] = commentList[0];\r
9016                     commentList[0] = NULL;\r
9017                 }\r
9018             } else {\r
9019                 currentMove = forwardMostMove = backwardMostMove = 0;\r
9020             }\r
9021         }\r
9022         yyboardindex = forwardMostMove;\r
9023         cm = (ChessMove) yylex();\r
9024     }\r
9025 \r
9026     if (first.pr == NoProc) {\r
9027         StartChessProgram(&first);\r
9028     }\r
9029     InitChessProgram(&first, FALSE);\r
9030     SendToProgram("force\n", &first);\r
9031     if (startedFromSetupPosition) {\r
9032         SendBoard(&first, forwardMostMove);\r
9033     if (appData.debugMode) {\r
9034         fprintf(debugFP, "Load Game\n");\r
9035     }\r
9036         DisplayBothClocks();\r
9037     }      \r
9038 \r
9039     /* [HGM] server: flag to write setup moves in broadcast file as one */\r
9040     loadFlag = appData.suppressLoadMoves;\r
9041 \r
9042     while (cm == Comment) {\r
9043         char *p;\r
9044         if (appData.debugMode) \r
9045           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
9046         p = yy_text;\r
9047         if (*p == '{' || *p == '[' || *p == '(') {\r
9048             p[strlen(p) - 1] = NULLCHAR;\r
9049             p++;\r
9050         }\r
9051         while (*p == '\n') p++;\r
9052         AppendComment(currentMove, p);\r
9053         yyboardindex = forwardMostMove;\r
9054         cm = (ChessMove) yylex();\r
9055     }\r
9056 \r
9057     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||\r
9058         cm == WhiteWins || cm == BlackWins ||\r
9059         cm == GameIsDrawn || cm == GameUnfinished) {\r
9060         DisplayMessage("", _("No moves in game"));\r
9061         if (cmailMsgLoaded) {\r
9062             if (appData.debugMode)\r
9063               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);\r
9064             ClearHighlights();\r
9065             flipView = FALSE;\r
9066         }\r
9067         DrawPosition(FALSE, boards[currentMove]);\r
9068         DisplayBothClocks();\r
9069         gameMode = EditGame;\r
9070         ModeHighlight();\r
9071         gameFileFP = NULL;\r
9072         cmailOldMove = 0;\r
9073         return TRUE;\r
9074     }\r
9075 \r
9076     // [HGM] PV info: routine tests if comment empty\r
9077     if (!matchMode && (pausing || appData.timeDelay != 0)) {\r
9078         DisplayComment(currentMove - 1, commentList[currentMove]);\r
9079     }\r
9080     if (!matchMode && appData.timeDelay != 0) \r
9081       DrawPosition(FALSE, boards[currentMove]);\r
9082 \r
9083     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {\r
9084       programStats.ok_to_send = 1;\r
9085     }\r
9086 \r
9087     /* if the first token after the PGN tags is a move\r
9088      * and not move number 1, retrieve it from the parser \r
9089      */\r
9090     if (cm != MoveNumberOne)\r
9091         LoadGameOneMove(cm);\r
9092 \r
9093     /* load the remaining moves from the file */\r
9094     while (LoadGameOneMove((ChessMove)0)) {\r
9095       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
9096       timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
9097     }\r
9098 \r
9099     /* rewind to the start of the game */\r
9100     currentMove = backwardMostMove;\r
9101 \r
9102     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
9103 \r
9104     if (oldGameMode == AnalyzeFile ||\r
9105         oldGameMode == AnalyzeMode) {\r
9106       AnalyzeFileEvent();\r
9107     }\r
9108 \r
9109     if (matchMode || appData.timeDelay == 0) {\r
9110       ToEndEvent();\r
9111       gameMode = EditGame;\r
9112       ModeHighlight();\r
9113     } else if (appData.timeDelay > 0) {\r
9114       AutoPlayGameLoop();\r
9115     }\r
9116 \r
9117     if (appData.debugMode) \r
9118         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);\r
9119 \r
9120     loadFlag = 0; /* [HGM] true game starts */\r
9121     return TRUE;\r
9122 }\r
9123 \r
9124 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */\r
9125 int\r
9126 ReloadPosition(offset)\r
9127      int offset;\r
9128 {\r
9129     int positionNumber = lastLoadPositionNumber + offset;\r
9130     if (lastLoadPositionFP == NULL) {\r
9131         DisplayError(_("No position has been loaded yet"), 0);\r
9132         return FALSE;\r
9133     }\r
9134     if (positionNumber <= 0) {\r
9135         DisplayError(_("Can't back up any further"), 0);\r
9136         return FALSE;\r
9137     }\r
9138     return LoadPosition(lastLoadPositionFP, positionNumber,\r
9139                         lastLoadPositionTitle);\r
9140 }\r
9141 \r
9142 /* Load the nth position from the given file */\r
9143 int\r
9144 LoadPositionFromFile(filename, n, title)\r
9145      char *filename;\r
9146      int n;\r
9147      char *title;\r
9148 {\r
9149     FILE *f;\r
9150     char buf[MSG_SIZ];\r
9151 \r
9152     if (strcmp(filename, "-") == 0) {\r
9153         return LoadPosition(stdin, n, "stdin");\r
9154     } else {\r
9155         f = fopen(filename, "rb");\r
9156         if (f == NULL) {\r
9157             sprintf(buf, _("Can't open \"%s\""), filename);\r
9158             DisplayError(buf, errno);\r
9159             return FALSE;\r
9160         } else {\r
9161             return LoadPosition(f, n, title);\r
9162         }\r
9163     }\r
9164 }\r
9165 \r
9166 /* Load the nth position from the given open file, and close it */\r
9167 int\r
9168 LoadPosition(f, positionNumber, title)\r
9169      FILE *f;\r
9170      int positionNumber;\r
9171      char *title;\r
9172 {\r
9173     char *p, line[MSG_SIZ];\r
9174     Board initial_position;\r
9175     int i, j, fenMode, pn;\r
9176     \r
9177     if (gameMode == Training )\r
9178         SetTrainingModeOff();\r
9179 \r
9180     if (gameMode != BeginningOfGame) {\r
9181         Reset(FALSE, TRUE);\r
9182     }\r
9183     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {\r
9184         fclose(lastLoadPositionFP);\r
9185     }\r
9186     if (positionNumber == 0) positionNumber = 1;\r
9187     lastLoadPositionFP = f;\r
9188     lastLoadPositionNumber = positionNumber;\r
9189     strcpy(lastLoadPositionTitle, title);\r
9190     if (first.pr == NoProc) {\r
9191       StartChessProgram(&first);\r
9192       InitChessProgram(&first, FALSE);\r
9193     }    \r
9194     pn = positionNumber;\r
9195     if (positionNumber < 0) {\r
9196         /* Negative position number means to seek to that byte offset */\r
9197         if (fseek(f, -positionNumber, 0) == -1) {\r
9198             DisplayError(_("Can't seek on position file"), 0);\r
9199             return FALSE;\r
9200         };\r
9201         pn = 1;\r
9202     } else {\r
9203         if (fseek(f, 0, 0) == -1) {\r
9204             if (f == lastLoadPositionFP ?\r
9205                 positionNumber == lastLoadPositionNumber + 1 :\r
9206                 positionNumber == 1) {\r
9207                 pn = 1;\r
9208             } else {\r
9209                 DisplayError(_("Can't seek on position file"), 0);\r
9210                 return FALSE;\r
9211             }\r
9212         }\r
9213     }\r
9214     /* See if this file is FEN or old-style xboard */\r
9215     if (fgets(line, MSG_SIZ, f) == NULL) {\r
9216         DisplayError(_("Position not found in file"), 0);\r
9217         return FALSE;\r
9218     }\r
9219 #if 0\r
9220     switch (line[0]) {\r
9221       case '#':  case 'x':\r
9222       default:\r
9223         fenMode = FALSE;\r
9224         break;\r
9225       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':\r
9226       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':\r
9227       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':\r
9228       case '7':  case '8':  case '9':\r
9229       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':\r
9230       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':\r
9231       case 'C':  case 'W':             case 'c':  case 'w': \r
9232         fenMode = TRUE;\r
9233         break;\r
9234     }\r
9235 #else\r
9236     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces\r
9237     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;\r
9238 #endif\r
9239 \r
9240     if (pn >= 2) {\r
9241         if (fenMode || line[0] == '#') pn--;\r
9242         while (pn > 0) {\r
9243             /* skip positions before number pn */\r
9244             if (fgets(line, MSG_SIZ, f) == NULL) {\r
9245                 Reset(TRUE, TRUE);\r
9246                 DisplayError(_("Position not found in file"), 0);\r
9247                 return FALSE;\r
9248             }\r
9249             if (fenMode || line[0] == '#') pn--;\r
9250         }\r
9251     }\r
9252 \r
9253     if (fenMode) {\r
9254         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {\r
9255             DisplayError(_("Bad FEN position in file"), 0);\r
9256             return FALSE;\r
9257         }\r
9258     } else {\r
9259         (void) fgets(line, MSG_SIZ, f);\r
9260         (void) fgets(line, MSG_SIZ, f);\r
9261     \r
9262         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
9263             (void) fgets(line, MSG_SIZ, f);\r
9264             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {\r
9265                 if (*p == ' ')\r
9266                   continue;\r
9267                 initial_position[i][j++] = CharToPiece(*p);\r
9268             }\r
9269         }\r
9270     \r
9271         blackPlaysFirst = FALSE;\r
9272         if (!feof(f)) {\r
9273             (void) fgets(line, MSG_SIZ, f);\r
9274             if (strncmp(line, "black", strlen("black"))==0)\r
9275               blackPlaysFirst = TRUE;\r
9276         }\r
9277     }\r
9278     startedFromSetupPosition = TRUE;\r
9279     \r
9280     SendToProgram("force\n", &first);\r
9281     CopyBoard(boards[0], initial_position);\r
9282     if (blackPlaysFirst) {\r
9283         currentMove = forwardMostMove = backwardMostMove = 1;\r
9284         strcpy(moveList[0], "");\r
9285         strcpy(parseList[0], "");\r
9286         CopyBoard(boards[1], initial_position);\r
9287         DisplayMessage("", _("Black to play"));\r
9288     } else {\r
9289         currentMove = forwardMostMove = backwardMostMove = 0;\r
9290         DisplayMessage("", _("White to play"));\r
9291     }\r
9292           /* [HGM] copy FEN attributes as well */\r
9293           {   int i;\r
9294               initialRulePlies = FENrulePlies;\r
9295               epStatus[forwardMostMove] = FENepStatus;\r
9296               for( i=0; i< nrCastlingRights; i++ )\r
9297                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];\r
9298           }\r
9299     SendBoard(&first, forwardMostMove);\r
9300     if (appData.debugMode) {\r
9301 int i, j;\r
9302   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}\r
9303   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");\r
9304         fprintf(debugFP, "Load Position\n");\r
9305     }\r
9306 \r
9307     if (positionNumber > 1) {\r
9308         sprintf(line, "%s %d", title, positionNumber);\r
9309         DisplayTitle(line);\r
9310     } else {\r
9311         DisplayTitle(title);\r
9312     }\r
9313     gameMode = EditGame;\r
9314     ModeHighlight();\r
9315     ResetClocks();\r
9316     timeRemaining[0][1] = whiteTimeRemaining;\r
9317     timeRemaining[1][1] = blackTimeRemaining;\r
9318     DrawPosition(FALSE, boards[currentMove]);\r
9319    \r
9320     return TRUE;\r
9321 }\r
9322 \r
9323 \r
9324 void\r
9325 CopyPlayerNameIntoFileName(dest, src)\r
9326      char **dest, *src;\r
9327 {\r
9328     while (*src != NULLCHAR && *src != ',') {\r
9329         if (*src == ' ') {\r
9330             *(*dest)++ = '_';\r
9331             src++;\r
9332         } else {\r
9333             *(*dest)++ = *src++;\r
9334         }\r
9335     }\r
9336 }\r
9337 \r
9338 char *DefaultFileName(ext)\r
9339      char *ext;\r
9340 {\r
9341     static char def[MSG_SIZ];\r
9342     char *p;\r
9343 \r
9344     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {\r
9345         p = def;\r
9346         CopyPlayerNameIntoFileName(&p, gameInfo.white);\r
9347         *p++ = '-';\r
9348         CopyPlayerNameIntoFileName(&p, gameInfo.black);\r
9349         *p++ = '.';\r
9350         strcpy(p, ext);\r
9351     } else {\r
9352         def[0] = NULLCHAR;\r
9353     }\r
9354     return def;\r
9355 }\r
9356 \r
9357 /* Save the current game to the given file */\r
9358 int\r
9359 SaveGameToFile(filename, append)\r
9360      char *filename;\r
9361      int append;\r
9362 {\r
9363     FILE *f;\r
9364     char buf[MSG_SIZ];\r
9365 \r
9366     if (strcmp(filename, "-") == 0) {\r
9367         return SaveGame(stdout, 0, NULL);\r
9368     } else {\r
9369         f = fopen(filename, append ? "a" : "w");\r
9370         if (f == NULL) {\r
9371             sprintf(buf, _("Can't open \"%s\""), filename);\r
9372             DisplayError(buf, errno);\r
9373             return FALSE;\r
9374         } else {\r
9375             return SaveGame(f, 0, NULL);\r
9376         }\r
9377     }\r
9378 }\r
9379 \r
9380 char *\r
9381 SavePart(str)\r
9382      char *str;\r
9383 {\r
9384     static char buf[MSG_SIZ];\r
9385     char *p;\r
9386     \r
9387     p = strchr(str, ' ');\r
9388     if (p == NULL) return str;\r
9389     strncpy(buf, str, p - str);\r
9390     buf[p - str] = NULLCHAR;\r
9391     return buf;\r
9392 }\r
9393 \r
9394 #define PGN_MAX_LINE 75\r
9395 \r
9396 #define PGN_SIDE_WHITE  0\r
9397 #define PGN_SIDE_BLACK  1\r
9398 \r
9399 /* [AS] */\r
9400 static int FindFirstMoveOutOfBook( int side )\r
9401 {\r
9402     int result = -1;\r
9403 \r
9404     if( backwardMostMove == 0 && ! startedFromSetupPosition) {\r
9405         int index = backwardMostMove;\r
9406         int has_book_hit = 0;\r
9407 \r
9408         if( (index % 2) != side ) {\r
9409             index++;\r
9410         }\r
9411 \r
9412         while( index < forwardMostMove ) {\r
9413             /* Check to see if engine is in book */\r
9414             int depth = pvInfoList[index].depth;\r
9415             int score = pvInfoList[index].score;\r
9416             int in_book = 0;\r
9417 \r
9418             if( depth <= 2 ) {\r
9419                 in_book = 1;\r
9420             }\r
9421             else if( score == 0 && depth == 63 ) {\r
9422                 in_book = 1; /* Zappa */\r
9423             }\r
9424             else if( score == 2 && depth == 99 ) {\r
9425                 in_book = 1; /* Abrok */\r
9426             }\r
9427 \r
9428             has_book_hit += in_book;\r
9429 \r
9430             if( ! in_book ) {\r
9431                 result = index;\r
9432 \r
9433                 break;\r
9434             }\r
9435 \r
9436             index += 2;\r
9437         }\r
9438     }\r
9439 \r
9440     return result;\r
9441 }\r
9442 \r
9443 /* [AS] */\r
9444 void GetOutOfBookInfo( char * buf )\r
9445 {\r
9446     int oob[2];\r
9447     int i;\r
9448     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9449 \r
9450     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );\r
9451     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );\r
9452 \r
9453     *buf = '\0';\r
9454 \r
9455     if( oob[0] >= 0 || oob[1] >= 0 ) {\r
9456         for( i=0; i<2; i++ ) {\r
9457             int idx = oob[i];\r
9458 \r
9459             if( idx >= 0 ) {\r
9460                 if( i > 0 && oob[0] >= 0 ) {\r
9461                     strcat( buf, "   " );\r
9462                 }\r
9463 \r
9464                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );\r
9465                 sprintf( buf+strlen(buf), "%s%.2f", \r
9466                     pvInfoList[idx].score >= 0 ? "+" : "",\r
9467                     pvInfoList[idx].score / 100.0 );\r
9468             }\r
9469         }\r
9470     }\r
9471 }\r
9472 \r
9473 /* Save game in PGN style and close the file */\r
9474 int\r
9475 SaveGamePGN(f)\r
9476      FILE *f;\r
9477 {\r
9478     int i, offset, linelen, newblock;\r
9479     time_t tm;\r
9480 //    char *movetext;\r
9481     char numtext[32];\r
9482     int movelen, numlen, blank;\r
9483     char move_buffer[100]; /* [AS] Buffer for move+PV info */\r
9484 \r
9485     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9486     \r
9487     tm = time((time_t *) NULL);\r
9488     \r
9489     PrintPGNTags(f, &gameInfo);\r
9490     \r
9491     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
9492         char *fen = PositionToFEN(backwardMostMove, NULL);\r
9493         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);\r
9494         fprintf(f, "\n{--------------\n");\r
9495         PrintPosition(f, backwardMostMove);\r
9496         fprintf(f, "--------------}\n");\r
9497         free(fen);\r
9498     }\r
9499     else {\r
9500         /* [AS] Out of book annotation */\r
9501         if( appData.saveOutOfBookInfo ) {\r
9502             char buf[64];\r
9503 \r
9504             GetOutOfBookInfo( buf );\r
9505 \r
9506             if( buf[0] != '\0' ) {\r
9507                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); \r
9508             }\r
9509         }\r
9510 \r
9511         fprintf(f, "\n");\r
9512     }\r
9513 \r
9514     i = backwardMostMove;\r
9515     linelen = 0;\r
9516     newblock = TRUE;\r
9517 \r
9518     while (i < forwardMostMove) {\r
9519         /* Print comments preceding this move */\r
9520         if (commentList[i] != NULL) {\r
9521             if (linelen > 0) fprintf(f, "\n");\r
9522             fprintf(f, "{\n%s}\n", commentList[i]);\r
9523             linelen = 0;\r
9524             newblock = TRUE;\r
9525         }\r
9526 \r
9527         /* Format move number */\r
9528         if ((i % 2) == 0) {\r
9529             sprintf(numtext, "%d.", (i - offset)/2 + 1);\r
9530         } else {\r
9531             if (newblock) {\r
9532                 sprintf(numtext, "%d...", (i - offset)/2 + 1);\r
9533             } else {\r
9534                 numtext[0] = NULLCHAR;\r
9535             }\r
9536         }\r
9537         numlen = strlen(numtext);\r
9538         newblock = FALSE;\r
9539 \r
9540         /* Print move number */\r
9541         blank = linelen > 0 && numlen > 0;\r
9542         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {\r
9543             fprintf(f, "\n");\r
9544             linelen = 0;\r
9545             blank = 0;\r
9546         }\r
9547         if (blank) {\r
9548             fprintf(f, " ");\r
9549             linelen++;\r
9550         }\r
9551         fprintf(f, numtext);\r
9552         linelen += numlen;\r
9553 \r
9554         /* Get move */\r
9555         movelen = strlen(parseList[i]); /* [HGM] pgn: line-break point before move */\r
9556 \r
9557         /* Print move */\r
9558         blank = linelen > 0 && movelen > 0;\r
9559         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
9560             fprintf(f, "\n");\r
9561             linelen = 0;\r
9562             blank = 0;\r
9563         }\r
9564         if (blank) {\r
9565             fprintf(f, " ");\r
9566             linelen++;\r
9567         }\r
9568         fprintf(f, parseList[i]);\r
9569         linelen += movelen;\r
9570 \r
9571         /* [AS] Add PV info if present */\r
9572         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {\r
9573             /* [HGM] add time */\r
9574             char buf[MSG_SIZ]; int seconds = 0;\r
9575 \r
9576 #if 0\r
9577             if(i >= backwardMostMove) {\r
9578                 if(WhiteOnMove(i))\r
9579                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]\r
9580                                   + GetTimeQuota(i/2) / WhitePlayer()->timeOdds;\r
9581                 else\r
9582                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]\r
9583                                   + GetTimeQuota(i/2) / WhitePlayer()->other->timeOdds;\r
9584             }\r
9585             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest\r
9586 #else\r
9587             seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time\r
9588 #endif\r
9589 \r
9590             if( seconds <= 0) buf[0] = 0; else\r
9591             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {\r
9592                 seconds = (seconds + 4)/10; // round to full seconds\r
9593                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else\r
9594                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);\r
9595             }\r
9596 \r
9597             sprintf( move_buffer, "{%s%.2f/%d%s}", \r
9598                 pvInfoList[i].score >= 0 ? "+" : "",\r
9599                 pvInfoList[i].score / 100.0,\r
9600                 pvInfoList[i].depth,\r
9601                 buf );\r
9602 \r
9603             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */\r
9604 \r
9605             /* Print score/depth */\r
9606             blank = linelen > 0 && movelen > 0;\r
9607             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
9608                 fprintf(f, "\n");\r
9609                 linelen = 0;\r
9610                 blank = 0;\r
9611             }\r
9612             if (blank) {\r
9613                 fprintf(f, " ");\r
9614                 linelen++;\r
9615             }\r
9616             fprintf(f, move_buffer);\r
9617             linelen += movelen;\r
9618         }\r
9619 \r
9620         i++;\r
9621     }\r
9622     \r
9623     /* Start a new line */\r
9624     if (linelen > 0) fprintf(f, "\n");\r
9625 \r
9626     /* Print comments after last move */\r
9627     if (commentList[i] != NULL) {\r
9628         fprintf(f, "{\n%s}\n", commentList[i]);\r
9629     }\r
9630 \r
9631     /* Print result */\r
9632     if (gameInfo.resultDetails != NULL &&\r
9633         gameInfo.resultDetails[0] != NULLCHAR) {\r
9634         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,\r
9635                 PGNResult(gameInfo.result));\r
9636     } else {\r
9637         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
9638     }\r
9639 \r
9640     fclose(f);\r
9641     return TRUE;\r
9642 }\r
9643 \r
9644 /* Save game in old style and close the file */\r
9645 int\r
9646 SaveGameOldStyle(f)\r
9647      FILE *f;\r
9648 {\r
9649     int i, offset;\r
9650     time_t tm;\r
9651     \r
9652     tm = time((time_t *) NULL);\r
9653     \r
9654     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));\r
9655     PrintOpponents(f);\r
9656     \r
9657     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
9658         fprintf(f, "\n[--------------\n");\r
9659         PrintPosition(f, backwardMostMove);\r
9660         fprintf(f, "--------------]\n");\r
9661     } else {\r
9662         fprintf(f, "\n");\r
9663     }\r
9664 \r
9665     i = backwardMostMove;\r
9666     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9667 \r
9668     while (i < forwardMostMove) {\r
9669         if (commentList[i] != NULL) {\r
9670             fprintf(f, "[%s]\n", commentList[i]);\r
9671         }\r
9672 \r
9673         if ((i % 2) == 1) {\r
9674             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);\r
9675             i++;\r
9676         } else {\r
9677             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);\r
9678             i++;\r
9679             if (commentList[i] != NULL) {\r
9680                 fprintf(f, "\n");\r
9681                 continue;\r
9682             }\r
9683             if (i >= forwardMostMove) {\r
9684                 fprintf(f, "\n");\r
9685                 break;\r
9686             }\r
9687             fprintf(f, "%s\n", parseList[i]);\r
9688             i++;\r
9689         }\r
9690     }\r
9691     \r
9692     if (commentList[i] != NULL) {\r
9693         fprintf(f, "[%s]\n", commentList[i]);\r
9694     }\r
9695 \r
9696     /* This isn't really the old style, but it's close enough */\r
9697     if (gameInfo.resultDetails != NULL &&\r
9698         gameInfo.resultDetails[0] != NULLCHAR) {\r
9699         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),\r
9700                 gameInfo.resultDetails);\r
9701     } else {\r
9702         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
9703     }\r
9704 \r
9705     fclose(f);\r
9706     return TRUE;\r
9707 }\r
9708 \r
9709 /* Save the current game to open file f and close the file */\r
9710 int\r
9711 SaveGame(f, dummy, dummy2)\r
9712      FILE *f;\r
9713      int dummy;\r
9714      char *dummy2;\r
9715 {\r
9716     if (gameMode == EditPosition) EditPositionDone();\r
9717     if (appData.oldSaveStyle)\r
9718       return SaveGameOldStyle(f);\r
9719     else\r
9720       return SaveGamePGN(f);\r
9721 }\r
9722 \r
9723 /* Save the current position to the given file */\r
9724 int\r
9725 SavePositionToFile(filename)\r
9726      char *filename;\r
9727 {\r
9728     FILE *f;\r
9729     char buf[MSG_SIZ];\r
9730 \r
9731     if (strcmp(filename, "-") == 0) {\r
9732         return SavePosition(stdout, 0, NULL);\r
9733     } else {\r
9734         f = fopen(filename, "a");\r
9735         if (f == NULL) {\r
9736             sprintf(buf, _("Can't open \"%s\""), filename);\r
9737             DisplayError(buf, errno);\r
9738             return FALSE;\r
9739         } else {\r
9740             SavePosition(f, 0, NULL);\r
9741             return TRUE;\r
9742         }\r
9743     }\r
9744 }\r
9745 \r
9746 /* Save the current position to the given open file and close the file */\r
9747 int\r
9748 SavePosition(f, dummy, dummy2)\r
9749      FILE *f;\r
9750      int dummy;\r
9751      char *dummy2;\r
9752 {\r
9753     time_t tm;\r
9754     char *fen;\r
9755     \r
9756     if (appData.oldSaveStyle) {\r
9757         tm = time((time_t *) NULL);\r
9758     \r
9759         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));\r
9760         PrintOpponents(f);\r
9761         fprintf(f, "[--------------\n");\r
9762         PrintPosition(f, currentMove);\r
9763         fprintf(f, "--------------]\n");\r
9764     } else {\r
9765         fen = PositionToFEN(currentMove, NULL);\r
9766         fprintf(f, "%s\n", fen);\r
9767         free(fen);\r
9768     }\r
9769     fclose(f);\r
9770     return TRUE;\r
9771 }\r
9772 \r
9773 void\r
9774 ReloadCmailMsgEvent(unregister)\r
9775      int unregister;\r
9776 {\r
9777 #if !WIN32\r
9778     static char *inFilename = NULL;\r
9779     static char *outFilename;\r
9780     int i;\r
9781     struct stat inbuf, outbuf;\r
9782     int status;\r
9783     \r
9784     /* Any registered moves are unregistered if unregister is set, */\r
9785     /* i.e. invoked by the signal handler */\r
9786     if (unregister) {\r
9787         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
9788             cmailMoveRegistered[i] = FALSE;\r
9789             if (cmailCommentList[i] != NULL) {\r
9790                 free(cmailCommentList[i]);\r
9791                 cmailCommentList[i] = NULL;\r
9792             }\r
9793         }\r
9794         nCmailMovesRegistered = 0;\r
9795     }\r
9796 \r
9797     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
9798         cmailResult[i] = CMAIL_NOT_RESULT;\r
9799     }\r
9800     nCmailResults = 0;\r
9801 \r
9802     if (inFilename == NULL) {\r
9803         /* Because the filenames are static they only get malloced once  */\r
9804         /* and they never get freed                                      */\r
9805         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);\r
9806         sprintf(inFilename, "%s.game.in", appData.cmailGameName);\r
9807 \r
9808         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);\r
9809         sprintf(outFilename, "%s.out", appData.cmailGameName);\r
9810     }\r
9811     \r
9812     status = stat(outFilename, &outbuf);\r
9813     if (status < 0) {\r
9814         cmailMailedMove = FALSE;\r
9815     } else {\r
9816         status = stat(inFilename, &inbuf);\r
9817         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);\r
9818     }\r
9819     \r
9820     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE\r
9821        counts the games, notes how each one terminated, etc.\r
9822        \r
9823        It would be nice to remove this kludge and instead gather all\r
9824        the information while building the game list.  (And to keep it\r
9825        in the game list nodes instead of having a bunch of fixed-size\r
9826        parallel arrays.)  Note this will require getting each game's\r
9827        termination from the PGN tags, as the game list builder does\r
9828        not process the game moves.  --mann\r
9829        */\r
9830     cmailMsgLoaded = TRUE;\r
9831     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);\r
9832     \r
9833     /* Load first game in the file or popup game menu */\r
9834     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);\r
9835 \r
9836 #endif /* !WIN32 */\r
9837     return;\r
9838 }\r
9839 \r
9840 int\r
9841 RegisterMove()\r
9842 {\r
9843     FILE *f;\r
9844     char string[MSG_SIZ];\r
9845 \r
9846     if (   cmailMailedMove\r
9847         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {\r
9848         return TRUE;            /* Allow free viewing  */\r
9849     }\r
9850 \r
9851     /* Unregister move to ensure that we don't leave RegisterMove        */\r
9852     /* with the move registered when the conditions for registering no   */\r
9853     /* longer hold                                                       */\r
9854     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
9855         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
9856         nCmailMovesRegistered --;\r
9857 \r
9858         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) \r
9859           {\r
9860               free(cmailCommentList[lastLoadGameNumber - 1]);\r
9861               cmailCommentList[lastLoadGameNumber - 1] = NULL;\r
9862           }\r
9863     }\r
9864 \r
9865     if (cmailOldMove == -1) {\r
9866         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);\r
9867         return FALSE;\r
9868     }\r
9869 \r
9870     if (currentMove > cmailOldMove + 1) {\r
9871         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);\r
9872         return FALSE;\r
9873     }\r
9874 \r
9875     if (currentMove < cmailOldMove) {\r
9876         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);\r
9877         return FALSE;\r
9878     }\r
9879 \r
9880     if (forwardMostMove > currentMove) {\r
9881         /* Silently truncate extra moves */\r
9882         TruncateGame();\r
9883     }\r
9884 \r
9885     if (   (currentMove == cmailOldMove + 1)\r
9886         || (   (currentMove == cmailOldMove)\r
9887             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)\r
9888                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {\r
9889         if (gameInfo.result != GameUnfinished) {\r
9890             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;\r
9891         }\r
9892 \r
9893         if (commentList[currentMove] != NULL) {\r
9894             cmailCommentList[lastLoadGameNumber - 1]\r
9895               = StrSave(commentList[currentMove]);\r
9896         }\r
9897         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);\r
9898 \r
9899         if (appData.debugMode)\r
9900           fprintf(debugFP, "Saving %s for game %d\n",\r
9901                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
9902 \r
9903         sprintf(string,\r
9904                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);\r
9905         \r
9906         f = fopen(string, "w");\r
9907         if (appData.oldSaveStyle) {\r
9908             SaveGameOldStyle(f); /* also closes the file */\r
9909             \r
9910             sprintf(string, "%s.pos.out", appData.cmailGameName);\r
9911             f = fopen(string, "w");\r
9912             SavePosition(f, 0, NULL); /* also closes the file */\r
9913         } else {\r
9914             fprintf(f, "{--------------\n");\r
9915             PrintPosition(f, currentMove);\r
9916             fprintf(f, "--------------}\n\n");\r
9917             \r
9918             SaveGame(f, 0, NULL); /* also closes the file*/\r
9919         }\r
9920         \r
9921         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;\r
9922         nCmailMovesRegistered ++;\r
9923     } else if (nCmailGames == 1) {\r
9924         DisplayError(_("You have not made a move yet"), 0);\r
9925         return FALSE;\r
9926     }\r
9927 \r
9928     return TRUE;\r
9929 }\r
9930 \r
9931 void\r
9932 MailMoveEvent()\r
9933 {\r
9934 #if !WIN32\r
9935     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";\r
9936     FILE *commandOutput;\r
9937     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];\r
9938     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */\r
9939     int nBuffers;\r
9940     int i;\r
9941     int archived;\r
9942     char *arcDir;\r
9943 \r
9944     if (! cmailMsgLoaded) {\r
9945         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);\r
9946         return;\r
9947     }\r
9948 \r
9949     if (nCmailGames == nCmailResults) {\r
9950         DisplayError(_("No unfinished games"), 0);\r
9951         return;\r
9952     }\r
9953 \r
9954 #if CMAIL_PROHIBIT_REMAIL\r
9955     if (cmailMailedMove) {\r
9956         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
9957         DisplayError(msg, 0);\r
9958         return;\r
9959     }\r
9960 #endif\r
9961 \r
9962     if (! (cmailMailedMove || RegisterMove())) return;\r
9963     \r
9964     if (   cmailMailedMove\r
9965         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {\r
9966         sprintf(string, partCommandString,\r
9967                 appData.debugMode ? " -v" : "", appData.cmailGameName);\r
9968         commandOutput = popen(string, "r");\r
9969 \r
9970         if (commandOutput == NULL) {\r
9971             DisplayError(_("Failed to invoke cmail"), 0);\r
9972         } else {\r
9973             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {\r
9974                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);\r
9975             }\r
9976             if (nBuffers > 1) {\r
9977                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);\r
9978                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);\r
9979                 nBytes = MSG_SIZ - 1;\r
9980             } else {\r
9981                 (void) memcpy(msg, buffer, nBytes);\r
9982             }\r
9983             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/\r
9984 \r
9985             if(StrStr(msg, "Mailed cmail message to ") != NULL) {\r
9986                 cmailMailedMove = TRUE; /* Prevent >1 moves    */\r
9987 \r
9988                 archived = TRUE;\r
9989                 for (i = 0; i < nCmailGames; i ++) {\r
9990                     if (cmailResult[i] == CMAIL_NOT_RESULT) {\r
9991                         archived = FALSE;\r
9992                     }\r
9993                 }\r
9994                 if (   archived\r
9995                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))\r
9996                         != NULL)) {\r
9997                     sprintf(buffer, "%s/%s.%s.archive",\r
9998                             arcDir,\r
9999                             appData.cmailGameName,\r
10000                             gameInfo.date);\r
10001                     LoadGameFromFile(buffer, 1, buffer, FALSE);\r
10002                     cmailMsgLoaded = FALSE;\r
10003                 }\r
10004             }\r
10005 \r
10006             DisplayInformation(msg);\r
10007             pclose(commandOutput);\r
10008         }\r
10009     } else {\r
10010         if ((*cmailMsg) != '\0') {\r
10011             DisplayInformation(cmailMsg);\r
10012         }\r
10013     }\r
10014 \r
10015     return;\r
10016 #endif /* !WIN32 */\r
10017 }\r
10018 \r
10019 char *\r
10020 CmailMsg()\r
10021 {\r
10022 #if WIN32\r
10023     return NULL;\r
10024 #else\r
10025     int  prependComma = 0;\r
10026     char number[5];\r
10027     char string[MSG_SIZ];       /* Space for game-list */\r
10028     int  i;\r
10029     \r
10030     if (!cmailMsgLoaded) return "";\r
10031 \r
10032     if (cmailMailedMove) {\r
10033         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));\r
10034     } else {\r
10035         /* Create a list of games left */\r
10036         sprintf(string, "[");\r
10037         for (i = 0; i < nCmailGames; i ++) {\r
10038             if (! (   cmailMoveRegistered[i]\r
10039                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {\r
10040                 if (prependComma) {\r
10041                     sprintf(number, ",%d", i + 1);\r
10042                 } else {\r
10043                     sprintf(number, "%d", i + 1);\r
10044                     prependComma = 1;\r
10045                 }\r
10046                 \r
10047                 strcat(string, number);\r
10048             }\r
10049         }\r
10050         strcat(string, "]");\r
10051 \r
10052         if (nCmailMovesRegistered + nCmailResults == 0) {\r
10053             switch (nCmailGames) {\r
10054               case 1:\r
10055                 sprintf(cmailMsg,\r
10056                         _("Still need to make move for game\n"));\r
10057                 break;\r
10058                 \r
10059               case 2:\r
10060                 sprintf(cmailMsg,\r
10061                         _("Still need to make moves for both games\n"));\r
10062                 break;\r
10063                 \r
10064               default:\r
10065                 sprintf(cmailMsg,\r
10066                         _("Still need to make moves for all %d games\n"),\r
10067                         nCmailGames);\r
10068                 break;\r
10069             }\r
10070         } else {\r
10071             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {\r
10072               case 1:\r
10073                 sprintf(cmailMsg,\r
10074                         _("Still need to make a move for game %s\n"),\r
10075                         string);\r
10076                 break;\r
10077                 \r
10078               case 0:\r
10079                 if (nCmailResults == nCmailGames) {\r
10080                     sprintf(cmailMsg, _("No unfinished games\n"));\r
10081                 } else {\r
10082                     sprintf(cmailMsg, _("Ready to send mail\n"));\r
10083                 }\r
10084                 break;\r
10085                 \r
10086               default:\r
10087                 sprintf(cmailMsg,\r
10088                         _("Still need to make moves for games %s\n"),\r
10089                         string);\r
10090             }\r
10091         }\r
10092     }\r
10093     return cmailMsg;\r
10094 #endif /* WIN32 */\r
10095 }\r
10096 \r
10097 void\r
10098 ResetGameEvent()\r
10099 {\r
10100     if (gameMode == Training)\r
10101       SetTrainingModeOff();\r
10102 \r
10103     Reset(TRUE, TRUE);\r
10104     cmailMsgLoaded = FALSE;\r
10105     if (appData.icsActive) {\r
10106       SendToICS(ics_prefix);\r
10107       SendToICS("refresh\n");\r
10108     }\r
10109 }\r
10110 \r
10111 void\r
10112 ExitEvent(status)\r
10113      int status;\r
10114 {\r
10115     exiting++;\r
10116     if (exiting > 2) {\r
10117       /* Give up on clean exit */\r
10118       exit(status);\r
10119     }\r
10120     if (exiting > 1) {\r
10121       /* Keep trying for clean exit */\r
10122       return;\r
10123     }\r
10124 \r
10125     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);\r
10126 \r
10127     if (telnetISR != NULL) {\r
10128       RemoveInputSource(telnetISR);\r
10129     }\r
10130     if (icsPR != NoProc) {\r
10131       DestroyChildProcess(icsPR, TRUE);\r
10132     }\r
10133 #if 0\r
10134     /* Save game if resource set and not already saved by GameEnds() */\r
10135     if ((gameInfo.resultDetails == NULL || errorExitFlag )\r
10136                              && forwardMostMove > 0) {\r
10137       if (*appData.saveGameFile != NULLCHAR) {\r
10138         SaveGameToFile(appData.saveGameFile, TRUE);\r
10139       } else if (appData.autoSaveGames) {\r
10140         AutoSaveGame();\r
10141       }\r
10142       if (*appData.savePositionFile != NULLCHAR) {\r
10143         SavePositionToFile(appData.savePositionFile);\r
10144       }\r
10145     }\r
10146     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
10147 #else\r
10148     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */\r
10149     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);\r
10150 #endif\r
10151     /* [HGM] crash: the above GameEnds() is a dud if another one was running */\r
10152     /* make sure this other one finishes before killing it!                  */\r
10153     if(endingGame) { int count = 0;\r
10154         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");\r
10155         while(endingGame && count++ < 10) DoSleep(1);\r
10156         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");\r
10157     }\r
10158 \r
10159     /* Kill off chess programs */\r
10160     if (first.pr != NoProc) {\r
10161         ExitAnalyzeMode();\r
10162         \r
10163         DoSleep( appData.delayBeforeQuit );\r
10164         SendToProgram("quit\n", &first);\r
10165         DoSleep( appData.delayAfterQuit );\r
10166         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );\r
10167     }\r
10168     if (second.pr != NoProc) {\r
10169         DoSleep( appData.delayBeforeQuit );\r
10170         SendToProgram("quit\n", &second);\r
10171         DoSleep( appData.delayAfterQuit );\r
10172         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );\r
10173     }\r
10174     if (first.isr != NULL) {\r
10175         RemoveInputSource(first.isr);\r
10176     }\r
10177     if (second.isr != NULL) {\r
10178         RemoveInputSource(second.isr);\r
10179     }\r
10180 \r
10181     ShutDownFrontEnd();\r
10182     exit(status);\r
10183 }\r
10184 \r
10185 void\r
10186 PauseEvent()\r
10187 {\r
10188     if (appData.debugMode)\r
10189         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);\r
10190     if (pausing) {\r
10191         pausing = FALSE;\r
10192         ModeHighlight();\r
10193         if (gameMode == MachinePlaysWhite ||\r
10194             gameMode == MachinePlaysBlack) {\r
10195             StartClocks();\r
10196         } else {\r
10197             DisplayBothClocks();\r
10198         }\r
10199         if (gameMode == PlayFromGameFile) {\r
10200             if (appData.timeDelay >= 0) \r
10201                 AutoPlayGameLoop();\r
10202         } else if (gameMode == IcsExamining && pauseExamInvalid) {\r
10203             Reset(FALSE, TRUE);\r
10204             SendToICS(ics_prefix);\r
10205             SendToICS("refresh\n");\r
10206         } else if (currentMove < forwardMostMove) {\r
10207             ForwardInner(forwardMostMove);\r
10208         }\r
10209         pauseExamInvalid = FALSE;\r
10210     } else {\r
10211         switch (gameMode) {\r
10212           default:\r
10213             return;\r
10214           case IcsExamining:\r
10215             pauseExamForwardMostMove = forwardMostMove;\r
10216             pauseExamInvalid = FALSE;\r
10217             /* fall through */\r
10218           case IcsObserving:\r
10219           case IcsPlayingWhite:\r
10220           case IcsPlayingBlack:\r
10221             pausing = TRUE;\r
10222             ModeHighlight();\r
10223             return;\r
10224           case PlayFromGameFile:\r
10225             (void) StopLoadGameTimer();\r
10226             pausing = TRUE;\r
10227             ModeHighlight();\r
10228             break;\r
10229           case BeginningOfGame:\r
10230             if (appData.icsActive) return;\r
10231             /* else fall through */\r
10232           case MachinePlaysWhite:\r
10233           case MachinePlaysBlack:\r
10234           case TwoMachinesPlay:\r
10235             if (forwardMostMove == 0)\r
10236               return;           /* don't pause if no one has moved */\r
10237             if ((gameMode == MachinePlaysWhite &&\r
10238                  !WhiteOnMove(forwardMostMove)) ||\r
10239                 (gameMode == MachinePlaysBlack &&\r
10240                  WhiteOnMove(forwardMostMove))) {\r
10241                 StopClocks();\r
10242             }\r
10243             pausing = TRUE;\r
10244             ModeHighlight();\r
10245             break;\r
10246         }\r
10247     }\r
10248 }\r
10249 \r
10250 void\r
10251 EditCommentEvent()\r
10252 {\r
10253     char title[MSG_SIZ];\r
10254 \r
10255     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {\r
10256         strcpy(title, _("Edit comment"));\r
10257     } else {\r
10258         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,\r
10259                 WhiteOnMove(currentMove - 1) ? " " : ".. ",\r
10260                 parseList[currentMove - 1]);\r
10261     }\r
10262 \r
10263     EditCommentPopUp(currentMove, title, commentList[currentMove]);\r
10264 }\r
10265 \r
10266 \r
10267 void\r
10268 EditTagsEvent()\r
10269 {\r
10270     char *tags = PGNTags(&gameInfo);\r
10271     EditTagsPopUp(tags);\r
10272     free(tags);\r
10273 }\r
10274 \r
10275 void\r
10276 AnalyzeModeEvent()\r
10277 {\r
10278     if (appData.noChessProgram || gameMode == AnalyzeMode)\r
10279       return;\r
10280 \r
10281     if (gameMode != AnalyzeFile) {\r
10282         if (!appData.icsEngineAnalyze) {\r
10283                EditGameEvent();\r
10284                if (gameMode != EditGame) return;\r
10285         }\r
10286         ResurrectChessProgram();\r
10287         SendToProgram("analyze\n", &first);\r
10288         first.analyzing = TRUE;\r
10289         /*first.maybeThinking = TRUE;*/\r
10290         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10291         AnalysisPopUp(_("Analysis"),\r
10292                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));\r
10293     }\r
10294     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;\r
10295     pausing = FALSE;\r
10296     ModeHighlight();\r
10297     SetGameInfo();\r
10298 \r
10299     StartAnalysisClock();\r
10300     GetTimeMark(&lastNodeCountTime);\r
10301     lastNodeCount = 0;\r
10302 }\r
10303 \r
10304 void\r
10305 AnalyzeFileEvent()\r
10306 {\r
10307     if (appData.noChessProgram || gameMode == AnalyzeFile)\r
10308       return;\r
10309 \r
10310     if (gameMode != AnalyzeMode) {\r
10311         EditGameEvent();\r
10312         if (gameMode != EditGame) return;\r
10313         ResurrectChessProgram();\r
10314         SendToProgram("analyze\n", &first);\r
10315         first.analyzing = TRUE;\r
10316         /*first.maybeThinking = TRUE;*/\r
10317         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10318         AnalysisPopUp(_("Analysis"),\r
10319                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));\r
10320     }\r
10321     gameMode = AnalyzeFile;\r
10322     pausing = FALSE;\r
10323     ModeHighlight();\r
10324     SetGameInfo();\r
10325 \r
10326     StartAnalysisClock();\r
10327     GetTimeMark(&lastNodeCountTime);\r
10328     lastNodeCount = 0;\r
10329 }\r
10330 \r
10331 void\r
10332 MachineWhiteEvent()\r
10333 {\r
10334     char buf[MSG_SIZ];\r
10335     char *bookHit = NULL;\r
10336 \r
10337     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))\r
10338       return;\r
10339 \r
10340 \r
10341     if (gameMode == PlayFromGameFile || \r
10342         gameMode == TwoMachinesPlay  || \r
10343         gameMode == Training         || \r
10344         gameMode == AnalyzeMode      || \r
10345         gameMode == EndOfGame)\r
10346         EditGameEvent();\r
10347 \r
10348     if (gameMode == EditPosition) \r
10349         EditPositionDone();\r
10350 \r
10351     if (!WhiteOnMove(currentMove)) {\r
10352         DisplayError(_("It is not White's turn"), 0);\r
10353         return;\r
10354     }\r
10355   \r
10356     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
10357       ExitAnalyzeMode();\r
10358 \r
10359     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
10360         gameMode == AnalyzeFile)\r
10361         TruncateGame();\r
10362 \r
10363     ResurrectChessProgram();    /* in case it isn't running */\r
10364     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */\r
10365         gameMode = MachinePlaysWhite;\r
10366         ResetClocks();\r
10367     } else\r
10368     gameMode = MachinePlaysWhite;\r
10369     pausing = FALSE;\r
10370     ModeHighlight();\r
10371     SetGameInfo();\r
10372     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10373     DisplayTitle(buf);\r
10374     if (first.sendName) {\r
10375       sprintf(buf, "name %s\n", gameInfo.black);\r
10376       SendToProgram(buf, &first);\r
10377     }\r
10378     if (first.sendTime) {\r
10379       if (first.useColors) {\r
10380         SendToProgram("black\n", &first); /*gnu kludge*/\r
10381       }\r
10382       SendTimeRemaining(&first, TRUE);\r
10383     }\r
10384     if (first.useColors) {\r
10385       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately\r
10386     }\r
10387     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move\r
10388     SetMachineThinkingEnables();\r
10389     first.maybeThinking = TRUE;\r
10390     StartClocks();\r
10391 \r
10392     if (appData.autoFlipView && !flipView) {\r
10393       flipView = !flipView;\r
10394       DrawPosition(FALSE, NULL);\r
10395       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;\r
10396     }\r
10397 \r
10398     if(bookHit) { // [HGM] book: simulate book reply\r
10399         static char bookMove[MSG_SIZ]; // a bit generous?\r
10400 \r
10401         programStats.nodes = programStats.depth = programStats.time = \r
10402         programStats.score = programStats.got_only_move = 0;\r
10403         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10404 \r
10405         strcpy(bookMove, "move ");\r
10406         strcat(bookMove, bookHit);\r
10407         HandleMachineMove(bookMove, &first);\r
10408     }\r
10409 }\r
10410 \r
10411 void\r
10412 MachineBlackEvent()\r
10413 {\r
10414     char buf[MSG_SIZ];\r
10415    char *bookHit = NULL;\r
10416 \r
10417     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))\r
10418         return;\r
10419 \r
10420 \r
10421     if (gameMode == PlayFromGameFile || \r
10422         gameMode == TwoMachinesPlay  || \r
10423         gameMode == Training         || \r
10424         gameMode == AnalyzeMode      || \r
10425         gameMode == EndOfGame)\r
10426         EditGameEvent();\r
10427 \r
10428     if (gameMode == EditPosition) \r
10429         EditPositionDone();\r
10430 \r
10431     if (WhiteOnMove(currentMove)) {\r
10432         DisplayError(_("It is not Black's turn"), 0);\r
10433         return;\r
10434     }\r
10435     \r
10436     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
10437       ExitAnalyzeMode();\r
10438 \r
10439     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
10440         gameMode == AnalyzeFile)\r
10441         TruncateGame();\r
10442 \r
10443     ResurrectChessProgram();    /* in case it isn't running */\r
10444     gameMode = MachinePlaysBlack;\r
10445     pausing = FALSE;\r
10446     ModeHighlight();\r
10447     SetGameInfo();\r
10448     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10449     DisplayTitle(buf);\r
10450     if (first.sendName) {\r
10451       sprintf(buf, "name %s\n", gameInfo.white);\r
10452       SendToProgram(buf, &first);\r
10453     }\r
10454     if (first.sendTime) {\r
10455       if (first.useColors) {\r
10456         SendToProgram("white\n", &first); /*gnu kludge*/\r
10457       }\r
10458       SendTimeRemaining(&first, FALSE);\r
10459     }\r
10460     if (first.useColors) {\r
10461       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately\r
10462     }\r
10463     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move\r
10464     SetMachineThinkingEnables();\r
10465     first.maybeThinking = TRUE;\r
10466     StartClocks();\r
10467 \r
10468     if (appData.autoFlipView && flipView) {\r
10469       flipView = !flipView;\r
10470       DrawPosition(FALSE, NULL);\r
10471       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;\r
10472     }\r
10473     if(bookHit) { // [HGM] book: simulate book reply\r
10474         static char bookMove[MSG_SIZ]; // a bit generous?\r
10475 \r
10476         programStats.nodes = programStats.depth = programStats.time = \r
10477         programStats.score = programStats.got_only_move = 0;\r
10478         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10479 \r
10480         strcpy(bookMove, "move ");\r
10481         strcat(bookMove, bookHit);\r
10482         HandleMachineMove(bookMove, &first);\r
10483     }\r
10484 }\r
10485 \r
10486 \r
10487 void\r
10488 DisplayTwoMachinesTitle()\r
10489 {\r
10490     char buf[MSG_SIZ];\r
10491     if (appData.matchGames > 0) {\r
10492         if (first.twoMachinesColor[0] == 'w') {\r
10493             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
10494                     gameInfo.white, gameInfo.black,\r
10495                     first.matchWins, second.matchWins,\r
10496                     matchGame - 1 - (first.matchWins + second.matchWins));\r
10497         } else {\r
10498             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
10499                     gameInfo.white, gameInfo.black,\r
10500                     second.matchWins, first.matchWins,\r
10501                     matchGame - 1 - (first.matchWins + second.matchWins));\r
10502         }\r
10503     } else {\r
10504         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
10505     }\r
10506     DisplayTitle(buf);\r
10507 }\r
10508 \r
10509 void\r
10510 TwoMachinesEvent P((void))\r
10511 {\r
10512     int i;\r
10513     char buf[MSG_SIZ];\r
10514     ChessProgramState *onmove;\r
10515     char *bookHit = NULL;\r
10516     \r
10517     if (appData.noChessProgram) return;\r
10518 \r
10519     switch (gameMode) {\r
10520       case TwoMachinesPlay:\r
10521         return;\r
10522       case MachinePlaysWhite:\r
10523       case MachinePlaysBlack:\r
10524         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
10525             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);\r
10526             return;\r
10527         }\r
10528         /* fall through */\r
10529       case BeginningOfGame:\r
10530       case PlayFromGameFile:\r
10531       case EndOfGame:\r
10532         EditGameEvent();\r
10533         if (gameMode != EditGame) return;\r
10534         break;\r
10535       case EditPosition:\r
10536         EditPositionDone();\r
10537         break;\r
10538       case AnalyzeMode:\r
10539       case AnalyzeFile:\r
10540         ExitAnalyzeMode();\r
10541         break;\r
10542       case EditGame:\r
10543       default:\r
10544         break;\r
10545     }\r
10546 \r
10547     forwardMostMove = currentMove;\r
10548     ResurrectChessProgram();    /* in case first program isn't running */\r
10549 \r
10550     if (second.pr == NULL) {\r
10551         StartChessProgram(&second);\r
10552         if (second.protocolVersion == 1) {\r
10553           TwoMachinesEventIfReady();\r
10554         } else {\r
10555           /* kludge: allow timeout for initial "feature" command */\r
10556           FreezeUI();\r
10557           DisplayMessage("", _("Starting second chess program"));\r
10558           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);\r
10559         }\r
10560         return;\r
10561     }\r
10562     DisplayMessage("", "");\r
10563     InitChessProgram(&second, FALSE);\r
10564     SendToProgram("force\n", &second);\r
10565     if (startedFromSetupPosition) {\r
10566         SendBoard(&second, backwardMostMove);\r
10567     if (appData.debugMode) {\r
10568         fprintf(debugFP, "Two Machines\n");\r
10569     }\r
10570     }\r
10571     for (i = backwardMostMove; i < forwardMostMove; i++) {\r
10572         SendMoveToProgram(i, &second);\r
10573     }\r
10574 \r
10575     gameMode = TwoMachinesPlay;\r
10576     pausing = FALSE;\r
10577     ModeHighlight();\r
10578     SetGameInfo();\r
10579     DisplayTwoMachinesTitle();\r
10580     firstMove = TRUE;\r
10581     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {\r
10582         onmove = &first;\r
10583     } else {\r
10584         onmove = &second;\r
10585     }\r
10586 \r
10587     SendToProgram(first.computerString, &first);\r
10588     if (first.sendName) {\r
10589       sprintf(buf, "name %s\n", second.tidy);\r
10590       SendToProgram(buf, &first);\r
10591     }\r
10592     SendToProgram(second.computerString, &second);\r
10593     if (second.sendName) {\r
10594       sprintf(buf, "name %s\n", first.tidy);\r
10595       SendToProgram(buf, &second);\r
10596     }\r
10597 \r
10598     ResetClocks();\r
10599     if (!first.sendTime || !second.sendTime) {\r
10600         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
10601         timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
10602     }\r
10603     if (onmove->sendTime) {\r
10604       if (onmove->useColors) {\r
10605         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/\r
10606       }\r
10607       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));\r
10608     }\r
10609     if (onmove->useColors) {\r
10610       SendToProgram(onmove->twoMachinesColor, onmove);\r
10611     }\r
10612     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move\r
10613 //    SendToProgram("go\n", onmove);\r
10614     onmove->maybeThinking = TRUE;\r
10615     SetMachineThinkingEnables();\r
10616 \r
10617     StartClocks();\r
10618 \r
10619     if(bookHit) { // [HGM] book: simulate book reply\r
10620         static char bookMove[MSG_SIZ]; // a bit generous?\r
10621 \r
10622         programStats.nodes = programStats.depth = programStats.time = \r
10623         programStats.score = programStats.got_only_move = 0;\r
10624         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
10625 \r
10626         strcpy(bookMove, "move ");\r
10627         strcat(bookMove, bookHit);\r
10628         HandleMachineMove(bookMove, &first);\r
10629     }\r
10630 }\r
10631 \r
10632 void\r
10633 TrainingEvent()\r
10634 {\r
10635     if (gameMode == Training) {\r
10636       SetTrainingModeOff();\r
10637       gameMode = PlayFromGameFile;\r
10638       DisplayMessage("", _("Training mode off"));\r
10639     } else {\r
10640       gameMode = Training;\r
10641       animateTraining = appData.animate;\r
10642 \r
10643       /* make sure we are not already at the end of the game */\r
10644       if (currentMove < forwardMostMove) {\r
10645         SetTrainingModeOn();\r
10646         DisplayMessage("", _("Training mode on"));\r
10647       } else {\r
10648         gameMode = PlayFromGameFile;\r
10649         DisplayError(_("Already at end of game"), 0);\r
10650       }\r
10651     }\r
10652     ModeHighlight();\r
10653 }\r
10654 \r
10655 void\r
10656 IcsClientEvent()\r
10657 {\r
10658     if (!appData.icsActive) return;\r
10659     switch (gameMode) {\r
10660       case IcsPlayingWhite:\r
10661       case IcsPlayingBlack:\r
10662       case IcsObserving:\r
10663       case IcsIdle:\r
10664       case BeginningOfGame:\r
10665       case IcsExamining:\r
10666         return;\r
10667 \r
10668       case EditGame:\r
10669         break;\r
10670 \r
10671       case EditPosition:\r
10672         EditPositionDone();\r
10673         break;\r
10674 \r
10675       case AnalyzeMode:\r
10676       case AnalyzeFile:\r
10677         ExitAnalyzeMode();\r
10678         break;\r
10679         \r
10680       default:\r
10681         EditGameEvent();\r
10682         break;\r
10683     }\r
10684 \r
10685     gameMode = IcsIdle;\r
10686     ModeHighlight();\r
10687     return;\r
10688 }\r
10689 \r
10690 \r
10691 void\r
10692 EditGameEvent()\r
10693 {\r
10694     int i;\r
10695 \r
10696     switch (gameMode) {\r
10697       case Training:\r
10698         SetTrainingModeOff();\r
10699         break;\r
10700       case MachinePlaysWhite:\r
10701       case MachinePlaysBlack:\r
10702       case BeginningOfGame:\r
10703         SendToProgram("force\n", &first);\r
10704         SetUserThinkingEnables();\r
10705         break;\r
10706       case PlayFromGameFile:\r
10707         (void) StopLoadGameTimer();\r
10708         if (gameFileFP != NULL) {\r
10709             gameFileFP = NULL;\r
10710         }\r
10711         break;\r
10712       case EditPosition:\r
10713         EditPositionDone();\r
10714         break;\r
10715       case AnalyzeMode:\r
10716       case AnalyzeFile:\r
10717         ExitAnalyzeMode();\r
10718         SendToProgram("force\n", &first);\r
10719         break;\r
10720       case TwoMachinesPlay:\r
10721         GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
10722         ResurrectChessProgram();\r
10723         SetUserThinkingEnables();\r
10724         break;\r
10725       case EndOfGame:\r
10726         ResurrectChessProgram();\r
10727         break;\r
10728       case IcsPlayingBlack:\r
10729       case IcsPlayingWhite:\r
10730         DisplayError(_("Warning: You are still playing a game"), 0);\r
10731         break;\r
10732       case IcsObserving:\r
10733         DisplayError(_("Warning: You are still observing a game"), 0);\r
10734         break;\r
10735       case IcsExamining:\r
10736         DisplayError(_("Warning: You are still examining a game"), 0);\r
10737         break;\r
10738       case IcsIdle:\r
10739         break;\r
10740       case EditGame:\r
10741       default:\r
10742         return;\r
10743     }\r
10744     \r
10745     pausing = FALSE;\r
10746     StopClocks();\r
10747     first.offeredDraw = second.offeredDraw = 0;\r
10748 \r
10749     if (gameMode == PlayFromGameFile) {\r
10750         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10751         blackTimeRemaining = timeRemaining[1][currentMove];\r
10752         DisplayTitle("");\r
10753     }\r
10754 \r
10755     if (gameMode == MachinePlaysWhite ||\r
10756         gameMode == MachinePlaysBlack ||\r
10757         gameMode == TwoMachinesPlay ||\r
10758         gameMode == EndOfGame) {\r
10759         i = forwardMostMove;\r
10760         while (i > currentMove) {\r
10761             SendToProgram("undo\n", &first);\r
10762             i--;\r
10763         }\r
10764         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10765         blackTimeRemaining = timeRemaining[1][currentMove];\r
10766         DisplayBothClocks();\r
10767         if (whiteFlag || blackFlag) {\r
10768             whiteFlag = blackFlag = 0;\r
10769         }\r
10770         DisplayTitle("");\r
10771     }           \r
10772     \r
10773     gameMode = EditGame;\r
10774     ModeHighlight();\r
10775     SetGameInfo();\r
10776 }\r
10777 \r
10778 \r
10779 void\r
10780 EditPositionEvent()\r
10781 {\r
10782     if (gameMode == EditPosition) {\r
10783         EditGameEvent();\r
10784         return;\r
10785     }\r
10786     \r
10787     EditGameEvent();\r
10788     if (gameMode != EditGame) return;\r
10789     \r
10790     gameMode = EditPosition;\r
10791     ModeHighlight();\r
10792     SetGameInfo();\r
10793     if (currentMove > 0)\r
10794       CopyBoard(boards[0], boards[currentMove]);\r
10795     \r
10796     blackPlaysFirst = !WhiteOnMove(currentMove);\r
10797     ResetClocks();\r
10798     currentMove = forwardMostMove = backwardMostMove = 0;\r
10799     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
10800     DisplayMove(-1);\r
10801 }\r
10802 \r
10803 void\r
10804 ExitAnalyzeMode()\r
10805 {\r
10806     /* [DM] icsEngineAnalyze - possible call from other functions */\r
10807     if (appData.icsEngineAnalyze) {\r
10808         appData.icsEngineAnalyze = FALSE;\r
10809 \r
10810         DisplayMessage("",_("Close ICS engine analyze..."));\r
10811     }\r
10812     if (first.analysisSupport && first.analyzing) {\r
10813       SendToProgram("exit\n", &first);\r
10814       first.analyzing = FALSE;\r
10815     }\r
10816     AnalysisPopDown();\r
10817     thinkOutput[0] = NULLCHAR;\r
10818 }\r
10819 \r
10820 void\r
10821 EditPositionDone()\r
10822 {\r
10823     startedFromSetupPosition = TRUE;\r
10824     InitChessProgram(&first, FALSE);\r
10825     SendToProgram("force\n", &first);\r
10826     if (blackPlaysFirst) {\r
10827         strcpy(moveList[0], "");\r
10828         strcpy(parseList[0], "");\r
10829         currentMove = forwardMostMove = backwardMostMove = 1;\r
10830         CopyBoard(boards[1], boards[0]);\r
10831         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */\r
10832         { int i;\r
10833           epStatus[1] = epStatus[0];\r
10834           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];\r
10835         }\r
10836     } else {\r
10837         currentMove = forwardMostMove = backwardMostMove = 0;\r
10838     }\r
10839     SendBoard(&first, forwardMostMove);\r
10840     if (appData.debugMode) {\r
10841         fprintf(debugFP, "EditPosDone\n");\r
10842     }\r
10843     DisplayTitle("");\r
10844     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
10845     timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
10846     gameMode = EditGame;\r
10847     ModeHighlight();\r
10848     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
10849     ClearHighlights(); /* [AS] */\r
10850 }\r
10851 \r
10852 /* Pause for `ms' milliseconds */\r
10853 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
10854 void\r
10855 TimeDelay(ms)\r
10856      long ms;\r
10857 {\r
10858     TimeMark m1, m2;\r
10859 \r
10860     GetTimeMark(&m1);\r
10861     do {\r
10862         GetTimeMark(&m2);\r
10863     } while (SubtractTimeMarks(&m2, &m1) < ms);\r
10864 }\r
10865 \r
10866 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
10867 void\r
10868 SendMultiLineToICS(buf)\r
10869      char *buf;\r
10870 {\r
10871     char temp[MSG_SIZ+1], *p;\r
10872     int len;\r
10873 \r
10874     len = strlen(buf);\r
10875     if (len > MSG_SIZ)\r
10876       len = MSG_SIZ;\r
10877   \r
10878     strncpy(temp, buf, len);\r
10879     temp[len] = 0;\r
10880 \r
10881     p = temp;\r
10882     while (*p) {\r
10883         if (*p == '\n' || *p == '\r')\r
10884           *p = ' ';\r
10885         ++p;\r
10886     }\r
10887 \r
10888     strcat(temp, "\n");\r
10889     SendToICS(temp);\r
10890     SendToPlayer(temp, strlen(temp));\r
10891 }\r
10892 \r
10893 void\r
10894 SetWhiteToPlayEvent()\r
10895 {\r
10896     if (gameMode == EditPosition) {\r
10897         blackPlaysFirst = FALSE;\r
10898         DisplayBothClocks();    /* works because currentMove is 0 */\r
10899     } else if (gameMode == IcsExamining) {\r
10900         SendToICS(ics_prefix);\r
10901         SendToICS("tomove white\n");\r
10902     }\r
10903 }\r
10904 \r
10905 void\r
10906 SetBlackToPlayEvent()\r
10907 {\r
10908     if (gameMode == EditPosition) {\r
10909         blackPlaysFirst = TRUE;\r
10910         currentMove = 1;        /* kludge */\r
10911         DisplayBothClocks();\r
10912         currentMove = 0;\r
10913     } else if (gameMode == IcsExamining) {\r
10914         SendToICS(ics_prefix);\r
10915         SendToICS("tomove black\n");\r
10916     }\r
10917 }\r
10918 \r
10919 void\r
10920 EditPositionMenuEvent(selection, x, y)\r
10921      ChessSquare selection;\r
10922      int x, y;\r
10923 {\r
10924     char buf[MSG_SIZ];\r
10925     ChessSquare piece = boards[0][y][x];\r
10926 \r
10927     if (gameMode != EditPosition && gameMode != IcsExamining) return;\r
10928 \r
10929     switch (selection) {\r
10930       case ClearBoard:\r
10931         if (gameMode == IcsExamining && ics_type == ICS_FICS) {\r
10932             SendToICS(ics_prefix);\r
10933             SendToICS("bsetup clear\n");\r
10934         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {\r
10935             SendToICS(ics_prefix);\r
10936             SendToICS("clearboard\n");\r
10937         } else {\r
10938             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;\r
10939                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */\r
10940                 for (y = 0; y < BOARD_HEIGHT; y++) {\r
10941                     if (gameMode == IcsExamining) {\r
10942                         if (boards[currentMove][y][x] != EmptySquare) {\r
10943                             sprintf(buf, "%sx@%c%c\n", ics_prefix,\r
10944                                     AAA + x, ONE + y);\r
10945                             SendToICS(buf);\r
10946                         }\r
10947                     } else {\r
10948                         boards[0][y][x] = p;\r
10949                     }\r
10950                 }\r
10951             }\r
10952         }\r
10953         if (gameMode == EditPosition) {\r
10954             DrawPosition(FALSE, boards[0]);\r
10955         }\r
10956         break;\r
10957 \r
10958       case WhitePlay:\r
10959         SetWhiteToPlayEvent();\r
10960         break;\r
10961 \r
10962       case BlackPlay:\r
10963         SetBlackToPlayEvent();\r
10964         break;\r
10965 \r
10966       case EmptySquare:\r
10967         if (gameMode == IcsExamining) {\r
10968             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);\r
10969             SendToICS(buf);\r
10970         } else {\r
10971             boards[0][y][x] = EmptySquare;\r
10972             DrawPosition(FALSE, boards[0]);\r
10973         }\r
10974         break;\r
10975 \r
10976       case PromotePiece:\r
10977         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||\r
10978            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {\r
10979             selection = (ChessSquare) (PROMOTED piece);\r
10980         } else if(piece == EmptySquare) selection = WhiteSilver;\r
10981         else selection = (ChessSquare)((int)piece - 1);\r
10982         goto defaultlabel;\r
10983 \r
10984       case DemotePiece:\r
10985         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||\r
10986            piece > (int)BlackMan && piece <= (int)BlackKing   ) {\r
10987             selection = (ChessSquare) (DEMOTED piece);\r
10988         } else if(piece == EmptySquare) selection = BlackSilver;\r
10989         else selection = (ChessSquare)((int)piece + 1);       \r
10990         goto defaultlabel;\r
10991 \r
10992       case WhiteQueen:\r
10993       case BlackQueen:\r
10994         if(gameInfo.variant == VariantShatranj ||\r
10995            gameInfo.variant == VariantXiangqi  ||\r
10996            gameInfo.variant == VariantCourier    )\r
10997             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);\r
10998         goto defaultlabel;\r
10999 \r
11000       case WhiteKing:\r
11001       case BlackKing:\r
11002         if(gameInfo.variant == VariantXiangqi)\r
11003             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);\r
11004         if(gameInfo.variant == VariantKnightmate)\r
11005             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);\r
11006       default:\r
11007         defaultlabel:\r
11008         if (gameMode == IcsExamining) {\r
11009             sprintf(buf, "%s%c@%c%c\n", ics_prefix,\r
11010                     PieceToChar(selection), AAA + x, ONE + y);\r
11011             SendToICS(buf);\r
11012         } else {\r
11013             boards[0][y][x] = selection;\r
11014             DrawPosition(FALSE, boards[0]);\r
11015         }\r
11016         break;\r
11017     }\r
11018 }\r
11019 \r
11020 \r
11021 void\r
11022 DropMenuEvent(selection, x, y)\r
11023      ChessSquare selection;\r
11024      int x, y;\r
11025 {\r
11026     ChessMove moveType;\r
11027 \r
11028     switch (gameMode) {\r
11029       case IcsPlayingWhite:\r
11030       case MachinePlaysBlack:\r
11031         if (!WhiteOnMove(currentMove)) {\r
11032             DisplayMoveError(_("It is Black's turn"));\r
11033             return;\r
11034         }\r
11035         moveType = WhiteDrop;\r
11036         break;\r
11037       case IcsPlayingBlack:\r
11038       case MachinePlaysWhite:\r
11039         if (WhiteOnMove(currentMove)) {\r
11040             DisplayMoveError(_("It is White's turn"));\r
11041             return;\r
11042         }\r
11043         moveType = BlackDrop;\r
11044         break;\r
11045       case EditGame:\r
11046         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
11047         break;\r
11048       default:\r
11049         return;\r
11050     }\r
11051 \r
11052     if (moveType == BlackDrop && selection < BlackPawn) {\r
11053       selection = (ChessSquare) ((int) selection\r
11054                                  + (int) BlackPawn - (int) WhitePawn);\r
11055     }\r
11056     if (boards[currentMove][y][x] != EmptySquare) {\r
11057         DisplayMoveError(_("That square is occupied"));\r
11058         return;\r
11059     }\r
11060 \r
11061     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);\r
11062 }\r
11063 \r
11064 void\r
11065 AcceptEvent()\r
11066 {\r
11067     /* Accept a pending offer of any kind from opponent */\r
11068     \r
11069     if (appData.icsActive) {\r
11070         SendToICS(ics_prefix);\r
11071         SendToICS("accept\n");\r
11072     } else if (cmailMsgLoaded) {\r
11073         if (currentMove == cmailOldMove &&\r
11074             commentList[cmailOldMove] != NULL &&\r
11075             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11076                    "Black offers a draw" : "White offers a draw")) {\r
11077             TruncateGame();\r
11078             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
11079             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
11080         } else {\r
11081             DisplayError(_("There is no pending offer on this move"), 0);\r
11082             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
11083         }\r
11084     } else {\r
11085         /* Not used for offers from chess program */\r
11086     }\r
11087 }\r
11088 \r
11089 void\r
11090 DeclineEvent()\r
11091 {\r
11092     /* Decline a pending offer of any kind from opponent */\r
11093     \r
11094     if (appData.icsActive) {\r
11095         SendToICS(ics_prefix);\r
11096         SendToICS("decline\n");\r
11097     } else if (cmailMsgLoaded) {\r
11098         if (currentMove == cmailOldMove &&\r
11099             commentList[cmailOldMove] != NULL &&\r
11100             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11101                    "Black offers a draw" : "White offers a draw")) {\r
11102 #ifdef NOTDEF\r
11103             AppendComment(cmailOldMove, "Draw declined");\r
11104             DisplayComment(cmailOldMove - 1, "Draw declined");\r
11105 #endif /*NOTDEF*/\r
11106         } else {\r
11107             DisplayError(_("There is no pending offer on this move"), 0);\r
11108         }\r
11109     } else {\r
11110         /* Not used for offers from chess program */\r
11111     }\r
11112 }\r
11113 \r
11114 void\r
11115 RematchEvent()\r
11116 {\r
11117     /* Issue ICS rematch command */\r
11118     if (appData.icsActive) {\r
11119         SendToICS(ics_prefix);\r
11120         SendToICS("rematch\n");\r
11121     }\r
11122 }\r
11123 \r
11124 void\r
11125 CallFlagEvent()\r
11126 {\r
11127     /* Call your opponent's flag (claim a win on time) */\r
11128     if (appData.icsActive) {\r
11129         SendToICS(ics_prefix);\r
11130         SendToICS("flag\n");\r
11131     } else {\r
11132         switch (gameMode) {\r
11133           default:\r
11134             return;\r
11135           case MachinePlaysWhite:\r
11136             if (whiteFlag) {\r
11137                 if (blackFlag)\r
11138                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
11139                            GE_PLAYER);\r
11140                 else\r
11141                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);\r
11142             } else {\r
11143                 DisplayError(_("Your opponent is not out of time"), 0);\r
11144             }\r
11145             break;\r
11146           case MachinePlaysBlack:\r
11147             if (blackFlag) {\r
11148                 if (whiteFlag)\r
11149                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
11150                            GE_PLAYER);\r
11151                 else\r
11152                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);\r
11153             } else {\r
11154                 DisplayError(_("Your opponent is not out of time"), 0);\r
11155             }\r
11156             break;\r
11157         }\r
11158     }\r
11159 }\r
11160 \r
11161 void\r
11162 DrawEvent()\r
11163 {\r
11164     /* Offer draw or accept pending draw offer from opponent */\r
11165     \r
11166     if (appData.icsActive) {\r
11167         /* Note: tournament rules require draw offers to be\r
11168            made after you make your move but before you punch\r
11169            your clock.  Currently ICS doesn't let you do that;\r
11170            instead, you immediately punch your clock after making\r
11171            a move, but you can offer a draw at any time. */\r
11172         \r
11173         SendToICS(ics_prefix);\r
11174         SendToICS("draw\n");\r
11175     } else if (cmailMsgLoaded) {\r
11176         if (currentMove == cmailOldMove &&\r
11177             commentList[cmailOldMove] != NULL &&\r
11178             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
11179                    "Black offers a draw" : "White offers a draw")) {\r
11180             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
11181             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
11182         } else if (currentMove == cmailOldMove + 1) {\r
11183             char *offer = WhiteOnMove(cmailOldMove) ?\r
11184               "White offers a draw" : "Black offers a draw";\r
11185             AppendComment(currentMove, offer);\r
11186             DisplayComment(currentMove - 1, offer);\r
11187             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;\r
11188         } else {\r
11189             DisplayError(_("You must make your move before offering a draw"), 0);\r
11190             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
11191         }\r
11192     } else if (first.offeredDraw) {\r
11193         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
11194     } else {\r
11195         if (first.sendDrawOffers) {\r
11196             SendToProgram("draw\n", &first);\r
11197             userOfferedDraw = TRUE;\r
11198         }\r
11199     }\r
11200 }\r
11201 \r
11202 void\r
11203 AdjournEvent()\r
11204 {\r
11205     /* Offer Adjourn or accept pending Adjourn offer from opponent */\r
11206     \r
11207     if (appData.icsActive) {\r
11208         SendToICS(ics_prefix);\r
11209         SendToICS("adjourn\n");\r
11210     } else {\r
11211         /* Currently GNU Chess doesn't offer or accept Adjourns */\r
11212     }\r
11213 }\r
11214 \r
11215 \r
11216 void\r
11217 AbortEvent()\r
11218 {\r
11219     /* Offer Abort or accept pending Abort offer from opponent */\r
11220     \r
11221     if (appData.icsActive) {\r
11222         SendToICS(ics_prefix);\r
11223         SendToICS("abort\n");\r
11224     } else {\r
11225         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);\r
11226     }\r
11227 }\r
11228 \r
11229 void\r
11230 ResignEvent()\r
11231 {\r
11232     /* Resign.  You can do this even if it's not your turn. */\r
11233     \r
11234     if (appData.icsActive) {\r
11235         SendToICS(ics_prefix);\r
11236         SendToICS("resign\n");\r
11237     } else {\r
11238         switch (gameMode) {\r
11239           case MachinePlaysWhite:\r
11240             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
11241             break;\r
11242           case MachinePlaysBlack:\r
11243             GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
11244             break;\r
11245           case EditGame:\r
11246             if (cmailMsgLoaded) {\r
11247                 TruncateGame();\r
11248                 if (WhiteOnMove(cmailOldMove)) {\r
11249                     GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
11250                 } else {\r
11251                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
11252                 }\r
11253                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;\r
11254             }\r
11255             break;\r
11256           default:\r
11257             break;\r
11258         }\r
11259     }\r
11260 }\r
11261 \r
11262 \r
11263 void\r
11264 StopObservingEvent()\r
11265 {\r
11266     /* Stop observing current games */\r
11267     SendToICS(ics_prefix);\r
11268     SendToICS("unobserve\n");\r
11269 }\r
11270 \r
11271 void\r
11272 StopExaminingEvent()\r
11273 {\r
11274     /* Stop observing current game */\r
11275     SendToICS(ics_prefix);\r
11276     SendToICS("unexamine\n");\r
11277 }\r
11278 \r
11279 void\r
11280 ForwardInner(target)\r
11281      int target;\r
11282 {\r
11283     int limit;\r
11284 \r
11285     if (appData.debugMode)\r
11286         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",\r
11287                 target, currentMove, forwardMostMove);\r
11288 \r
11289     if (gameMode == EditPosition)\r
11290       return;\r
11291 \r
11292     if (gameMode == PlayFromGameFile && !pausing)\r
11293       PauseEvent();\r
11294     \r
11295     if (gameMode == IcsExamining && pausing)\r
11296       limit = pauseExamForwardMostMove;\r
11297     else\r
11298       limit = forwardMostMove;\r
11299     \r
11300     if (target > limit) target = limit;\r
11301 \r
11302     if (target > 0 && moveList[target - 1][0]) {\r
11303         int fromX, fromY, toX, toY;\r
11304         toX = moveList[target - 1][2] - AAA;\r
11305         toY = moveList[target - 1][3] - ONE;\r
11306         if (moveList[target - 1][1] == '@') {\r
11307             if (appData.highlightLastMove) {\r
11308                 SetHighlights(-1, -1, toX, toY);\r
11309             }\r
11310         } else {\r
11311             fromX = moveList[target - 1][0] - AAA;\r
11312             fromY = moveList[target - 1][1] - ONE;\r
11313             if (target == currentMove + 1) {\r
11314                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
11315             }\r
11316             if (appData.highlightLastMove) {\r
11317                 SetHighlights(fromX, fromY, toX, toY);\r
11318             }\r
11319         }\r
11320     }\r
11321     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
11322         gameMode == Training || gameMode == PlayFromGameFile || \r
11323         gameMode == AnalyzeFile) {\r
11324         while (currentMove < target) {\r
11325             SendMoveToProgram(currentMove++, &first);\r
11326         }\r
11327     } else {\r
11328         currentMove = target;\r
11329     }\r
11330     \r
11331     if (gameMode == EditGame || gameMode == EndOfGame) {\r
11332         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11333         blackTimeRemaining = timeRemaining[1][currentMove];\r
11334     }\r
11335     DisplayBothClocks();\r
11336     DisplayMove(currentMove - 1);\r
11337     DrawPosition(FALSE, boards[currentMove]);\r
11338     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
11339     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty\r
11340         DisplayComment(currentMove - 1, commentList[currentMove]);\r
11341     }\r
11342 }\r
11343 \r
11344 \r
11345 void\r
11346 ForwardEvent()\r
11347 {\r
11348     if (gameMode == IcsExamining && !pausing) {\r
11349         SendToICS(ics_prefix);\r
11350         SendToICS("forward\n");\r
11351     } else {\r
11352         ForwardInner(currentMove + 1);\r
11353     }\r
11354 }\r
11355 \r
11356 void\r
11357 ToEndEvent()\r
11358 {\r
11359     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11360         /* to optimze, we temporarily turn off analysis mode while we feed\r
11361          * the remaining moves to the engine. Otherwise we get analysis output\r
11362          * after each move.\r
11363          */ \r
11364         if (first.analysisSupport) {\r
11365           SendToProgram("exit\nforce\n", &first);\r
11366           first.analyzing = FALSE;\r
11367         }\r
11368     }\r
11369         \r
11370     if (gameMode == IcsExamining && !pausing) {\r
11371         SendToICS(ics_prefix);\r
11372         SendToICS("forward 999999\n");\r
11373     } else {\r
11374         ForwardInner(forwardMostMove);\r
11375     }\r
11376 \r
11377     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11378         /* we have fed all the moves, so reactivate analysis mode */\r
11379         SendToProgram("analyze\n", &first);\r
11380         first.analyzing = TRUE;\r
11381         /*first.maybeThinking = TRUE;*/\r
11382         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
11383     }\r
11384 }\r
11385 \r
11386 void\r
11387 BackwardInner(target)\r
11388      int target;\r
11389 {\r
11390     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */\r
11391 \r
11392     if (appData.debugMode)\r
11393         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",\r
11394                 target, currentMove, forwardMostMove);\r
11395 \r
11396     if (gameMode == EditPosition) return;\r
11397     if (currentMove <= backwardMostMove) {\r
11398         ClearHighlights();\r
11399         DrawPosition(full_redraw, boards[currentMove]);\r
11400         return;\r
11401     }\r
11402     if (gameMode == PlayFromGameFile && !pausing)\r
11403       PauseEvent();\r
11404     \r
11405     if (moveList[target][0]) {\r
11406         int fromX, fromY, toX, toY;\r
11407         toX = moveList[target][2] - AAA;\r
11408         toY = moveList[target][3] - ONE;\r
11409         if (moveList[target][1] == '@') {\r
11410             if (appData.highlightLastMove) {\r
11411                 SetHighlights(-1, -1, toX, toY);\r
11412             }\r
11413         } else {\r
11414             fromX = moveList[target][0] - AAA;\r
11415             fromY = moveList[target][1] - ONE;\r
11416             if (target == currentMove - 1) {\r
11417                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);\r
11418             }\r
11419             if (appData.highlightLastMove) {\r
11420                 SetHighlights(fromX, fromY, toX, toY);\r
11421             }\r
11422         }\r
11423     }\r
11424     if (gameMode == EditGame || gameMode==AnalyzeMode ||\r
11425         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
11426         while (currentMove > target) {\r
11427             SendToProgram("undo\n", &first);\r
11428             currentMove--;\r
11429         }\r
11430     } else {\r
11431         currentMove = target;\r
11432     }\r
11433     \r
11434     if (gameMode == EditGame || gameMode == EndOfGame) {\r
11435         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11436         blackTimeRemaining = timeRemaining[1][currentMove];\r
11437     }\r
11438     DisplayBothClocks();\r
11439     DisplayMove(currentMove - 1);\r
11440     DrawPosition(full_redraw, boards[currentMove]);\r
11441     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
11442     // [HGM] PV info: routine tests if comment empty\r
11443     DisplayComment(currentMove - 1, commentList[currentMove]);\r
11444 }\r
11445 \r
11446 void\r
11447 BackwardEvent()\r
11448 {\r
11449     if (gameMode == IcsExamining && !pausing) {\r
11450         SendToICS(ics_prefix);\r
11451         SendToICS("backward\n");\r
11452     } else {\r
11453         BackwardInner(currentMove - 1);\r
11454     }\r
11455 }\r
11456 \r
11457 void\r
11458 ToStartEvent()\r
11459 {\r
11460     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11461         /* to optimze, we temporarily turn off analysis mode while we undo\r
11462          * all the moves. Otherwise we get analysis output after each undo.\r
11463          */ \r
11464         if (first.analysisSupport) {\r
11465           SendToProgram("exit\nforce\n", &first);\r
11466           first.analyzing = FALSE;\r
11467         }\r
11468     }\r
11469 \r
11470     if (gameMode == IcsExamining && !pausing) {\r
11471         SendToICS(ics_prefix);\r
11472         SendToICS("backward 999999\n");\r
11473     } else {\r
11474         BackwardInner(backwardMostMove);\r
11475     }\r
11476 \r
11477     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11478         /* we have fed all the moves, so reactivate analysis mode */\r
11479         SendToProgram("analyze\n", &first);\r
11480         first.analyzing = TRUE;\r
11481         /*first.maybeThinking = TRUE;*/\r
11482         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
11483     }\r
11484 }\r
11485 \r
11486 void\r
11487 ToNrEvent(int to)\r
11488 {\r
11489   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();\r
11490   if (to >= forwardMostMove) to = forwardMostMove;\r
11491   if (to <= backwardMostMove) to = backwardMostMove;\r
11492   if (to < currentMove) {\r
11493     BackwardInner(to);\r
11494   } else {\r
11495     ForwardInner(to);\r
11496   }\r
11497 }\r
11498 \r
11499 void\r
11500 RevertEvent()\r
11501 {\r
11502     if (gameMode != IcsExamining) {\r
11503         DisplayError(_("You are not examining a game"), 0);\r
11504         return;\r
11505     }\r
11506     if (pausing) {\r
11507         DisplayError(_("You can't revert while pausing"), 0);\r
11508         return;\r
11509     }\r
11510     SendToICS(ics_prefix);\r
11511     SendToICS("revert\n");\r
11512 }\r
11513 \r
11514 void\r
11515 RetractMoveEvent()\r
11516 {\r
11517     switch (gameMode) {\r
11518       case MachinePlaysWhite:\r
11519       case MachinePlaysBlack:\r
11520         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
11521             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);\r
11522             return;\r
11523         }\r
11524         if (forwardMostMove < 2) return;\r
11525         currentMove = forwardMostMove = forwardMostMove - 2;\r
11526         whiteTimeRemaining = timeRemaining[0][currentMove];\r
11527         blackTimeRemaining = timeRemaining[1][currentMove];\r
11528         DisplayBothClocks();\r
11529         DisplayMove(currentMove - 1);\r
11530         ClearHighlights();/*!! could figure this out*/\r
11531         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */\r
11532         SendToProgram("remove\n", &first);\r
11533         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */\r
11534         break;\r
11535 \r
11536       case BeginningOfGame:\r
11537       default:\r
11538         break;\r
11539 \r
11540       case IcsPlayingWhite:\r
11541       case IcsPlayingBlack:\r
11542         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {\r
11543             SendToICS(ics_prefix);\r
11544             SendToICS("takeback 2\n");\r
11545         } else {\r
11546             SendToICS(ics_prefix);\r
11547             SendToICS("takeback 1\n");\r
11548         }\r
11549         break;\r
11550     }\r
11551 }\r
11552 \r
11553 void\r
11554 MoveNowEvent()\r
11555 {\r
11556     ChessProgramState *cps;\r
11557 \r
11558     switch (gameMode) {\r
11559       case MachinePlaysWhite:\r
11560         if (!WhiteOnMove(forwardMostMove)) {\r
11561             DisplayError(_("It is your turn"), 0);\r
11562             return;\r
11563         }\r
11564         cps = &first;\r
11565         break;\r
11566       case MachinePlaysBlack:\r
11567         if (WhiteOnMove(forwardMostMove)) {\r
11568             DisplayError(_("It is your turn"), 0);\r
11569             return;\r
11570         }\r
11571         cps = &first;\r
11572         break;\r
11573       case TwoMachinesPlay:\r
11574         if (WhiteOnMove(forwardMostMove) ==\r
11575             (first.twoMachinesColor[0] == 'w')) {\r
11576             cps = &first;\r
11577         } else {\r
11578             cps = &second;\r
11579         }\r
11580         break;\r
11581       case BeginningOfGame:\r
11582       default:\r
11583         return;\r
11584     }\r
11585     SendToProgram("?\n", cps);\r
11586 }\r
11587 \r
11588 void\r
11589 TruncateGameEvent()\r
11590 {\r
11591     EditGameEvent();\r
11592     if (gameMode != EditGame) return;\r
11593     TruncateGame();\r
11594 }\r
11595 \r
11596 void\r
11597 TruncateGame()\r
11598 {\r
11599     if (forwardMostMove > currentMove) {\r
11600         if (gameInfo.resultDetails != NULL) {\r
11601             free(gameInfo.resultDetails);\r
11602             gameInfo.resultDetails = NULL;\r
11603             gameInfo.result = GameUnfinished;\r
11604         }\r
11605         forwardMostMove = currentMove;\r
11606         HistorySet(parseList, backwardMostMove, forwardMostMove,\r
11607                    currentMove-1);\r
11608     }\r
11609 }\r
11610 \r
11611 void\r
11612 HintEvent()\r
11613 {\r
11614     if (appData.noChessProgram) return;\r
11615     switch (gameMode) {\r
11616       case MachinePlaysWhite:\r
11617         if (WhiteOnMove(forwardMostMove)) {\r
11618             DisplayError(_("Wait until your turn"), 0);\r
11619             return;\r
11620         }\r
11621         break;\r
11622       case BeginningOfGame:\r
11623       case MachinePlaysBlack:\r
11624         if (!WhiteOnMove(forwardMostMove)) {\r
11625             DisplayError(_("Wait until your turn"), 0);\r
11626             return;\r
11627         }\r
11628         break;\r
11629       default:\r
11630         DisplayError(_("No hint available"), 0);\r
11631         return;\r
11632     }\r
11633     SendToProgram("hint\n", &first);\r
11634     hintRequested = TRUE;\r
11635 }\r
11636 \r
11637 void\r
11638 BookEvent()\r
11639 {\r
11640     if (appData.noChessProgram) return;\r
11641     switch (gameMode) {\r
11642       case MachinePlaysWhite:\r
11643         if (WhiteOnMove(forwardMostMove)) {\r
11644             DisplayError(_("Wait until your turn"), 0);\r
11645             return;\r
11646         }\r
11647         break;\r
11648       case BeginningOfGame:\r
11649       case MachinePlaysBlack:\r
11650         if (!WhiteOnMove(forwardMostMove)) {\r
11651             DisplayError(_("Wait until your turn"), 0);\r
11652             return;\r
11653         }\r
11654         break;\r
11655       case EditPosition:\r
11656         EditPositionDone();\r
11657         break;\r
11658       case TwoMachinesPlay:\r
11659         return;\r
11660       default:\r
11661         break;\r
11662     }\r
11663     SendToProgram("bk\n", &first);\r
11664     bookOutput[0] = NULLCHAR;\r
11665     bookRequested = TRUE;\r
11666 }\r
11667 \r
11668 void\r
11669 AboutGameEvent()\r
11670 {\r
11671     char *tags = PGNTags(&gameInfo);\r
11672     TagsPopUp(tags, CmailMsg());\r
11673     free(tags);\r
11674 }\r
11675 \r
11676 /* end button procedures */\r
11677 \r
11678 void\r
11679 PrintPosition(fp, move)\r
11680      FILE *fp;\r
11681      int move;\r
11682 {\r
11683     int i, j;\r
11684     \r
11685     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
11686         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
11687             char c = PieceToChar(boards[move][i][j]);\r
11688             fputc(c == 'x' ? '.' : c, fp);\r
11689             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);\r
11690         }\r
11691     }\r
11692     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))\r
11693       fprintf(fp, "white to play\n");\r
11694     else\r
11695       fprintf(fp, "black to play\n");\r
11696 }\r
11697 \r
11698 void\r
11699 PrintOpponents(fp)\r
11700      FILE *fp;\r
11701 {\r
11702     if (gameInfo.white != NULL) {\r
11703         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);\r
11704     } else {\r
11705         fprintf(fp, "\n");\r
11706     }\r
11707 }\r
11708 \r
11709 /* Find last component of program's own name, using some heuristics */\r
11710 void\r
11711 TidyProgramName(prog, host, buf)\r
11712      char *prog, *host, buf[MSG_SIZ];\r
11713 {\r
11714     char *p, *q;\r
11715     int local = (strcmp(host, "localhost") == 0);\r
11716     while (!local && (p = strchr(prog, ';')) != NULL) {\r
11717         p++;\r
11718         while (*p == ' ') p++;\r
11719         prog = p;\r
11720     }\r
11721     if (*prog == '"' || *prog == '\'') {\r
11722         q = strchr(prog + 1, *prog);\r
11723     } else {\r
11724         q = strchr(prog, ' ');\r
11725     }\r
11726     if (q == NULL) q = prog + strlen(prog);\r
11727     p = q;\r
11728     while (p >= prog && *p != '/' && *p != '\\') p--;\r
11729     p++;\r
11730     if(p == prog && *p == '"') p++;\r
11731     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;\r
11732     memcpy(buf, p, q - p);\r
11733     buf[q - p] = NULLCHAR;\r
11734     if (!local) {\r
11735         strcat(buf, "@");\r
11736         strcat(buf, host);\r
11737     }\r
11738 }\r
11739 \r
11740 char *\r
11741 TimeControlTagValue()\r
11742 {\r
11743     char buf[MSG_SIZ];\r
11744     if (!appData.clockMode) {\r
11745         strcpy(buf, "-");\r
11746     } else if (movesPerSession > 0) {\r
11747         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);\r
11748     } else if (timeIncrement == 0) {\r
11749         sprintf(buf, "%ld", timeControl/1000);\r
11750     } else {\r
11751         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);\r
11752     }\r
11753     return StrSave(buf);\r
11754 }\r
11755 \r
11756 void\r
11757 SetGameInfo()\r
11758 {\r
11759     /* This routine is used only for certain modes */\r
11760     VariantClass v = gameInfo.variant;\r
11761     ClearGameInfo(&gameInfo);\r
11762     gameInfo.variant = v;\r
11763 \r
11764     switch (gameMode) {\r
11765       case MachinePlaysWhite:\r
11766         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11767         gameInfo.site = StrSave(HostName());\r
11768         gameInfo.date = PGNDate();\r
11769         gameInfo.round = StrSave("-");\r
11770         gameInfo.white = StrSave(first.tidy);\r
11771         gameInfo.black = StrSave(UserName());\r
11772         gameInfo.timeControl = TimeControlTagValue();\r
11773         break;\r
11774 \r
11775       case MachinePlaysBlack:\r
11776         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11777         gameInfo.site = StrSave(HostName());\r
11778         gameInfo.date = PGNDate();\r
11779         gameInfo.round = StrSave("-");\r
11780         gameInfo.white = StrSave(UserName());\r
11781         gameInfo.black = StrSave(first.tidy);\r
11782         gameInfo.timeControl = TimeControlTagValue();\r
11783         break;\r
11784 \r
11785       case TwoMachinesPlay:\r
11786         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11787         gameInfo.site = StrSave(HostName());\r
11788         gameInfo.date = PGNDate();\r
11789         if (matchGame > 0) {\r
11790             char buf[MSG_SIZ];\r
11791             sprintf(buf, "%d", matchGame);\r
11792             gameInfo.round = StrSave(buf);\r
11793         } else {\r
11794             gameInfo.round = StrSave("-");\r
11795         }\r
11796         if (first.twoMachinesColor[0] == 'w') {\r
11797             gameInfo.white = StrSave(first.tidy);\r
11798             gameInfo.black = StrSave(second.tidy);\r
11799         } else {\r
11800             gameInfo.white = StrSave(second.tidy);\r
11801             gameInfo.black = StrSave(first.tidy);\r
11802         }\r
11803         gameInfo.timeControl = TimeControlTagValue();\r
11804         break;\r
11805 \r
11806       case EditGame:\r
11807         gameInfo.event = StrSave("Edited game");\r
11808         gameInfo.site = StrSave(HostName());\r
11809         gameInfo.date = PGNDate();\r
11810         gameInfo.round = StrSave("-");\r
11811         gameInfo.white = StrSave("-");\r
11812         gameInfo.black = StrSave("-");\r
11813         break;\r
11814 \r
11815       case EditPosition:\r
11816         gameInfo.event = StrSave("Edited position");\r
11817         gameInfo.site = StrSave(HostName());\r
11818         gameInfo.date = PGNDate();\r
11819         gameInfo.round = StrSave("-");\r
11820         gameInfo.white = StrSave("-");\r
11821         gameInfo.black = StrSave("-");\r
11822         break;\r
11823 \r
11824       case IcsPlayingWhite:\r
11825       case IcsPlayingBlack:\r
11826       case IcsObserving:\r
11827       case IcsExamining:\r
11828         break;\r
11829 \r
11830       case PlayFromGameFile:\r
11831         gameInfo.event = StrSave("Game from non-PGN file");\r
11832         gameInfo.site = StrSave(HostName());\r
11833         gameInfo.date = PGNDate();\r
11834         gameInfo.round = StrSave("-");\r
11835         gameInfo.white = StrSave("?");\r
11836         gameInfo.black = StrSave("?");\r
11837         break;\r
11838 \r
11839       default:\r
11840         break;\r
11841     }\r
11842 }\r
11843 \r
11844 void\r
11845 ReplaceComment(index, text)\r
11846      int index;\r
11847      char *text;\r
11848 {\r
11849     int len;\r
11850 \r
11851     while (*text == '\n') text++;\r
11852     len = strlen(text);\r
11853     while (len > 0 && text[len - 1] == '\n') len--;\r
11854 \r
11855     if (commentList[index] != NULL)\r
11856       free(commentList[index]);\r
11857 \r
11858     if (len == 0) {\r
11859         commentList[index] = NULL;\r
11860         return;\r
11861     }\r
11862     commentList[index] = (char *) malloc(len + 2);\r
11863     strncpy(commentList[index], text, len);\r
11864     commentList[index][len] = '\n';\r
11865     commentList[index][len + 1] = NULLCHAR;\r
11866 }\r
11867 \r
11868 void\r
11869 CrushCRs(text)\r
11870      char *text;\r
11871 {\r
11872   char *p = text;\r
11873   char *q = text;\r
11874   char ch;\r
11875 \r
11876   do {\r
11877     ch = *p++;\r
11878     if (ch == '\r') continue;\r
11879     *q++ = ch;\r
11880   } while (ch != '\0');\r
11881 }\r
11882 \r
11883 void\r
11884 AppendComment(index, text)\r
11885      int index;\r
11886      char *text;\r
11887 {\r
11888     int oldlen, len;\r
11889     char *old;\r
11890 \r
11891     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */\r
11892 \r
11893     CrushCRs(text);\r
11894     while (*text == '\n') text++;\r
11895     len = strlen(text);\r
11896     while (len > 0 && text[len - 1] == '\n') len--;\r
11897 \r
11898     if (len == 0) return;\r
11899 \r
11900     if (commentList[index] != NULL) {\r
11901         old = commentList[index];\r
11902         oldlen = strlen(old);\r
11903         commentList[index] = (char *) malloc(oldlen + len + 2);\r
11904         strcpy(commentList[index], old);\r
11905         free(old);\r
11906         strncpy(&commentList[index][oldlen], text, len);\r
11907         commentList[index][oldlen + len] = '\n';\r
11908         commentList[index][oldlen + len + 1] = NULLCHAR;\r
11909     } else {\r
11910         commentList[index] = (char *) malloc(len + 2);\r
11911         strncpy(commentList[index], text, len);\r
11912         commentList[index][len] = '\n';\r
11913         commentList[index][len + 1] = NULLCHAR;\r
11914     }\r
11915 }\r
11916 \r
11917 static char * FindStr( char * text, char * sub_text )\r
11918 {\r
11919     char * result = strstr( text, sub_text );\r
11920 \r
11921     if( result != NULL ) {\r
11922         result += strlen( sub_text );\r
11923     }\r
11924 \r
11925     return result;\r
11926 }\r
11927 \r
11928 /* [AS] Try to extract PV info from PGN comment */\r
11929 /* [HGM] PV time: and then remove it, to prevent it appearing twice */\r
11930 char *GetInfoFromComment( int index, char * text )\r
11931 {\r
11932     char * sep = text;\r
11933 \r
11934     if( text != NULL && index > 0 ) {\r
11935         int score = 0;\r
11936         int depth = 0;\r
11937         int time = -1, sec = 0, deci;\r
11938         char * s_eval = FindStr( text, "[%eval " );\r
11939         char * s_emt = FindStr( text, "[%emt " );\r
11940 \r
11941         if( s_eval != NULL || s_emt != NULL ) {\r
11942             /* New style */\r
11943             char delim;\r
11944 \r
11945             if( s_eval != NULL ) {\r
11946                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {\r
11947                     return text;\r
11948                 }\r
11949 \r
11950                 if( delim != ']' ) {\r
11951                     return text;\r
11952                 }\r
11953             }\r
11954 \r
11955             if( s_emt != NULL ) {\r
11956             }\r
11957         }\r
11958         else {\r
11959             /* We expect something like: [+|-]nnn.nn/dd */\r
11960             int score_lo = 0;\r
11961 \r
11962             sep = strchr( text, '/' );\r
11963             if( sep == NULL || sep < (text+4) ) {\r
11964                 return text;\r
11965             }\r
11966 \r
11967             time = -1; sec = -1; deci = -1;\r
11968             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&\r
11969                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&\r
11970                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&\r
11971                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {\r
11972                 return text;\r
11973             }\r
11974 \r
11975             if( score_lo < 0 || score_lo >= 100 ) {\r
11976                 return text;\r
11977             }\r
11978 \r
11979             if(sec >= 0) time = 600*time + 10*sec; else\r
11980             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec\r
11981 \r
11982             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;\r
11983 \r
11984             /* [HGM] PV time: now locate end of PV info */\r
11985             while( *++sep >= '0' && *sep <= '9'); // strip depth\r
11986             if(time >= 0)\r
11987             while( *++sep >= '0' && *sep <= '9'); // strip time\r
11988             if(sec >= 0)\r
11989             while( *++sep >= '0' && *sep <= '9'); // strip seconds\r
11990             if(deci >= 0)\r
11991             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds\r
11992             while(*sep == ' ') sep++;\r
11993         }\r
11994 \r
11995         if( depth <= 0 ) {\r
11996             return text;\r
11997         }\r
11998 \r
11999         if( time < 0 ) {\r
12000             time = -1;\r
12001         }\r
12002 \r
12003         pvInfoList[index-1].depth = depth;\r
12004         pvInfoList[index-1].score = score;\r
12005         pvInfoList[index-1].time  = 10*time; // centi-sec\r
12006     }\r
12007     return sep;\r
12008 }\r
12009 \r
12010 void\r
12011 SendToProgram(message, cps)\r
12012      char *message;\r
12013      ChessProgramState *cps;\r
12014 {\r
12015     int count, outCount, error;\r
12016     char buf[MSG_SIZ];\r
12017 \r
12018     if (cps->pr == NULL) return;\r
12019     Attention(cps);\r
12020     \r
12021     if (appData.debugMode) {\r
12022         TimeMark now;\r
12023         GetTimeMark(&now);\r
12024         fprintf(debugFP, "%ld >%-6s: %s", \r
12025                 SubtractTimeMarks(&now, &programStartTime),\r
12026                 cps->which, message);\r
12027     }\r
12028     \r
12029     count = strlen(message);\r
12030     outCount = OutputToProcess(cps->pr, message, count, &error);\r
12031     if (outCount < count && !exiting \r
12032                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */\r
12033         sprintf(buf, _("Error writing to %s chess program"), cps->which);\r
12034         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
12035             if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
12036                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
12037                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);\r
12038             } else {\r
12039                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
12040             }\r
12041             gameInfo.resultDetails = buf;\r
12042         }\r
12043         DisplayFatalError(buf, error, 1);\r
12044     }\r
12045 }\r
12046 \r
12047 void\r
12048 ReceiveFromProgram(isr, closure, message, count, error)\r
12049      InputSourceRef isr;\r
12050      VOIDSTAR closure;\r
12051      char *message;\r
12052      int count;\r
12053      int error;\r
12054 {\r
12055     char *end_str;\r
12056     char buf[MSG_SIZ];\r
12057     ChessProgramState *cps = (ChessProgramState *)closure;\r
12058 \r
12059     if (isr != cps->isr) return; /* Killed intentionally */\r
12060     if (count <= 0) {\r
12061         if (count == 0) {\r
12062             sprintf(buf,\r
12063                     _("Error: %s chess program (%s) exited unexpectedly"),\r
12064                     cps->which, cps->program);\r
12065         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
12066                 if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
12067                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
12068                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);\r
12069                 } else {\r
12070                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
12071                 }\r
12072                 gameInfo.resultDetails = buf;\r
12073             }\r
12074             RemoveInputSource(cps->isr);\r
12075             DisplayFatalError(buf, 0, 1);\r
12076         } else {\r
12077             sprintf(buf,\r
12078                     _("Error reading from %s chess program (%s)"),\r
12079                     cps->which, cps->program);\r
12080             RemoveInputSource(cps->isr);\r
12081 \r
12082             /* [AS] Program is misbehaving badly... kill it */\r
12083             if( count == -2 ) {\r
12084                 DestroyChildProcess( cps->pr, 9 );\r
12085                 cps->pr = NoProc;\r
12086             }\r
12087 \r
12088             DisplayFatalError(buf, error, 1);\r
12089         }\r
12090         return;\r
12091     }\r
12092     \r
12093     if ((end_str = strchr(message, '\r')) != NULL)\r
12094       *end_str = NULLCHAR;\r
12095     if ((end_str = strchr(message, '\n')) != NULL)\r
12096       *end_str = NULLCHAR;\r
12097     \r
12098     if (appData.debugMode) {\r
12099         TimeMark now; int print = 1;\r
12100         char *quote = ""; char c; int i;\r
12101 \r
12102         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */\r
12103                 char start = message[0];\r
12104                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing\r
12105                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && \r
12106                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&\r
12107                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&\r
12108                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&\r
12109                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&\r
12110                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')\r
12111                         { quote = "# "; print = (appData.engineComments == 2); }\r
12112                 message[0] = start; // restore original message\r
12113         }\r
12114         if(print) {\r
12115                 GetTimeMark(&now);\r
12116                 fprintf(debugFP, "%ld <%-6s: %s%s\n", \r
12117                         SubtractTimeMarks(&now, &programStartTime), cps->which, \r
12118                         quote,\r
12119                         message);\r
12120         }\r
12121     }\r
12122 \r
12123     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */\r
12124     if (appData.icsEngineAnalyze) {\r
12125         if (strstr(message, "whisper") != NULL ||\r
12126              strstr(message, "kibitz") != NULL || \r
12127             strstr(message, "tellics") != NULL) return;\r
12128     }\r
12129 \r
12130     HandleMachineMove(message, cps);\r
12131 }\r
12132 \r
12133 \r
12134 void\r
12135 SendTimeControl(cps, mps, tc, inc, sd, st)\r
12136      ChessProgramState *cps;\r
12137      int mps, inc, sd, st;\r
12138      long tc;\r
12139 {\r
12140     char buf[MSG_SIZ];\r
12141     int seconds;\r
12142 \r
12143     if( timeControl_2 > 0 ) {\r
12144         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {\r
12145             tc = timeControl_2;\r
12146         }\r
12147     }\r
12148     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */\r
12149     inc /= cps->timeOdds;\r
12150     st  /= cps->timeOdds;\r
12151 \r
12152     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */\r
12153 \r
12154     if (st > 0) {\r
12155       /* Set exact time per move, normally using st command */\r
12156       if (cps->stKludge) {\r
12157         /* GNU Chess 4 has no st command; uses level in a nonstandard way */\r
12158         seconds = st % 60;\r
12159         if (seconds == 0) {\r
12160           sprintf(buf, "level 1 %d\n", st/60);\r
12161         } else {\r
12162           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);\r
12163         }\r
12164       } else {\r
12165         sprintf(buf, "st %d\n", st);\r
12166       }\r
12167     } else {\r
12168       /* Set conventional or incremental time control, using level command */\r
12169       if (seconds == 0) {\r
12170         /* Note old gnuchess bug -- minutes:seconds used to not work.\r
12171            Fixed in later versions, but still avoid :seconds\r
12172            when seconds is 0. */\r
12173         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);\r
12174       } else {\r
12175         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,\r
12176                 seconds, inc/1000);\r
12177       }\r
12178     }\r
12179     SendToProgram(buf, cps);\r
12180 \r
12181     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */\r
12182     /* Orthogonally, limit search to given depth */\r
12183     if (sd > 0) {\r
12184       if (cps->sdKludge) {\r
12185         sprintf(buf, "depth\n%d\n", sd);\r
12186       } else {\r
12187         sprintf(buf, "sd %d\n", sd);\r
12188       }\r
12189       SendToProgram(buf, cps);\r
12190     }\r
12191 \r
12192     if(cps->nps > 0) { /* [HGM] nps */\r
12193         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!\r
12194         else {\r
12195                 sprintf(buf, "nps %d\n", cps->nps);\r
12196               SendToProgram(buf, cps);\r
12197         }\r
12198     }\r
12199 }\r
12200 \r
12201 ChessProgramState *WhitePlayer()\r
12202 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */\r
12203 {\r
12204     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || \r
12205        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)\r
12206         return &second;\r
12207     return &first;\r
12208 }\r
12209 \r
12210 void\r
12211 SendTimeRemaining(cps, machineWhite)\r
12212      ChessProgramState *cps;\r
12213      int /*boolean*/ machineWhite;\r
12214 {\r
12215     char message[MSG_SIZ];\r
12216     long time, otime;\r
12217 \r
12218     /* Note: this routine must be called when the clocks are stopped\r
12219        or when they have *just* been set or switched; otherwise\r
12220        it will be off by the time since the current tick started.\r
12221     */\r
12222     if (machineWhite) {\r
12223         time = whiteTimeRemaining / 10;\r
12224         otime = blackTimeRemaining / 10;\r
12225     } else {\r
12226         time = blackTimeRemaining / 10;\r
12227         otime = whiteTimeRemaining / 10;\r
12228     }\r
12229     /* [HGM] translate opponent's time by time-odds factor */\r
12230     otime = (otime * cps->other->timeOdds) / cps->timeOdds;\r
12231     if (appData.debugMode) {\r
12232         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);\r
12233     }\r
12234 \r
12235     if (time <= 0) time = 1;\r
12236     if (otime <= 0) otime = 1;\r
12237     \r
12238     sprintf(message, "time %ld\n", time);\r
12239     SendToProgram(message, cps);\r
12240 \r
12241     sprintf(message, "otim %ld\n", otime);\r
12242     SendToProgram(message, cps);\r
12243 }\r
12244 \r
12245 int\r
12246 BoolFeature(p, name, loc, cps)\r
12247      char **p;\r
12248      char *name;\r
12249      int *loc;\r
12250      ChessProgramState *cps;\r
12251 {\r
12252   char buf[MSG_SIZ];\r
12253   int len = strlen(name);\r
12254   int val;\r
12255   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
12256     (*p) += len + 1;\r
12257     sscanf(*p, "%d", &val);\r
12258     *loc = (val != 0);\r
12259     while (**p && **p != ' ') (*p)++;\r
12260     sprintf(buf, "accepted %s\n", name);\r
12261     SendToProgram(buf, cps);\r
12262     return TRUE;\r
12263   }\r
12264   return FALSE;\r
12265 }\r
12266 \r
12267 int\r
12268 IntFeature(p, name, loc, cps)\r
12269      char **p;\r
12270      char *name;\r
12271      int *loc;\r
12272      ChessProgramState *cps;\r
12273 {\r
12274   char buf[MSG_SIZ];\r
12275   int len = strlen(name);\r
12276   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
12277     (*p) += len + 1;\r
12278     sscanf(*p, "%d", loc);\r
12279     while (**p && **p != ' ') (*p)++;\r
12280     sprintf(buf, "accepted %s\n", name);\r
12281     SendToProgram(buf, cps);\r
12282     return TRUE;\r
12283   }\r
12284   return FALSE;\r
12285 }\r
12286 \r
12287 int\r
12288 StringFeature(p, name, loc, cps)\r
12289      char **p;\r
12290      char *name;\r
12291      char loc[];\r
12292      ChessProgramState *cps;\r
12293 {\r
12294   char buf[MSG_SIZ];\r
12295   int len = strlen(name);\r
12296   if (strncmp((*p), name, len) == 0\r
12297       && (*p)[len] == '=' && (*p)[len+1] == '\"') {\r
12298     (*p) += len + 2;\r
12299     sscanf(*p, "%[^\"]", loc);\r
12300     while (**p && **p != '\"') (*p)++;\r
12301     if (**p == '\"') (*p)++;\r
12302     sprintf(buf, "accepted %s\n", name);\r
12303     SendToProgram(buf, cps);\r
12304     return TRUE;\r
12305   }\r
12306   return FALSE;\r
12307 }\r
12308 \r
12309 int \r
12310 ParseOption(Option *opt, ChessProgramState *cps)\r
12311 // [HGM] options: process the string that defines an engine option, and determine\r
12312 // name, type, default value, and allowed value range\r
12313 {\r
12314         char *p, *q, buf[MSG_SIZ];\r
12315         int n, min = (-1)<<31, max = 1<<31, def;\r
12316 \r
12317         if(p = strstr(opt->name, " -spin ")) {\r
12318             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;\r
12319             if(max < min) max = min; // enforce consistency\r
12320             if(def < min) def = min;\r
12321             if(def > max) def = max;\r
12322             opt->value = def;\r
12323             opt->min = min;\r
12324             opt->max = max;\r
12325             opt->type = Spin;\r
12326         } else if(p = strstr(opt->name, " -string ")) {\r
12327             opt->textValue = p+9;\r
12328             opt->type = TextBox;\r
12329         } else if(p = strstr(opt->name, " -check ")) {\r
12330             if(sscanf(p, " -check %d", &def) < 1) return FALSE;\r
12331             opt->value = (def != 0);\r
12332             opt->type = CheckBox;\r
12333         } else if(p = strstr(opt->name, " -combo ")) {\r
12334             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type\r
12335             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices\r
12336             opt->value = n = 0;\r
12337             while(q = StrStr(q, " /// ")) {\r
12338                 n++; *q = 0;    // count choices, and null-terminate each of them\r
12339                 q += 5;\r
12340                 if(*q == '*') { // remember default, which is marked with * prefix\r
12341                     q++;\r
12342                     opt->value = n;\r
12343                 }\r
12344                 cps->comboList[cps->comboCnt++] = q;\r
12345             }\r
12346             cps->comboList[cps->comboCnt++] = NULL;\r
12347             opt->max = n + 1;\r
12348             opt->type = ComboBox;\r
12349         } else if(p = strstr(opt->name, " -button")) {\r
12350             opt->type = Button;\r
12351         } else if(p = strstr(opt->name, " -save")) {\r
12352             opt->type = SaveButton;\r
12353         } else return FALSE;\r
12354         *p = 0; // terminate option name\r
12355         // now look if the command-line options define a setting for this engine option.\r
12356         if(cps->optionSettings && cps->optionSettings[0])\r
12357             p = strstr(cps->optionSettings, opt->name); else p = NULL;\r
12358         if(p && (p == cps->optionSettings || p[-1] == ',')) {\r
12359                 sprintf(buf, "option %s", p);\r
12360                 if(p = strstr(buf, ",")) *p = 0;\r
12361                 strcat(buf, "\n");\r
12362                 SendToProgram(buf, cps);\r
12363         }\r
12364         return TRUE;\r
12365 }\r
12366 \r
12367 void\r
12368 FeatureDone(cps, val)\r
12369      ChessProgramState* cps;\r
12370      int val;\r
12371 {\r
12372   DelayedEventCallback cb = GetDelayedEvent();\r
12373   if ((cb == InitBackEnd3 && cps == &first) ||\r
12374       (cb == TwoMachinesEventIfReady && cps == &second)) {\r
12375     CancelDelayedEvent();\r
12376     ScheduleDelayedEvent(cb, val ? 1 : 3600000);\r
12377   }\r
12378   cps->initDone = val;\r
12379 }\r
12380 \r
12381 /* Parse feature command from engine */\r
12382 void\r
12383 ParseFeatures(args, cps)\r
12384      char* args;\r
12385      ChessProgramState *cps;  \r
12386 {\r
12387   char *p = args;\r
12388   char *q;\r
12389   int val;\r
12390   char buf[MSG_SIZ];\r
12391 \r
12392   for (;;) {\r
12393     while (*p == ' ') p++;\r
12394     if (*p == NULLCHAR) return;\r
12395 \r
12396     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;\r
12397     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    \r
12398     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    \r
12399     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    \r
12400     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    \r
12401     if (BoolFeature(&p, "reuse", &val, cps)) {\r
12402       /* Engine can disable reuse, but can't enable it if user said no */\r
12403       if (!val) cps->reuse = FALSE;\r
12404       continue;\r
12405     }\r
12406     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;\r
12407     if (StringFeature(&p, "myname", &cps->tidy, cps)) {\r
12408       if (gameMode == TwoMachinesPlay) {\r
12409         DisplayTwoMachinesTitle();\r
12410       } else {\r
12411         DisplayTitle("");\r
12412       }\r
12413       continue;\r
12414     }\r
12415     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;\r
12416     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;\r
12417     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;\r
12418     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;\r
12419     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;\r
12420     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;\r
12421     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;\r
12422     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;\r
12423     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */\r
12424     if (IntFeature(&p, "done", &val, cps)) {\r
12425       FeatureDone(cps, val);\r
12426       continue;\r
12427     }\r
12428     /* Added by Tord: */\r
12429     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;\r
12430     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;\r
12431     /* End of additions by Tord */\r
12432 \r
12433     /* [HGM] added features: */\r
12434     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;\r
12435     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;\r
12436     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;\r
12437     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;\r
12438     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;\r
12439     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;\r
12440     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {\r
12441         ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature\r
12442         if(cps->nrOptions >= MAX_OPTIONS) {\r
12443             cps->nrOptions--;\r
12444             sprintf(buf, "%s engine has too many options\n", cps->which);\r
12445             DisplayError(buf, 0);\r
12446         }\r
12447         continue;\r
12448     }\r
12449     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;\r
12450     /* End of additions by HGM */\r
12451 \r
12452     /* unknown feature: complain and skip */\r
12453     q = p;\r
12454     while (*q && *q != '=') q++;\r
12455     sprintf(buf, "rejected %.*s\n", q-p, p);\r
12456     SendToProgram(buf, cps);\r
12457     p = q;\r
12458     if (*p == '=') {\r
12459       p++;\r
12460       if (*p == '\"') {\r
12461         p++;\r
12462         while (*p && *p != '\"') p++;\r
12463         if (*p == '\"') p++;\r
12464       } else {\r
12465         while (*p && *p != ' ') p++;\r
12466       }\r
12467     }\r
12468   }\r
12469 \r
12470 }\r
12471 \r
12472 void\r
12473 PeriodicUpdatesEvent(newState)\r
12474      int newState;\r
12475 {\r
12476     if (newState == appData.periodicUpdates)\r
12477       return;\r
12478 \r
12479     appData.periodicUpdates=newState;\r
12480 \r
12481     /* Display type changes, so update it now */\r
12482     DisplayAnalysis();\r
12483 \r
12484     /* Get the ball rolling again... */\r
12485     if (newState) {\r
12486         AnalysisPeriodicEvent(1);\r
12487         StartAnalysisClock();\r
12488     }\r
12489 }\r
12490 \r
12491 void\r
12492 PonderNextMoveEvent(newState)\r
12493      int newState;\r
12494 {\r
12495     if (newState == appData.ponderNextMove) return;\r
12496     if (gameMode == EditPosition) EditPositionDone();\r
12497     if (newState) {\r
12498         SendToProgram("hard\n", &first);\r
12499         if (gameMode == TwoMachinesPlay) {\r
12500             SendToProgram("hard\n", &second);\r
12501         }\r
12502     } else {\r
12503         SendToProgram("easy\n", &first);\r
12504         thinkOutput[0] = NULLCHAR;\r
12505         if (gameMode == TwoMachinesPlay) {\r
12506             SendToProgram("easy\n", &second);\r
12507         }\r
12508     }\r
12509     appData.ponderNextMove = newState;\r
12510 }\r
12511 \r
12512 void\r
12513 NewSettingEvent(option, command, value)\r
12514      char *command;\r
12515      int option, value;\r
12516 {\r
12517     char buf[MSG_SIZ];\r
12518 \r
12519     if (gameMode == EditPosition) EditPositionDone();\r
12520     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);\r
12521     SendToProgram(buf, &first);\r
12522     if (gameMode == TwoMachinesPlay) {\r
12523         SendToProgram(buf, &second);\r
12524     }\r
12525 }\r
12526 \r
12527 void\r
12528 ShowThinkingEvent()\r
12529 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup\r
12530 {\r
12531     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated\r
12532     int newState = appData.showThinking\r
12533         // [HGM] thinking: other features now need thinking output as well\r
12534         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();\r
12535     \r
12536     if (oldState == newState) return;\r
12537     oldState = newState;\r
12538     if (gameMode == EditPosition) EditPositionDone();\r
12539     if (oldState) {\r
12540         SendToProgram("post\n", &first);\r
12541         if (gameMode == TwoMachinesPlay) {\r
12542             SendToProgram("post\n", &second);\r
12543         }\r
12544     } else {\r
12545         SendToProgram("nopost\n", &first);\r
12546         thinkOutput[0] = NULLCHAR;\r
12547         if (gameMode == TwoMachinesPlay) {\r
12548             SendToProgram("nopost\n", &second);\r
12549         }\r
12550     }\r
12551 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!\r
12552 }\r
12553 \r
12554 void\r
12555 AskQuestionEvent(title, question, replyPrefix, which)\r
12556      char *title; char *question; char *replyPrefix; char *which;\r
12557 {\r
12558   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;\r
12559   if (pr == NoProc) return;\r
12560   AskQuestion(title, question, replyPrefix, pr);\r
12561 }\r
12562 \r
12563 void\r
12564 DisplayMove(moveNumber)\r
12565      int moveNumber;\r
12566 {\r
12567     char message[MSG_SIZ];\r
12568     char res[MSG_SIZ];\r
12569     char cpThinkOutput[MSG_SIZ];\r
12570 \r
12571     if(appData.noGUI) return; // [HGM] fast: suppress display of moves\r
12572     \r
12573     if (moveNumber == forwardMostMove - 1 || \r
12574         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
12575 \r
12576         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));\r
12577 \r
12578         if (strchr(cpThinkOutput, '\n')) {\r
12579             *strchr(cpThinkOutput, '\n') = NULLCHAR;\r
12580         }\r
12581     } else {\r
12582         *cpThinkOutput = NULLCHAR;\r
12583     }\r
12584 \r
12585     /* [AS] Hide thinking from human user */\r
12586     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {\r
12587         *cpThinkOutput = NULLCHAR;\r
12588         if( thinkOutput[0] != NULLCHAR ) {\r
12589             int i;\r
12590 \r
12591             for( i=0; i<=hiddenThinkOutputState; i++ ) {\r
12592                 cpThinkOutput[i] = '.';\r
12593             }\r
12594             cpThinkOutput[i] = NULLCHAR;\r
12595             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;\r
12596         }\r
12597     }\r
12598 \r
12599     if (moveNumber == forwardMostMove - 1 &&\r
12600         gameInfo.resultDetails != NULL) {\r
12601         if (gameInfo.resultDetails[0] == NULLCHAR) {\r
12602             sprintf(res, " %s", PGNResult(gameInfo.result));\r
12603         } else {\r
12604             sprintf(res, " {%s} %s",\r
12605                     gameInfo.resultDetails, PGNResult(gameInfo.result));\r
12606         }\r
12607     } else {\r
12608         res[0] = NULLCHAR;\r
12609     }\r
12610 \r
12611     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
12612         DisplayMessage(res, cpThinkOutput);\r
12613     } else {\r
12614         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,\r
12615                 WhiteOnMove(moveNumber) ? " " : ".. ",\r
12616                 parseList[moveNumber], res);\r
12617         DisplayMessage(message, cpThinkOutput);\r
12618     }\r
12619 }\r
12620 \r
12621 void\r
12622 DisplayAnalysisText(text)\r
12623      char *text;\r
12624 {\r
12625     char buf[MSG_SIZ];\r
12626 \r
12627     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile \r
12628                || appData.icsEngineAnalyze) {\r
12629         sprintf(buf, "Analysis (%s)", first.tidy);\r
12630         AnalysisPopUp(buf, text);\r
12631     }\r
12632 }\r
12633 \r
12634 static int\r
12635 only_one_move(str)\r
12636      char *str;\r
12637 {\r
12638     while (*str && isspace(*str)) ++str;\r
12639     while (*str && !isspace(*str)) ++str;\r
12640     if (!*str) return 1;\r
12641     while (*str && isspace(*str)) ++str;\r
12642     if (!*str) return 1;\r
12643     return 0;\r
12644 }\r
12645 \r
12646 void\r
12647 DisplayAnalysis()\r
12648 {\r
12649     char buf[MSG_SIZ];\r
12650     char lst[MSG_SIZ / 2];\r
12651     double nps;\r
12652     static char *xtra[] = { "", " (--)", " (++)" };\r
12653     int h, m, s, cs;\r
12654   \r
12655     if (programStats.time == 0) {\r
12656         programStats.time = 1;\r
12657     }\r
12658   \r
12659     if (programStats.got_only_move) {\r
12660         safeStrCpy(buf, programStats.movelist, sizeof(buf));\r
12661     } else {\r
12662         safeStrCpy( lst, programStats.movelist, sizeof(lst));\r
12663 \r
12664         nps = (u64ToDouble(programStats.nodes) /\r
12665              ((double)programStats.time /100.0));\r
12666 \r
12667         cs = programStats.time % 100;\r
12668         s = programStats.time / 100;\r
12669         h = (s / (60*60));\r
12670         s = s - h*60*60;\r
12671         m = (s/60);\r
12672         s = s - m*60;\r
12673 \r
12674         if (programStats.moves_left > 0 && appData.periodicUpdates) {\r
12675           if (programStats.move_name[0] != NULLCHAR) {\r
12676             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12677                     programStats.depth,\r
12678                     programStats.nr_moves-programStats.moves_left,\r
12679                     programStats.nr_moves, programStats.move_name,\r
12680                     ((float)programStats.score)/100.0, lst,\r
12681                     only_one_move(lst)?\r
12682                     xtra[programStats.got_fail] : "",\r
12683                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12684           } else {\r
12685             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12686                     programStats.depth,\r
12687                     programStats.nr_moves-programStats.moves_left,\r
12688                     programStats.nr_moves, ((float)programStats.score)/100.0,\r
12689                     lst,\r
12690                     only_one_move(lst)?\r
12691                     xtra[programStats.got_fail] : "",\r
12692                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12693           }\r
12694         } else {\r
12695             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
12696                     programStats.depth,\r
12697                     ((float)programStats.score)/100.0,\r
12698                     lst,\r
12699                     only_one_move(lst)?\r
12700                     xtra[programStats.got_fail] : "",\r
12701                     (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
12702         }\r
12703     }\r
12704     DisplayAnalysisText(buf);\r
12705 }\r
12706 \r
12707 void\r
12708 DisplayComment(moveNumber, text)\r
12709      int moveNumber;\r
12710      char *text;\r
12711 {\r
12712     char title[MSG_SIZ];\r
12713     char buf[8000]; // comment can be long!\r
12714     int score, depth;\r
12715 \r
12716     if( appData.autoDisplayComment ) {\r
12717         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
12718             strcpy(title, "Comment");\r
12719         } else {\r
12720             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,\r
12721                     WhiteOnMove(moveNumber) ? " " : ".. ",\r
12722                     parseList[moveNumber]);\r
12723         }\r
12724     } else title[0] = 0;\r
12725 \r
12726     // [HGM] PV info: display PV info together with (or as) comment\r
12727     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {\r
12728         if(text == NULL) text = "";                                           \r
12729         score = pvInfoList[moveNumber].score;\r
12730         sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,\r
12731                               depth, (pvInfoList[moveNumber].time+50)/100, text);\r
12732         CommentPopUp(title, buf);\r
12733     } else\r
12734     if (text != NULL)\r
12735         CommentPopUp(title, text);\r
12736 }\r
12737 \r
12738 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it\r
12739  * might be busy thinking or pondering.  It can be omitted if your\r
12740  * gnuchess is configured to stop thinking immediately on any user\r
12741  * input.  However, that gnuchess feature depends on the FIONREAD\r
12742  * ioctl, which does not work properly on some flavors of Unix.\r
12743  */\r
12744 void\r
12745 Attention(cps)\r
12746      ChessProgramState *cps;\r
12747 {\r
12748 #if ATTENTION\r
12749     if (!cps->useSigint) return;\r
12750     if (appData.noChessProgram || (cps->pr == NoProc)) return;\r
12751     switch (gameMode) {\r
12752       case MachinePlaysWhite:\r
12753       case MachinePlaysBlack:\r
12754       case TwoMachinesPlay:\r
12755       case IcsPlayingWhite:\r
12756       case IcsPlayingBlack:\r
12757       case AnalyzeMode:\r
12758       case AnalyzeFile:\r
12759         /* Skip if we know it isn't thinking */\r
12760         if (!cps->maybeThinking) return;\r
12761         if (appData.debugMode)\r
12762           fprintf(debugFP, "Interrupting %s\n", cps->which);\r
12763         InterruptChildProcess(cps->pr);\r
12764         cps->maybeThinking = FALSE;\r
12765         break;\r
12766       default:\r
12767         break;\r
12768     }\r
12769 #endif /*ATTENTION*/\r
12770 }\r
12771 \r
12772 int\r
12773 CheckFlags()\r
12774 {\r
12775     if (whiteTimeRemaining <= 0) {\r
12776         if (!whiteFlag) {\r
12777             whiteFlag = TRUE;\r
12778             if (appData.icsActive) {\r
12779                 if (appData.autoCallFlag &&\r
12780                     gameMode == IcsPlayingBlack && !blackFlag) {\r
12781                   SendToICS(ics_prefix);\r
12782                   SendToICS("flag\n");\r
12783                 }\r
12784             } else {\r
12785                 if (blackFlag) {\r
12786                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));\r
12787                 } else {\r
12788                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));\r
12789                     if (appData.autoCallFlag) {\r
12790                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);\r
12791                         return TRUE;\r
12792                     }\r
12793                 }\r
12794             }\r
12795         }\r
12796     }\r
12797     if (blackTimeRemaining <= 0) {\r
12798         if (!blackFlag) {\r
12799             blackFlag = TRUE;\r
12800             if (appData.icsActive) {\r
12801                 if (appData.autoCallFlag &&\r
12802                     gameMode == IcsPlayingWhite && !whiteFlag) {\r
12803                   SendToICS(ics_prefix);\r
12804                   SendToICS("flag\n");\r
12805                 }\r
12806             } else {\r
12807                 if (whiteFlag) {\r
12808                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));\r
12809                 } else {\r
12810                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));\r
12811                     if (appData.autoCallFlag) {\r
12812                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);\r
12813                         return TRUE;\r
12814                     }\r
12815                 }\r
12816             }\r
12817         }\r
12818     }\r
12819     return FALSE;\r
12820 }\r
12821 \r
12822 void\r
12823 CheckTimeControl()\r
12824 {\r
12825     if (!appData.clockMode || appData.icsActive ||\r
12826         gameMode == PlayFromGameFile || forwardMostMove == 0) return;\r
12827 \r
12828     /*\r
12829      * add time to clocks when time control is achieved ([HGM] now also used for increment)\r
12830      */\r
12831     if ( !WhiteOnMove(forwardMostMove) )\r
12832         /* White made time control */\r
12833         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
12834         /* [HGM] time odds: correct new time quota for time odds! */\r
12835                                             / WhitePlayer()->timeOdds;\r
12836       else\r
12837         /* Black made time control */\r
12838         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
12839                                             / WhitePlayer()->other->timeOdds;\r
12840 }\r
12841 \r
12842 void\r
12843 DisplayBothClocks()\r
12844 {\r
12845     int wom = gameMode == EditPosition ?\r
12846       !blackPlaysFirst : WhiteOnMove(currentMove);\r
12847     DisplayWhiteClock(whiteTimeRemaining, wom);\r
12848     DisplayBlackClock(blackTimeRemaining, !wom);\r
12849 }\r
12850 \r
12851 \r
12852 /* Timekeeping seems to be a portability nightmare.  I think everyone\r
12853    has ftime(), but I'm really not sure, so I'm including some ifdefs\r
12854    to use other calls if you don't.  Clocks will be less accurate if\r
12855    you have neither ftime nor gettimeofday.\r
12856 */\r
12857 \r
12858 /* VS 2008 requires the #include outside of the function */\r
12859 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME\r
12860 #include <sys/timeb.h>\r
12861 #endif\r
12862 \r
12863 /* Get the current time as a TimeMark */\r
12864 void\r
12865 GetTimeMark(tm)\r
12866      TimeMark *tm;\r
12867 {\r
12868 #if HAVE_GETTIMEOFDAY\r
12869 \r
12870     struct timeval timeVal;\r
12871     struct timezone timeZone;\r
12872 \r
12873     gettimeofday(&timeVal, &timeZone);\r
12874     tm->sec = (long) timeVal.tv_sec; \r
12875     tm->ms = (int) (timeVal.tv_usec / 1000L);\r
12876 \r
12877 #else /*!HAVE_GETTIMEOFDAY*/\r
12878 #if HAVE_FTIME\r
12879 \r
12880 // include <sys/timeb.h> / moved to just above start of function\r
12881     struct timeb timeB;\r
12882 \r
12883     ftime(&timeB);\r
12884     tm->sec = (long) timeB.time;\r
12885     tm->ms = (int) timeB.millitm;\r
12886 \r
12887 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/\r
12888     tm->sec = (long) time(NULL);\r
12889     tm->ms = 0;\r
12890 #endif\r
12891 #endif\r
12892 }\r
12893 \r
12894 /* Return the difference in milliseconds between two\r
12895    time marks.  We assume the difference will fit in a long!\r
12896 */\r
12897 long\r
12898 SubtractTimeMarks(tm2, tm1)\r
12899      TimeMark *tm2, *tm1;\r
12900 {\r
12901     return 1000L*(tm2->sec - tm1->sec) +\r
12902            (long) (tm2->ms - tm1->ms);\r
12903 }\r
12904 \r
12905 \r
12906 /*\r
12907  * Code to manage the game clocks.\r
12908  *\r
12909  * In tournament play, black starts the clock and then white makes a move.\r
12910  * We give the human user a slight advantage if he is playing white---the\r
12911  * clocks don't run until he makes his first move, so it takes zero time.\r
12912  * Also, we don't account for network lag, so we could get out of sync\r
12913  * with GNU Chess's clock -- but then, referees are always right.  \r
12914  */\r
12915 \r
12916 static TimeMark tickStartTM;\r
12917 static long intendedTickLength;\r
12918 \r
12919 long\r
12920 NextTickLength(timeRemaining)\r
12921      long timeRemaining;\r
12922 {\r
12923     long nominalTickLength, nextTickLength;\r
12924 \r
12925     if (timeRemaining > 0L && timeRemaining <= 10000L)\r
12926       nominalTickLength = 100L;\r
12927     else\r
12928       nominalTickLength = 1000L;\r
12929     nextTickLength = timeRemaining % nominalTickLength;\r
12930     if (nextTickLength <= 0) nextTickLength += nominalTickLength;\r
12931 \r
12932     return nextTickLength;\r
12933 }\r
12934 \r
12935 /* Adjust clock one minute up or down */\r
12936 void\r
12937 AdjustClock(Boolean which, int dir)\r
12938 {\r
12939     if(which) blackTimeRemaining += 60000*dir;\r
12940     else      whiteTimeRemaining += 60000*dir;\r
12941     DisplayBothClocks();\r
12942 }\r
12943 \r
12944 /* Stop clocks and reset to a fresh time control */\r
12945 void\r
12946 ResetClocks() \r
12947 {\r
12948     (void) StopClockTimer();\r
12949     if (appData.icsActive) {\r
12950         whiteTimeRemaining = blackTimeRemaining = 0;\r
12951     } else { /* [HGM] correct new time quote for time odds */\r
12952         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;\r
12953         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;\r
12954     }\r
12955     if (whiteFlag || blackFlag) {\r
12956         DisplayTitle("");\r
12957         whiteFlag = blackFlag = FALSE;\r
12958     }\r
12959     DisplayBothClocks();\r
12960 }\r
12961 \r
12962 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */\r
12963 \r
12964 /* Decrement running clock by amount of time that has passed */\r
12965 void\r
12966 DecrementClocks()\r
12967 {\r
12968     long timeRemaining;\r
12969     long lastTickLength, fudge;\r
12970     TimeMark now;\r
12971 \r
12972     if (!appData.clockMode) return;\r
12973     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;\r
12974         \r
12975     GetTimeMark(&now);\r
12976 \r
12977     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
12978 \r
12979     /* Fudge if we woke up a little too soon */\r
12980     fudge = intendedTickLength - lastTickLength;\r
12981     if (fudge < 0 || fudge > FUDGE) fudge = 0;\r
12982 \r
12983     if (WhiteOnMove(forwardMostMove)) {\r
12984         if(whiteNPS >= 0) lastTickLength = 0;\r
12985         timeRemaining = whiteTimeRemaining -= lastTickLength;\r
12986         DisplayWhiteClock(whiteTimeRemaining - fudge,\r
12987                           WhiteOnMove(currentMove));\r
12988     } else {\r
12989         if(blackNPS >= 0) lastTickLength = 0;\r
12990         timeRemaining = blackTimeRemaining -= lastTickLength;\r
12991         DisplayBlackClock(blackTimeRemaining - fudge,\r
12992                           !WhiteOnMove(currentMove));\r
12993     }\r
12994 \r
12995     if (CheckFlags()) return;\r
12996         \r
12997     tickStartTM = now;\r
12998     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;\r
12999     StartClockTimer(intendedTickLength);\r
13000 \r
13001     /* if the time remaining has fallen below the alarm threshold, sound the\r
13002      * alarm. if the alarm has sounded and (due to a takeback or time control\r
13003      * with increment) the time remaining has increased to a level above the\r
13004      * threshold, reset the alarm so it can sound again. \r
13005      */\r
13006     \r
13007     if (appData.icsActive && appData.icsAlarm) {\r
13008 \r
13009         /* make sure we are dealing with the user's clock */\r
13010         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||\r
13011                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))\r
13012            )) return;\r
13013 \r
13014         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {\r
13015             alarmSounded = FALSE;\r
13016         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { \r
13017             PlayAlarmSound();\r
13018             alarmSounded = TRUE;\r
13019         }\r
13020     }\r
13021 }\r
13022 \r
13023 \r
13024 /* A player has just moved, so stop the previously running\r
13025    clock and (if in clock mode) start the other one.\r
13026    We redisplay both clocks in case we're in ICS mode, because\r
13027    ICS gives us an update to both clocks after every move.\r
13028    Note that this routine is called *after* forwardMostMove\r
13029    is updated, so the last fractional tick must be subtracted\r
13030    from the color that is *not* on move now.\r
13031 */\r
13032 void\r
13033 SwitchClocks()\r
13034 {\r
13035     long lastTickLength;\r
13036     TimeMark now;\r
13037     int flagged = FALSE;\r
13038 \r
13039     GetTimeMark(&now);\r
13040 \r
13041     if (StopClockTimer() && appData.clockMode) {\r
13042         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13043         if (WhiteOnMove(forwardMostMove)) {\r
13044             if(blackNPS >= 0) lastTickLength = 0;\r
13045             blackTimeRemaining -= lastTickLength;\r
13046            /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
13047 //         if(pvInfoList[forwardMostMove-1].time == -1)\r
13048                  pvInfoList[forwardMostMove-1].time =               // use GUI time\r
13049                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;\r
13050         } else {\r
13051            if(whiteNPS >= 0) lastTickLength = 0;\r
13052            whiteTimeRemaining -= lastTickLength;\r
13053            /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
13054 //         if(pvInfoList[forwardMostMove-1].time == -1)\r
13055                  pvInfoList[forwardMostMove-1].time = \r
13056                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;\r
13057         }\r
13058         flagged = CheckFlags();\r
13059     }\r
13060     CheckTimeControl();\r
13061 \r
13062     if (flagged || !appData.clockMode) return;\r
13063 \r
13064     switch (gameMode) {\r
13065       case MachinePlaysBlack:\r
13066       case MachinePlaysWhite:\r
13067       case BeginningOfGame:\r
13068         if (pausing) return;\r
13069         break;\r
13070 \r
13071       case EditGame:\r
13072       case PlayFromGameFile:\r
13073       case IcsExamining:\r
13074         return;\r
13075 \r
13076       default:\r
13077         break;\r
13078     }\r
13079 \r
13080     tickStartTM = now;\r
13081     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
13082       whiteTimeRemaining : blackTimeRemaining);\r
13083     StartClockTimer(intendedTickLength);\r
13084 }\r
13085         \r
13086 \r
13087 /* Stop both clocks */\r
13088 void\r
13089 StopClocks()\r
13090 {       \r
13091     long lastTickLength;\r
13092     TimeMark now;\r
13093 \r
13094     if (!StopClockTimer()) return;\r
13095     if (!appData.clockMode) return;\r
13096 \r
13097     GetTimeMark(&now);\r
13098 \r
13099     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
13100     if (WhiteOnMove(forwardMostMove)) {\r
13101         if(whiteNPS >= 0) lastTickLength = 0;\r
13102         whiteTimeRemaining -= lastTickLength;\r
13103         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));\r
13104     } else {\r
13105         if(blackNPS >= 0) lastTickLength = 0;\r
13106         blackTimeRemaining -= lastTickLength;\r
13107         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));\r
13108     }\r
13109     CheckFlags();\r
13110 }\r
13111         \r
13112 /* Start clock of player on move.  Time may have been reset, so\r
13113    if clock is already running, stop and restart it. */\r
13114 void\r
13115 StartClocks()\r
13116 {\r
13117     (void) StopClockTimer(); /* in case it was running already */\r
13118     DisplayBothClocks();\r
13119     if (CheckFlags()) return;\r
13120 \r
13121     if (!appData.clockMode) return;\r
13122     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;\r
13123 \r
13124     GetTimeMark(&tickStartTM);\r
13125     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
13126       whiteTimeRemaining : blackTimeRemaining);\r
13127 \r
13128    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */\r
13129     whiteNPS = blackNPS = -1; \r
13130     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'\r
13131        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white\r
13132         whiteNPS = first.nps;\r
13133     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'\r
13134        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black\r
13135         blackNPS = first.nps;\r
13136     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode\r
13137         whiteNPS = second.nps;\r
13138     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')\r
13139         blackNPS = second.nps;\r
13140     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);\r
13141 \r
13142     StartClockTimer(intendedTickLength);\r
13143 }\r
13144 \r
13145 char *\r
13146 TimeString(ms)\r
13147      long ms;\r
13148 {\r
13149     long second, minute, hour, day;\r
13150     char *sign = "";\r
13151     static char buf[32];\r
13152     \r
13153     if (ms > 0 && ms <= 9900) {\r
13154       /* convert milliseconds to tenths, rounding up */\r
13155       double tenths = floor( ((double)(ms + 99L)) / 100.00 );\r
13156 \r
13157       sprintf(buf, " %03.1f ", tenths/10.0);\r
13158       return buf;\r
13159     }\r
13160 \r
13161     /* convert milliseconds to seconds, rounding up */\r
13162     /* use floating point to avoid strangeness of integer division\r
13163        with negative dividends on many machines */\r
13164     second = (long) floor(((double) (ms + 999L)) / 1000.0);\r
13165 \r
13166     if (second < 0) {\r
13167         sign = "-";\r
13168         second = -second;\r
13169     }\r
13170     \r
13171     day = second / (60 * 60 * 24);\r
13172     second = second % (60 * 60 * 24);\r
13173     hour = second / (60 * 60);\r
13174     second = second % (60 * 60);\r
13175     minute = second / 60;\r
13176     second = second % 60;\r
13177     \r
13178     if (day > 0)\r
13179       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",\r
13180               sign, day, hour, minute, second);\r
13181     else if (hour > 0)\r
13182       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);\r
13183     else\r
13184       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);\r
13185     \r
13186     return buf;\r
13187 }\r
13188 \r
13189 \r
13190 /*\r
13191  * This is necessary because some C libraries aren't ANSI C compliant yet.\r
13192  */\r
13193 char *\r
13194 StrStr(string, match)\r
13195      char *string, *match;\r
13196 {\r
13197     int i, length;\r
13198     \r
13199     length = strlen(match);\r
13200     \r
13201     for (i = strlen(string) - length; i >= 0; i--, string++)\r
13202       if (!strncmp(match, string, length))\r
13203         return string;\r
13204     \r
13205     return NULL;\r
13206 }\r
13207 \r
13208 char *\r
13209 StrCaseStr(string, match)\r
13210      char *string, *match;\r
13211 {\r
13212     int i, j, length;\r
13213     \r
13214     length = strlen(match);\r
13215     \r
13216     for (i = strlen(string) - length; i >= 0; i--, string++) {\r
13217         for (j = 0; j < length; j++) {\r
13218             if (ToLower(match[j]) != ToLower(string[j]))\r
13219               break;\r
13220         }\r
13221         if (j == length) return string;\r
13222     }\r
13223 \r
13224     return NULL;\r
13225 }\r
13226 \r
13227 #ifndef _amigados\r
13228 int\r
13229 StrCaseCmp(s1, s2)\r
13230      char *s1, *s2;\r
13231 {\r
13232     char c1, c2;\r
13233     \r
13234     for (;;) {\r
13235         c1 = ToLower(*s1++);\r
13236         c2 = ToLower(*s2++);\r
13237         if (c1 > c2) return 1;\r
13238         if (c1 < c2) return -1;\r
13239         if (c1 == NULLCHAR) return 0;\r
13240     }\r
13241 }\r
13242 \r
13243 \r
13244 int\r
13245 ToLower(c)\r
13246      int c;\r
13247 {\r
13248     return isupper(c) ? tolower(c) : c;\r
13249 }\r
13250 \r
13251 \r
13252 int\r
13253 ToUpper(c)\r
13254      int c;\r
13255 {\r
13256     return islower(c) ? toupper(c) : c;\r
13257 }\r
13258 #endif /* !_amigados    */\r
13259 \r
13260 char *\r
13261 StrSave(s)\r
13262      char *s;\r
13263 {\r
13264     char *ret;\r
13265 \r
13266     if ((ret = (char *) malloc(strlen(s) + 1))) {\r
13267         strcpy(ret, s);\r
13268     }\r
13269     return ret;\r
13270 }\r
13271 \r
13272 char *\r
13273 StrSavePtr(s, savePtr)\r
13274      char *s, **savePtr;\r
13275 {\r
13276     if (*savePtr) {\r
13277         free(*savePtr);\r
13278     }\r
13279     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {\r
13280         strcpy(*savePtr, s);\r
13281     }\r
13282     return(*savePtr);\r
13283 }\r
13284 \r
13285 char *\r
13286 PGNDate()\r
13287 {\r
13288     time_t clock;\r
13289     struct tm *tm;\r
13290     char buf[MSG_SIZ];\r
13291 \r
13292     clock = time((time_t *)NULL);\r
13293     tm = localtime(&clock);\r
13294     sprintf(buf, "%04d.%02d.%02d",\r
13295             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);\r
13296     return StrSave(buf);\r
13297 }\r
13298 \r
13299 \r
13300 char *\r
13301 PositionToFEN(move, overrideCastling)\r
13302      int move;\r
13303      char *overrideCastling;\r
13304 {\r
13305     int i, j, fromX, fromY, toX, toY;\r
13306     int whiteToPlay;\r
13307     char buf[128];\r
13308     char *p, *q;\r
13309     int emptycount;\r
13310     ChessSquare piece;\r
13311 \r
13312     whiteToPlay = (gameMode == EditPosition) ?\r
13313       !blackPlaysFirst : (move % 2 == 0);\r
13314     p = buf;\r
13315 \r
13316     /* Piece placement data */\r
13317     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
13318         emptycount = 0;\r
13319         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
13320             if (boards[move][i][j] == EmptySquare) {\r
13321                 emptycount++;\r
13322             } else { ChessSquare piece = boards[move][i][j];\r
13323                 if (emptycount > 0) {\r
13324                     if(emptycount<10) /* [HGM] can be >= 10 */\r
13325                         *p++ = '0' + emptycount;\r
13326                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
13327                     emptycount = 0;\r
13328                 }\r
13329                 if(PieceToChar(piece) == '+') {\r
13330                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */\r
13331                     *p++ = '+';\r
13332                     piece = (ChessSquare)(DEMOTED piece);\r
13333                 } \r
13334                 *p++ = PieceToChar(piece);\r
13335                 if(p[-1] == '~') {\r
13336                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */\r
13337                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));\r
13338                     *p++ = '~';\r
13339                 }\r
13340             }\r
13341         }\r
13342         if (emptycount > 0) {\r
13343             if(emptycount<10) /* [HGM] can be >= 10 */\r
13344                 *p++ = '0' + emptycount;\r
13345             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
13346             emptycount = 0;\r
13347         }\r
13348         *p++ = '/';\r
13349     }\r
13350     *(p - 1) = ' ';\r
13351 \r
13352     /* [HGM] print Crazyhouse or Shogi holdings */\r
13353     if( gameInfo.holdingsWidth ) {\r
13354         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */\r
13355         q = p;\r
13356         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */\r
13357             piece = boards[move][i][BOARD_WIDTH-1];\r
13358             if( piece != EmptySquare )\r
13359               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)\r
13360                   *p++ = PieceToChar(piece);\r
13361         }\r
13362         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */\r
13363             piece = boards[move][BOARD_HEIGHT-i-1][0];\r
13364             if( piece != EmptySquare )\r
13365               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)\r
13366                   *p++ = PieceToChar(piece);\r
13367         }\r
13368 \r
13369         if( q == p ) *p++ = '-';\r
13370         *p++ = ']';\r
13371         *p++ = ' ';\r
13372     }\r
13373 \r
13374     /* Active color */\r
13375     *p++ = whiteToPlay ? 'w' : 'b';\r
13376     *p++ = ' ';\r
13377 \r
13378   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines\r
13379     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';\r
13380   } else {\r
13381   if(nrCastlingRights) {\r
13382      q = p;\r
13383      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {\r
13384        /* [HGM] write directly from rights */\r
13385            if(castlingRights[move][2] >= 0 &&\r
13386               castlingRights[move][0] >= 0   )\r
13387                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';\r
13388            if(castlingRights[move][2] >= 0 &&\r
13389               castlingRights[move][1] >= 0   )\r
13390                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';\r
13391            if(castlingRights[move][5] >= 0 &&\r
13392               castlingRights[move][3] >= 0   )\r
13393                 *p++ = castlingRights[move][3] + AAA;\r
13394            if(castlingRights[move][5] >= 0 &&\r
13395               castlingRights[move][4] >= 0   )\r
13396                 *p++ = castlingRights[move][4] + AAA;\r
13397      } else {\r
13398 \r
13399         /* [HGM] write true castling rights */\r
13400         if( nrCastlingRights == 6 ) {\r
13401             if(castlingRights[move][0] == BOARD_RGHT-1 &&\r
13402                castlingRights[move][2] >= 0  ) *p++ = 'K';\r
13403             if(castlingRights[move][1] == BOARD_LEFT &&\r
13404                castlingRights[move][2] >= 0  ) *p++ = 'Q';\r
13405             if(castlingRights[move][3] == BOARD_RGHT-1 &&\r
13406                castlingRights[move][5] >= 0  ) *p++ = 'k';\r
13407             if(castlingRights[move][4] == BOARD_LEFT &&\r
13408                castlingRights[move][5] >= 0  ) *p++ = 'q';\r
13409         }\r
13410      }\r
13411      if (q == p) *p++ = '-'; /* No castling rights */\r
13412      *p++ = ' ';\r
13413   }\r
13414 \r
13415   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
13416      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
13417     /* En passant target square */\r
13418     if (move > backwardMostMove) {\r
13419         fromX = moveList[move - 1][0] - AAA;\r
13420         fromY = moveList[move - 1][1] - ONE;\r
13421         toX = moveList[move - 1][2] - AAA;\r
13422         toY = moveList[move - 1][3] - ONE;\r
13423         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&\r
13424             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&\r
13425             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&\r
13426             fromX == toX) {\r
13427             /* 2-square pawn move just happened */\r
13428             *p++ = toX + AAA;\r
13429             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';\r
13430         } else {\r
13431             *p++ = '-';\r
13432         }\r
13433     } else {\r
13434         *p++ = '-';\r
13435     }\r
13436     *p++ = ' ';\r
13437   }\r
13438   }\r
13439 \r
13440     /* [HGM] find reversible plies */\r
13441     {   int i = 0, j=move;\r
13442 \r
13443         if (appData.debugMode) { int k;\r
13444             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);\r
13445             for(k=backwardMostMove; k<=forwardMostMove; k++)\r
13446                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);\r
13447 \r
13448         }\r
13449 \r
13450         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;\r
13451         if( j == backwardMostMove ) i += initialRulePlies;\r
13452         sprintf(p, "%d ", i);\r
13453         p += i>=100 ? 4 : i >= 10 ? 3 : 2;\r
13454     }\r
13455     /* Fullmove number */\r
13456     sprintf(p, "%d", (move / 2) + 1);\r
13457     \r
13458     return StrSave(buf);\r
13459 }\r
13460 \r
13461 Boolean\r
13462 ParseFEN(board, blackPlaysFirst, fen)\r
13463     Board board;\r
13464      int *blackPlaysFirst;\r
13465      char *fen;\r
13466 {\r
13467     int i, j;\r
13468     char *p;\r
13469     int emptycount;\r
13470     ChessSquare piece;\r
13471 \r
13472     p = fen;\r
13473 \r
13474     /* [HGM] by default clear Crazyhouse holdings, if present */\r
13475     if(gameInfo.holdingsWidth) {\r
13476        for(i=0; i<BOARD_HEIGHT; i++) {\r
13477            board[i][0]             = EmptySquare; /* black holdings */\r
13478            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */\r
13479            board[i][1]             = (ChessSquare) 0; /* black counts */\r
13480            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */\r
13481        }\r
13482     }\r
13483 \r
13484     /* Piece placement data */\r
13485     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
13486         j = 0;\r
13487         for (;;) {\r
13488             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {\r
13489                 if (*p == '/') p++;\r
13490                 emptycount = gameInfo.boardWidth - j;\r
13491                 while (emptycount--)\r
13492                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13493                 break;\r
13494 #if(BOARD_SIZE >= 10)\r
13495             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */\r
13496                 p++; emptycount=10;\r
13497                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
13498                 while (emptycount--)\r
13499                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13500 #endif\r
13501             } else if (isdigit(*p)) {\r
13502                 emptycount = *p++ - '0';\r
13503                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */\r
13504                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
13505                 while (emptycount--)\r
13506                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
13507             } else if (*p == '+' || isalpha(*p)) {\r
13508                 if (j >= gameInfo.boardWidth) return FALSE;\r
13509                 if(*p=='+') {\r
13510                     piece = CharToPiece(*++p);\r
13511                     if(piece == EmptySquare) return FALSE; /* unknown piece */\r
13512                     piece = (ChessSquare) (PROMOTED piece ); p++;\r
13513                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */\r
13514                 } else piece = CharToPiece(*p++);\r
13515 \r
13516                 if(piece==EmptySquare) return FALSE; /* unknown piece */\r
13517                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */\r
13518                     piece = (ChessSquare) (PROMOTED piece);\r
13519                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */\r
13520                     p++;\r
13521                 }\r
13522                 board[i][(j++)+gameInfo.holdingsWidth] = piece;\r
13523             } else {\r
13524                 return FALSE;\r
13525             }\r
13526         }\r
13527     }\r
13528     while (*p == '/' || *p == ' ') p++;\r
13529 \r
13530     /* [HGM] look for Crazyhouse holdings here */\r
13531     while(*p==' ') p++;\r
13532     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {\r
13533         if(*p == '[') p++;\r
13534         if(*p == '-' ) *p++; /* empty holdings */ else {\r
13535             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */\r
13536             /* if we would allow FEN reading to set board size, we would   */\r
13537             /* have to add holdings and shift the board read so far here   */\r
13538             while( (piece = CharToPiece(*p) ) != EmptySquare ) {\r
13539                 *p++;\r
13540                 if((int) piece >= (int) BlackPawn ) {\r
13541                     i = (int)piece - (int)BlackPawn;\r
13542                     i = PieceToNumber((ChessSquare)i);\r
13543                     if( i >= gameInfo.holdingsSize ) return FALSE;\r
13544                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */\r
13545                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */\r
13546                 } else {\r
13547                     i = (int)piece - (int)WhitePawn;\r
13548                     i = PieceToNumber((ChessSquare)i);\r
13549                     if( i >= gameInfo.holdingsSize ) return FALSE;\r
13550                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */\r
13551                     board[i][BOARD_WIDTH-2]++;          /* black holdings */\r
13552                 }\r
13553             }\r
13554         }\r
13555         if(*p == ']') *p++;\r
13556     }\r
13557 \r
13558     while(*p == ' ') p++;\r
13559 \r
13560     /* Active color */\r
13561     switch (*p++) {\r
13562       case 'w':\r
13563         *blackPlaysFirst = FALSE;\r
13564         break;\r
13565       case 'b': \r
13566         *blackPlaysFirst = TRUE;\r
13567         break;\r
13568       default:\r
13569         return FALSE;\r
13570     }\r
13571 \r
13572     /* [HGM] We NO LONGER ignore the rest of the FEN notation */\r
13573     /* return the extra info in global variiables             */\r
13574 \r
13575     /* set defaults in case FEN is incomplete */\r
13576     FENepStatus = EP_UNKNOWN;\r
13577     for(i=0; i<nrCastlingRights; i++ ) {\r
13578         FENcastlingRights[i] =\r
13579             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];\r
13580     }   /* assume possible unless obviously impossible */\r
13581     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;\r
13582     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;\r
13583     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;\r
13584     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;\r
13585     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;\r
13586     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;\r
13587     FENrulePlies = 0;\r
13588 \r
13589     while(*p==' ') p++;\r
13590     if(nrCastlingRights) {\r
13591       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {\r
13592           /* castling indicator present, so default becomes no castlings */\r
13593           for(i=0; i<nrCastlingRights; i++ ) {\r
13594                  FENcastlingRights[i] = -1;\r
13595           }\r
13596       }\r
13597       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||\r
13598              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&\r
13599              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||\r
13600              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {\r
13601         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;\r
13602 \r
13603         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {\r
13604             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;\r
13605             if(board[0             ][i] == WhiteKing) whiteKingFile = i;\r
13606         }\r
13607         switch(c) {\r
13608           case'K':\r
13609               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);\r
13610               FENcastlingRights[0] = i != whiteKingFile ? i : -1;\r
13611               FENcastlingRights[2] = whiteKingFile;\r
13612               break;\r
13613           case'Q':\r
13614               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);\r
13615               FENcastlingRights[1] = i != whiteKingFile ? i : -1;\r
13616               FENcastlingRights[2] = whiteKingFile;\r
13617               break;\r
13618           case'k':\r
13619               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);\r
13620               FENcastlingRights[3] = i != blackKingFile ? i : -1;\r
13621               FENcastlingRights[5] = blackKingFile;\r
13622               break;\r
13623           case'q':\r
13624               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);\r
13625               FENcastlingRights[4] = i != blackKingFile ? i : -1;\r
13626               FENcastlingRights[5] = blackKingFile;\r
13627           case '-':\r
13628               break;\r
13629           default: /* FRC castlings */\r
13630               if(c >= 'a') { /* black rights */\r
13631                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
13632                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;\r
13633                   if(i == BOARD_RGHT) break;\r
13634                   FENcastlingRights[5] = i;\r
13635                   c -= AAA;\r
13636                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||\r
13637                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;\r
13638                   if(c > i)\r
13639                       FENcastlingRights[3] = c;\r
13640                   else\r
13641                       FENcastlingRights[4] = c;\r
13642               } else { /* white rights */\r
13643                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
13644                     if(board[0][i] == WhiteKing) break;\r
13645                   if(i == BOARD_RGHT) break;\r
13646                   FENcastlingRights[2] = i;\r
13647                   c -= AAA - 'a' + 'A';\r
13648                   if(board[0][c] >= WhiteKing) break;\r
13649                   if(c > i)\r
13650                       FENcastlingRights[0] = c;\r
13651                   else\r
13652                       FENcastlingRights[1] = c;\r
13653               }\r
13654         }\r
13655       }\r
13656     if (appData.debugMode) {\r
13657         fprintf(debugFP, "FEN castling rights:");\r
13658         for(i=0; i<nrCastlingRights; i++)\r
13659         fprintf(debugFP, " %d", FENcastlingRights[i]);\r
13660         fprintf(debugFP, "\n");\r
13661     }\r
13662 \r
13663       while(*p==' ') p++;\r
13664     }\r
13665 \r
13666     /* read e.p. field in games that know e.p. capture */\r
13667     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
13668        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
13669       if(*p=='-') {\r
13670         p++; FENepStatus = EP_NONE;\r
13671       } else {\r
13672          char c = *p++ - AAA;\r
13673 \r
13674          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;\r
13675          if(*p >= '0' && *p <='9') *p++;\r
13676          FENepStatus = c;\r
13677       }\r
13678     }\r
13679 \r
13680 \r
13681     if(sscanf(p, "%d", &i) == 1) {\r
13682         FENrulePlies = i; /* 50-move ply counter */\r
13683         /* (The move number is still ignored)    */\r
13684     }\r
13685 \r
13686     return TRUE;\r
13687 }\r
13688       \r
13689 void\r
13690 EditPositionPasteFEN(char *fen)\r
13691 {\r
13692   if (fen != NULL) {\r
13693     Board initial_position;\r
13694 \r
13695     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {\r
13696       DisplayError(_("Bad FEN position in clipboard"), 0);\r
13697       return ;\r
13698     } else {\r
13699       int savedBlackPlaysFirst = blackPlaysFirst;\r
13700       EditPositionEvent();\r
13701       blackPlaysFirst = savedBlackPlaysFirst;\r
13702       CopyBoard(boards[0], initial_position);\r
13703           /* [HGM] copy FEN attributes as well */\r
13704           {   int i;\r
13705               initialRulePlies = FENrulePlies;\r
13706               epStatus[0] = FENepStatus;\r
13707               for( i=0; i<nrCastlingRights; i++ )\r
13708                   castlingRights[0][i] = FENcastlingRights[i];\r
13709           }\r
13710       EditPositionDone();\r
13711       DisplayBothClocks();\r
13712       DrawPosition(FALSE, boards[currentMove]);\r
13713     }\r
13714   }\r
13715 }\r